# -*- coding: utf-8 -*- # Licensed under a 3-clause BSD style license - see LICENSE.rst import io import os import sys import subprocess import pytest from astropy.config import (configuration, set_temp_config, paths, create_config_file) from astropy.utils.data import get_pkg_data_filename from astropy.utils.exceptions import AstropyDeprecationWarning OLD_CONFIG = {} def setup_module(): OLD_CONFIG.clear() OLD_CONFIG.update(configuration._cfgobjs) def teardown_module(): configuration._cfgobjs.clear() configuration._cfgobjs.update(OLD_CONFIG) def test_paths(): assert 'astropy' in paths.get_config_dir() assert 'astropy' in paths.get_cache_dir() assert 'testpkg' in paths.get_config_dir(rootname='testpkg') assert 'testpkg' in paths.get_cache_dir(rootname='testpkg') def test_set_temp_config(tmpdir, monkeypatch): # Check that we start in an understood state. assert configuration._cfgobjs == OLD_CONFIG # Temporarily remove any temporary overrides of the configuration dir. monkeypatch.setattr(paths.set_temp_config, '_temp_path', None) orig_config_dir = paths.get_config_dir(rootname='astropy') temp_config_dir = str(tmpdir.mkdir('config')) temp_astropy_config = os.path.join(temp_config_dir, 'astropy') # Test decorator mode @paths.set_temp_config(temp_config_dir) def test_func(): assert paths.get_config_dir(rootname='astropy') == temp_astropy_config # Test temporary restoration of original default with paths.set_temp_config() as d: assert d == orig_config_dir == paths.get_config_dir(rootname='astropy') test_func() # Test context manager mode (with cleanup) with paths.set_temp_config(temp_config_dir, delete=True): assert paths.get_config_dir(rootname='astropy') == temp_astropy_config assert not os.path.exists(temp_config_dir) # Check that we have returned to our old configuration. assert configuration._cfgobjs == OLD_CONFIG def test_set_temp_cache(tmpdir, monkeypatch): monkeypatch.setattr(paths.set_temp_cache, '_temp_path', None) orig_cache_dir = paths.get_cache_dir(rootname='astropy') temp_cache_dir = str(tmpdir.mkdir('cache')) temp_astropy_cache = os.path.join(temp_cache_dir, 'astropy') # Test decorator mode @paths.set_temp_cache(temp_cache_dir) def test_func(): assert paths.get_cache_dir(rootname='astropy') == temp_astropy_cache # Test temporary restoration of original default with paths.set_temp_cache() as d: assert d == orig_cache_dir == paths.get_cache_dir(rootname='astropy') test_func() # Test context manager mode (with cleanup) with paths.set_temp_cache(temp_cache_dir, delete=True): assert paths.get_cache_dir(rootname='astropy') == temp_astropy_cache assert not os.path.exists(temp_cache_dir) def test_set_temp_cache_resets_on_exception(tmpdir): """Test for regression of bug #9704""" t = paths.get_cache_dir() a = tmpdir / 'a' with open(a, 'wt') as f: f.write("not a good cache\n") with pytest.raises(OSError): with paths.set_temp_cache(a): pass assert t == paths.get_cache_dir() def test_config_file(): from astropy.config.configuration import get_config, reload_config apycfg = get_config('astropy') assert apycfg.filename.endswith('astropy.cfg') cfgsec = get_config('astropy.config') assert cfgsec.depth == 1 assert cfgsec.name == 'config' assert cfgsec.parent.filename.endswith('astropy.cfg') # try with a different package name, still inside astropy config dir: testcfg = get_config('testpkg', rootname='astropy') parts = os.path.normpath(testcfg.filename).split(os.sep) assert '.astropy' in parts or 'astropy' in parts assert parts[-1] == 'testpkg.cfg' configuration._cfgobjs['testpkg'] = None # HACK # try with a different package name, no specified root name (should # default to astropy): testcfg = get_config('testpkg') parts = os.path.normpath(testcfg.filename).split(os.sep) assert '.astropy' in parts or 'astropy' in parts assert parts[-1] == 'testpkg.cfg' configuration._cfgobjs['testpkg'] = None # HACK # try with a different package name, specified root name: testcfg = get_config('testpkg', rootname='testpkg') parts = os.path.normpath(testcfg.filename).split(os.sep) assert '.testpkg' in parts or 'testpkg' in parts assert parts[-1] == 'testpkg.cfg' configuration._cfgobjs['testpkg'] = None # HACK # try with a subpackage with specified root name: testcfg_sec = get_config('testpkg.somemodule', rootname='testpkg') parts = os.path.normpath(testcfg_sec.parent.filename).split(os.sep) assert '.testpkg' in parts or 'testpkg' in parts assert parts[-1] == 'testpkg.cfg' configuration._cfgobjs['testpkg'] = None # HACK reload_config('astropy') def check_config(conf): # test that the output contains some lines that we expect assert '# unicode_output = False' in conf assert '[io.fits]' in conf assert '[table]' in conf assert '# replace_warnings = ,' in conf assert '[table.jsviewer]' in conf assert '# css_urls = https://cdn.datatables.net/1.10.12/css/jquery.dataTables.css,' in conf assert '[visualization.wcsaxes]' in conf assert '## Whether to log exceptions before raising them.' in conf assert '# log_exceptions = False' in conf def test_generate_config(tmp_path): from astropy.config.configuration import generate_config out = io.StringIO() generate_config('astropy', out) conf = out.getvalue() outfile = tmp_path / 'astropy.cfg' generate_config('astropy', outfile) with open(outfile) as fp: conf2 = fp.read() for c in (conf, conf2): check_config(c) def test_generate_config2(tmp_path): """Test that generate_config works with the default filename.""" with set_temp_config(tmp_path): from astropy.config.configuration import generate_config generate_config('astropy') assert os.path.exists(tmp_path / 'astropy' / 'astropy.cfg') with open(tmp_path / 'astropy' / 'astropy.cfg') as fp: conf = fp.read() check_config(conf) def test_create_config_file(tmp_path, caplog): with set_temp_config(tmp_path): create_config_file('astropy') # check that the config file has been created assert ('The configuration file has been successfully written' in caplog.records[0].message) assert os.path.exists(tmp_path / 'astropy' / 'astropy.cfg') with open(tmp_path / 'astropy' / 'astropy.cfg') as fp: conf = fp.read() check_config(conf) caplog.clear() # now modify the config file conf = conf.replace('# unicode_output = False', 'unicode_output = True') with open(tmp_path / 'astropy' / 'astropy.cfg', mode='w') as fp: fp.write(conf) with set_temp_config(tmp_path): create_config_file('astropy') # check that the config file has not been overwritten since it was modified assert ('The configuration file already exists and seems to have been ' 'customized' in caplog.records[0].message) caplog.clear() with set_temp_config(tmp_path): create_config_file('astropy', overwrite=True) # check that the config file has been overwritten assert ('The configuration file has been successfully written' in caplog.records[0].message) def test_configitem(): from astropy.config.configuration import ConfigNamespace, ConfigItem, get_config ci = ConfigItem(34, 'this is a Description') class Conf(ConfigNamespace): tstnm = ci conf = Conf() assert ci.module == 'astropy.config.tests.test_configs' assert ci() == 34 assert ci.description == 'this is a Description' assert conf.tstnm == 34 sec = get_config(ci.module) assert sec['tstnm'] == 34 ci.description = 'updated Descr' ci.set(32) assert ci() == 32 # It's useful to go back to the default to allow other test functions to # call this one and still be in the default configuration. ci.description = 'this is a Description' ci.set(34) assert ci() == 34 # Test iterator for one-item namespace result = [x for x in conf] assert result == ['tstnm'] result = [x for x in conf.keys()] assert result == ['tstnm'] result = [x for x in conf.values()] assert result == [ci] result = [x for x in conf.items()] assert result == [('tstnm', ci)] def test_configitem_types(): from astropy.config.configuration import ConfigNamespace, ConfigItem ci1 = ConfigItem(34) ci2 = ConfigItem(34.3) ci3 = ConfigItem(True) ci4 = ConfigItem('astring') class Conf(ConfigNamespace): tstnm1 = ci1 tstnm2 = ci2 tstnm3 = ci3 tstnm4 = ci4 conf = Conf() assert isinstance(conf.tstnm1, int) assert isinstance(conf.tstnm2, float) assert isinstance(conf.tstnm3, bool) assert isinstance(conf.tstnm4, str) with pytest.raises(TypeError): conf.tstnm1 = 34.3 conf.tstnm2 = 12 # this would should succeed as up-casting with pytest.raises(TypeError): conf.tstnm3 = 'fasd' with pytest.raises(TypeError): conf.tstnm4 = 546.245 # Test iterator for multi-item namespace. Assume ordered by insertion order. item_names = [x for x in conf] assert item_names == ['tstnm1', 'tstnm2', 'tstnm3', 'tstnm4'] result = [x for x in conf.keys()] assert result == item_names result = [x for x in conf.values()] assert result == [ci1, ci2, ci3, ci4] result = [x for x in conf.items()] assert result == [('tstnm1', ci1), ('tstnm2', ci2), ('tstnm3', ci3), ('tstnm4', ci4)] def test_configitem_options(tmpdir): from astropy.config.configuration import ConfigNamespace, ConfigItem, get_config cio = ConfigItem(['op1', 'op2', 'op3']) class Conf(ConfigNamespace): tstnmo = cio conf = Conf() # noqa sec = get_config(cio.module) assert isinstance(cio(), str) assert cio() == 'op1' assert sec['tstnmo'] == 'op1' cio.set('op2') with pytest.raises(TypeError): cio.set('op5') assert sec['tstnmo'] == 'op2' # now try saving apycfg = sec while apycfg.parent is not apycfg: apycfg = apycfg.parent f = tmpdir.join('astropy.cfg') with open(f.strpath, 'wb') as fd: apycfg.write(fd) with open(f.strpath, 'r', encoding='utf-8') as fd: lns = [x.strip() for x in f.readlines()] assert 'tstnmo = op2' in lns def test_config_noastropy_fallback(monkeypatch): """ Tests to make sure configuration items fall back to their defaults when there's a problem accessing the astropy directory """ # make sure the config directory is not searched monkeypatch.setenv('XDG_CONFIG_HOME', 'foo') monkeypatch.delenv('XDG_CONFIG_HOME') monkeypatch.setattr(paths.set_temp_config, '_temp_path', None) # make sure the _find_or_create_root_dir function fails as though the # astropy dir could not be accessed def osraiser(dirnm, linkto, pkgname=None): raise OSError monkeypatch.setattr(paths, '_find_or_create_root_dir', osraiser) # also have to make sure the stored configuration objects are cleared monkeypatch.setattr(configuration, '_cfgobjs', {}) with pytest.raises(OSError): # make sure the config dir search fails paths.get_config_dir(rootname='astropy') # now run the basic tests, and make sure the warning about no astropy # is present test_configitem() def test_configitem_setters(): from astropy.config.configuration import ConfigNamespace, ConfigItem class Conf(ConfigNamespace): tstnm12 = ConfigItem(42, 'this is another Description') conf = Conf() assert conf.tstnm12 == 42 with conf.set_temp('tstnm12', 45): assert conf.tstnm12 == 45 assert conf.tstnm12 == 42 conf.tstnm12 = 43 assert conf.tstnm12 == 43 with conf.set_temp('tstnm12', 46): assert conf.tstnm12 == 46 # Make sure it is reset even with Exception try: with conf.set_temp('tstnm12', 47): raise Exception except Exception: pass assert conf.tstnm12 == 43 def test_empty_config_file(): from astropy.config.configuration import is_unedited_config_file def get_content(fn): with open(get_pkg_data_filename(fn), 'rt', encoding='latin-1') as fd: return fd.read() content = get_content('data/empty.cfg') assert is_unedited_config_file(content) content = get_content('data/not_empty.cfg') assert not is_unedited_config_file(content) class TestAliasRead: def setup_class(self): configuration._override_config_file = get_pkg_data_filename('data/alias.cfg') def test_alias_read(self): from astropy.utils.data import conf with pytest.warns( AstropyDeprecationWarning, match=r"Config parameter 'name_resolve_timeout' in section " r"\[coordinates.name_resolve\].*") as w: conf.reload() assert conf.remote_timeout == 42 assert len(w) == 1 def teardown_class(self): from astropy.utils.data import conf configuration._override_config_file = None conf.reload() def test_configitem_unicode(tmpdir): from astropy.config.configuration import ConfigNamespace, ConfigItem, get_config cio = ConfigItem('ასტრონომიის') class Conf(ConfigNamespace): tstunicode = cio conf = Conf() # noqa sec = get_config(cio.module) assert isinstance(cio(), str) assert cio() == 'ასტრონომიის' assert sec['tstunicode'] == 'ასტრონომიის' def test_warning_move_to_top_level(): # Check that the warning about deprecation config items in the # file works. See #2514 from astropy import conf configuration._override_config_file = get_pkg_data_filename('data/deprecated.cfg') try: with pytest.warns(AstropyDeprecationWarning) as w: conf.reload() conf.max_lines assert len(w) == 1 finally: configuration._override_config_file = None conf.reload() def test_no_home(): # "import astropy" fails when neither $HOME or $XDG_CONFIG_HOME # are set. To test, we unset those environment variables for a # subprocess and try to import astropy. test_path = os.path.dirname(__file__) astropy_path = os.path.abspath( os.path.join(test_path, '..', '..', '..')) env = os.environ.copy() paths = [astropy_path] if env.get('PYTHONPATH'): paths.append(env.get('PYTHONPATH')) env['PYTHONPATH'] = os.pathsep.join(paths) for val in ['HOME', 'XDG_CONFIG_HOME']: if val in env: del env[val] retcode = subprocess.check_call( [sys.executable, '-c', 'import astropy'], env=env) assert retcode == 0