1212 lines
46 KiB
Python
1212 lines
46 KiB
Python
# Copyright (c) OpenMMLab. All rights reserved.
|
|
import argparse
|
|
import copy
|
|
import os
|
|
import os.path as osp
|
|
import pickle
|
|
import platform
|
|
import sys
|
|
import tempfile
|
|
from importlib import import_module
|
|
from pathlib import Path
|
|
from unittest import TestCase
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
import mmengine
|
|
from mmengine import Config, ConfigDict, DictAction
|
|
from mmengine.config.lazy import LazyObject
|
|
from mmengine.fileio import dump, load
|
|
from mmengine.registry import MODELS, DefaultScope, Registry
|
|
from mmengine.utils import is_installed
|
|
|
|
|
|
class TestConfig:
|
|
data_path = osp.join(osp.dirname(osp.dirname(__file__)), 'data/')
|
|
|
|
@pytest.mark.parametrize('file_format', ['py', 'json', 'yaml'])
|
|
def test_init(self, file_format):
|
|
# test init Config by __init__
|
|
cfg = Config()
|
|
assert cfg.filename is None
|
|
assert cfg.text == ''
|
|
assert len(cfg) == 0
|
|
assert cfg._cfg_dict == {}
|
|
|
|
# test `cfg_dict` parameter
|
|
# `cfg_dict` is either dict or None
|
|
with pytest.raises(TypeError, match='cfg_dict must be a dict'):
|
|
Config([0, 1])
|
|
|
|
# test `filename` parameter
|
|
cfg_dict = dict(
|
|
item1=[1, 2], item2=dict(a=0), item3=True, item4='test')
|
|
cfg_file = osp.join(
|
|
self.data_path,
|
|
f'config/{file_format}_config/simple_config.{file_format}')
|
|
cfg = Config(cfg_dict, filename=cfg_file)
|
|
assert isinstance(cfg, Config)
|
|
assert cfg.filename == cfg_file
|
|
assert cfg.text == open(cfg_file).read()
|
|
|
|
cfg_file = osp.join(
|
|
self.data_path,
|
|
f'config/{file_format}_config/test_reserved_key.{file_format}')
|
|
# reserved keys cannot be set in config
|
|
with pytest.raises(
|
|
KeyError, match='filename is reserved for config '
|
|
'file'):
|
|
Config.fromfile(cfg_file)
|
|
|
|
def test_fromfile(self):
|
|
# test whether import `custom_imports` from cfg_file.
|
|
cfg_file = osp.join(self.data_path, 'config',
|
|
'py_config/test_custom_import.py')
|
|
sys.path.append(osp.join(self.data_path, 'config/py_config'))
|
|
cfg = Config.fromfile(cfg_file, import_custom_modules=True)
|
|
assert isinstance(cfg, Config)
|
|
# If import successfully, os.environ[''TEST_VALUE''] will be
|
|
# set to 'test'
|
|
assert os.environ.pop('TEST_VALUE') == 'test'
|
|
sys.path.pop()
|
|
|
|
Config.fromfile(cfg_file, import_custom_modules=False)
|
|
assert 'TEST_VALUE' not in os.environ
|
|
sys.modules.pop('test_custom_import_module')
|
|
with pytest.raises(
|
|
ImportError, match='Failed to import custom modules from'):
|
|
Config.fromfile(cfg_file, import_custom_modules=True)
|
|
|
|
@pytest.mark.parametrize('file_format', ['py', 'json', 'yaml'])
|
|
def test_fromstring(self, file_format):
|
|
filename = f'{file_format}_config/simple_config.{file_format}'
|
|
cfg_file = osp.join(self.data_path, 'config', filename)
|
|
file_format = osp.splitext(filename)[-1]
|
|
in_cfg = Config.fromfile(cfg_file)
|
|
|
|
cfg_str = open(cfg_file).read()
|
|
out_cfg = Config.fromstring(cfg_str, file_format)
|
|
assert in_cfg._cfg_dict == out_cfg._cfg_dict
|
|
|
|
# test pretty_text only supports py file format
|
|
# in_cfg.pretty_text is .py format, cannot be parsed to .json
|
|
if file_format != '.py':
|
|
with pytest.raises(Exception):
|
|
Config.fromstring(in_cfg.pretty_text, file_format)
|
|
|
|
# error format
|
|
with pytest.raises(IOError):
|
|
Config.fromstring(cfg_str, '.xml')
|
|
|
|
def test_magic_methods(self):
|
|
cfg_dict = dict(
|
|
item1=[1, 2], item2=dict(a=0), item3=True, item4='test')
|
|
filename = 'py_config/simple_config.py'
|
|
cfg_file = osp.join(self.data_path, 'config', filename)
|
|
cfg = Config.fromfile(cfg_file)
|
|
# len(cfg)
|
|
assert len(cfg) == 4
|
|
# cfg.keys()
|
|
assert set(cfg.keys()) == set(cfg_dict.keys())
|
|
assert set(cfg._cfg_dict.keys()) == set(cfg_dict.keys())
|
|
# cfg.values()
|
|
for value in cfg.values():
|
|
assert value in cfg_dict.values()
|
|
# cfg.items()
|
|
for name, value in cfg.items():
|
|
assert name in cfg_dict
|
|
assert value in cfg_dict.values()
|
|
# cfg.field
|
|
assert cfg.item1 == cfg_dict['item1']
|
|
assert cfg.item2 == cfg_dict['item2']
|
|
assert cfg.item2.a == 0
|
|
assert cfg.item3 == cfg_dict['item3']
|
|
assert cfg.item4 == cfg_dict['item4']
|
|
# accessing keys that do not exist will cause error
|
|
with pytest.raises(AttributeError):
|
|
cfg.not_exist
|
|
# field in cfg, cfg[field], cfg.get()
|
|
for name in ['item1', 'item2', 'item3', 'item4']:
|
|
assert name in cfg
|
|
assert cfg[name] == cfg_dict[name]
|
|
assert cfg.get(name) == cfg_dict[name]
|
|
assert cfg.get('not_exist') is None
|
|
assert cfg.get('not_exist', 0) == 0
|
|
# accessing keys that do not exist will cause error
|
|
with pytest.raises(KeyError):
|
|
cfg['not_exist']
|
|
assert 'item1' in cfg
|
|
assert 'not_exist' not in cfg
|
|
# cfg.update()
|
|
cfg.update(dict(item1=0))
|
|
assert cfg.item1 == 0
|
|
cfg.update(dict(item2=dict(a=1)))
|
|
assert cfg.item2.a == 1
|
|
# test __setattr__
|
|
cfg = Config()
|
|
cfg.item1 = [1, 2]
|
|
cfg.item2 = {'a': 0}
|
|
cfg['item5'] = {'a': {'b': None}}
|
|
assert cfg._cfg_dict['item1'] == [1, 2]
|
|
assert cfg.item1 == [1, 2]
|
|
assert cfg._cfg_dict['item2'] == {'a': 0}
|
|
assert cfg.item2.a == 0
|
|
assert cfg._cfg_dict['item5'] == {'a': {'b': None}}
|
|
assert cfg.item5.a.b is None
|
|
|
|
def test_merge_from_dict(self):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/simple_config.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
input_options = {'item2.a': 1, 'item2.b': 0.1, 'item3': False}
|
|
cfg.merge_from_dict(input_options)
|
|
assert cfg.item2 == dict(a=1, b=0.1)
|
|
assert cfg.item3 is False
|
|
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_merge_from_dict.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
|
|
# Allow list keys
|
|
input_options = {'item.0.a': 1, 'item.1.b': 1}
|
|
cfg.merge_from_dict(input_options, allow_list_keys=True)
|
|
assert cfg.item == [{'a': 1}, {'b': 1, 'c': 0}]
|
|
|
|
# allow_list_keys is False
|
|
input_options = {'item.0.a': 1, 'item.1.b': 1}
|
|
with pytest.raises(TypeError):
|
|
cfg.merge_from_dict(input_options, allow_list_keys=False)
|
|
|
|
# Overflowed index number
|
|
input_options = {'item.2.a': 1}
|
|
with pytest.raises(KeyError):
|
|
cfg.merge_from_dict(input_options, allow_list_keys=True)
|
|
|
|
def test_diff(self):
|
|
cfg1 = Config(dict(a=1, b=2))
|
|
cfg2 = Config(dict(a=1, b=3))
|
|
|
|
diff_str = \
|
|
'--- \n\n+++ \n\n@@ -1,3 +1,3 @@\n\n a = 1\n-b = 2\n+b = 3\n \n\n'
|
|
|
|
assert Config.diff(cfg1, cfg2) == diff_str
|
|
|
|
cfg1_file = osp.join(self.data_path, 'config/py_config/test_diff_1.py')
|
|
cfg1 = Config.fromfile(cfg1_file)
|
|
|
|
cfg2_file = osp.join(self.data_path, 'config/py_config/test_diff_2.py')
|
|
cfg2 = Config.fromfile(cfg2_file)
|
|
|
|
assert Config.diff(cfg1, cfg2) == diff_str
|
|
|
|
def test_auto_argparser(self):
|
|
# Temporarily make sys.argv only has one argument and keep backups
|
|
tmp = sys.argv[1:]
|
|
sys.argv = sys.argv[:2]
|
|
sys.argv[1] = osp.join(
|
|
self.data_path,
|
|
'config/py_config/test_merge_from_multiple_bases.py')
|
|
parser, cfg = Config.auto_argparser()
|
|
args = parser.parse_args()
|
|
assert args.config == sys.argv[1]
|
|
for key in cfg._cfg_dict.keys():
|
|
if not isinstance(cfg[key], ConfigDict):
|
|
assert not getattr(args, key)
|
|
# TODO currently do not support nested keys, bool args will be
|
|
# overwritten by int
|
|
sys.argv.extend(tmp)
|
|
|
|
def test_dict_to_config_dict(self):
|
|
cfg_dict = dict(
|
|
a=1, b=dict(c=dict()), d=[dict(e=dict(f=(dict(g=1), [])))])
|
|
cfg_dict = Config._dict_to_config_dict(cfg_dict)
|
|
assert isinstance(cfg_dict, ConfigDict)
|
|
assert isinstance(cfg_dict.a, int)
|
|
assert isinstance(cfg_dict.b, ConfigDict)
|
|
assert isinstance(cfg_dict.b.c, ConfigDict)
|
|
assert isinstance(cfg_dict.d, list)
|
|
assert isinstance(cfg_dict.d[0], ConfigDict)
|
|
assert isinstance(cfg_dict.d[0].e, ConfigDict)
|
|
assert isinstance(cfg_dict.d[0].e.f, tuple)
|
|
assert isinstance(cfg_dict.d[0].e.f[0], ConfigDict)
|
|
assert isinstance(cfg_dict.d[0].e.f[1], list)
|
|
|
|
def test_dump(self, tmp_path):
|
|
file_path = 'config/py_config/test_merge_from_multiple_bases.py'
|
|
cfg_file = osp.join(self.data_path, file_path)
|
|
cfg = Config.fromfile(cfg_file)
|
|
dump_py = tmp_path / 'simple_config.py'
|
|
|
|
cfg.dump(dump_py)
|
|
assert cfg.dump() == cfg.pretty_text
|
|
assert open(dump_py).read() == cfg.pretty_text
|
|
|
|
# test dump json/yaml.
|
|
file_path = 'config/json_config/simple.config.json'
|
|
cfg_file = osp.join(self.data_path, file_path)
|
|
cfg = Config.fromfile(cfg_file)
|
|
dump_json = tmp_path / 'simple_config.json'
|
|
cfg.dump(dump_json)
|
|
|
|
with open(dump_json) as f:
|
|
assert f.read() == cfg.dump()
|
|
|
|
# test pickle
|
|
file_path = 'config/py_config/test_dump_pickle_support.py'
|
|
cfg_file = osp.join(self.data_path, file_path)
|
|
cfg = Config.fromfile(cfg_file)
|
|
|
|
text_cfg_filename = tmp_path / '_text_config.py'
|
|
cfg.dump(text_cfg_filename)
|
|
text_cfg = Config.fromfile(text_cfg_filename)
|
|
assert text_cfg.str_item_7 == osp.join(osp.expanduser('~'), 'folder')
|
|
assert text_cfg.str_item_8 == 'string with \tescape\\ characters\n'
|
|
assert text_cfg._cfg_dict == cfg._cfg_dict
|
|
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_dump_pickle_support.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
|
|
pkl_cfg_filename = tmp_path / '_pickle.pkl'
|
|
dump(cfg, pkl_cfg_filename)
|
|
pkl_cfg = load(pkl_cfg_filename)
|
|
assert pkl_cfg._cfg_dict == cfg._cfg_dict
|
|
# Test dump config from dict.
|
|
cfg_dict = dict(a=1, b=2)
|
|
cfg = Config(cfg_dict)
|
|
assert cfg.pretty_text == cfg.dump()
|
|
# Test dump python format config.
|
|
dump_file = tmp_path / 'dump_from_dict.py'
|
|
cfg.dump(dump_file)
|
|
with open(dump_file) as f:
|
|
assert f.read() == 'a = 1\nb = 2\n'
|
|
# Test dump json format config.
|
|
dump_file = tmp_path / 'dump_from_dict.json'
|
|
cfg.dump(dump_file)
|
|
with open(dump_file) as f:
|
|
assert f.read() == '{"a": 1, "b": 2}'
|
|
# Test dump yaml format config.
|
|
dump_file = tmp_path / 'dump_from_dict.yaml'
|
|
cfg.dump(dump_file)
|
|
with open(dump_file) as f:
|
|
assert f.read() == 'a: 1\nb: 2\n'
|
|
|
|
def test_pretty_text(self, tmp_path):
|
|
cfg_file = osp.join(
|
|
self.data_path,
|
|
'config/py_config/test_merge_from_multiple_bases.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
text_cfg_filename = tmp_path / '_text_config.py'
|
|
with open(text_cfg_filename, 'w') as f:
|
|
f.write(cfg.pretty_text)
|
|
text_cfg = Config.fromfile(text_cfg_filename)
|
|
assert text_cfg._cfg_dict == cfg._cfg_dict
|
|
|
|
def test_repr(self, tmp_path):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/simple_config.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
tmp_txt = tmp_path / 'tmp.txt'
|
|
with open(tmp_txt, 'w') as f:
|
|
print(cfg, file=f)
|
|
with open(tmp_txt) as f:
|
|
assert f.read().strip() == f'Config (path: {cfg.filename}): ' \
|
|
f'{cfg._cfg_dict.__repr__()}'
|
|
|
|
def test_dict_action(self):
|
|
parser = argparse.ArgumentParser(description='Train a detector')
|
|
parser.add_argument(
|
|
'--options', nargs='+', action=DictAction, help='custom options')
|
|
# Nested brackets
|
|
args = parser.parse_args(
|
|
['--options', 'item2.a=a,b', 'item2.b=[(a,b), [1,2], false]'])
|
|
out_dict = {
|
|
'item2.a': ['a', 'b'],
|
|
'item2.b': [('a', 'b'), [1, 2], False]
|
|
}
|
|
assert args.options == out_dict
|
|
# Single Nested brackets
|
|
args = parser.parse_args(['--options', 'item2.a=[[1]]'])
|
|
out_dict = {'item2.a': [[1]]}
|
|
assert args.options == out_dict
|
|
# Imbalance bracket will cause error
|
|
with pytest.raises(AssertionError):
|
|
parser.parse_args(['--options', 'item2.a=[(a,b), [1,2], false'])
|
|
# Normal values
|
|
args = parser.parse_args([
|
|
'--options', 'item2.a=1', 'item2.b=0.1', 'item2.c=x', 'item3=false'
|
|
])
|
|
out_dict = {
|
|
'item2.a': 1,
|
|
'item2.b': 0.1,
|
|
'item2.c': 'x',
|
|
'item3': False
|
|
}
|
|
assert args.options == out_dict
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/simple_config.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
cfg.merge_from_dict(args.options)
|
|
assert cfg.item2 == dict(a=1, b=0.1, c='x')
|
|
assert cfg.item3 is False
|
|
|
|
# test multiple options
|
|
args = parser.parse_args([
|
|
'--options', 'item1.a=1', 'item2.a=2', '--options', 'item2.a=1',
|
|
'item3=false'
|
|
])
|
|
out_dict = {'item1.a': 1, 'item2.a': 1, 'item3': False}
|
|
assert args.options == out_dict
|
|
|
|
def test_validate_py_syntax(self, tmp_path):
|
|
tmp_cfg = tmp_path / 'tmp_config.py'
|
|
with open(tmp_cfg, 'w') as f:
|
|
f.write('dict(a=1,b=2.c=3)')
|
|
# Incorrect point in dict will cause error
|
|
with pytest.raises(SyntaxError):
|
|
Config._validate_py_syntax(tmp_cfg)
|
|
with open(tmp_cfg, 'w') as f:
|
|
f.write('[dict(a=1, b=2, c=(1, 2)]')
|
|
# Imbalance bracket will cause error
|
|
with pytest.raises(SyntaxError):
|
|
Config._validate_py_syntax(tmp_cfg)
|
|
with open(tmp_cfg, 'w') as f:
|
|
f.write('dict(a=1,b=2\nc=3)')
|
|
# Incorrect feed line in dict will cause error
|
|
with pytest.raises(SyntaxError):
|
|
Config._validate_py_syntax(tmp_cfg)
|
|
|
|
def test_substitute_predefined_vars(self, tmp_path):
|
|
cfg_text = 'a={{fileDirname}}\n' \
|
|
'b={{fileBasename}}\n' \
|
|
'c={{fileBasenameNoExtension}}\n' \
|
|
'd={{fileExtname}}\n'
|
|
|
|
cfg = tmp_path / 'tmp_cfg1.py'
|
|
substituted_cfg = tmp_path / 'tmp_cfg2.py'
|
|
|
|
file_dirname = osp.dirname(cfg)
|
|
file_basename = osp.basename(cfg)
|
|
file_basename_no_extension = osp.splitext(file_basename)[0]
|
|
file_extname = osp.splitext(cfg)[1]
|
|
|
|
expected_text = f'a={file_dirname}\n' \
|
|
f'b={file_basename}\n' \
|
|
f'c={file_basename_no_extension}\n' \
|
|
f'd={file_extname}\n'
|
|
expected_text = expected_text.replace('\\', '/')
|
|
with open(cfg, 'w') as f:
|
|
f.write(cfg_text)
|
|
Config._substitute_predefined_vars(cfg, substituted_cfg)
|
|
|
|
with open(substituted_cfg) as f:
|
|
assert f.read() == expected_text
|
|
|
|
def test_substitute_environment_vars(self, tmp_path):
|
|
cfg = tmp_path / 'tmp_cfg1.py'
|
|
substituted_cfg = tmp_path / 'tmp_cfg2.py'
|
|
|
|
cfg_text = 'a={{$A:}}\n'
|
|
with open(cfg, 'w') as f:
|
|
f.write(cfg_text)
|
|
with pytest.raises(KeyError):
|
|
Config._substitute_env_variables(cfg, substituted_cfg)
|
|
|
|
os.environ['A'] = 'text_A'
|
|
Config._substitute_env_variables(cfg, substituted_cfg)
|
|
with open(substituted_cfg) as f:
|
|
assert f.read() == 'a=text_A\n'
|
|
os.environ.pop('A')
|
|
|
|
cfg_text = 'b={{$B:80}}\n'
|
|
with open(cfg, 'w') as f:
|
|
f.write(cfg_text)
|
|
Config._substitute_env_variables(cfg, substituted_cfg)
|
|
with open(substituted_cfg) as f:
|
|
assert f.read() == 'b=80\n'
|
|
|
|
os.environ['B'] = '100'
|
|
Config._substitute_env_variables(cfg, substituted_cfg)
|
|
with open(substituted_cfg) as f:
|
|
assert f.read() == 'b=100\n'
|
|
os.environ.pop('B')
|
|
|
|
cfg_text = 'c={{"$C:80"}}\n'
|
|
with open(cfg, 'w') as f:
|
|
f.write(cfg_text)
|
|
Config._substitute_env_variables(cfg, substituted_cfg)
|
|
with open(substituted_cfg) as f:
|
|
assert f.read() == 'c=80\n'
|
|
|
|
def test_pre_substitute_base_vars(self, tmp_path):
|
|
cfg_path = osp.join(self.data_path, 'config',
|
|
'py_config/test_pre_substitute_base_vars.py')
|
|
tmp_cfg = tmp_path / 'tmp_cfg.py'
|
|
base_var_dict = Config._pre_substitute_base_vars(cfg_path, tmp_cfg)
|
|
assert 'item6' in base_var_dict.values()
|
|
assert 'item10' in base_var_dict.values()
|
|
assert 'item11' in base_var_dict.values()
|
|
sys.path.append(str(tmp_path))
|
|
cfg_module_dict = import_module(tmp_cfg.name.strip('.py')).__dict__
|
|
assert cfg_module_dict['item22'].startswith('_item11')
|
|
assert cfg_module_dict['item23'].startswith('_item10')
|
|
assert cfg_module_dict['item25']['c'][1].startswith('_item6')
|
|
sys.path.pop()
|
|
|
|
cfg_path = osp.join(self.data_path, 'config',
|
|
'json_config/test_base.json')
|
|
tmp_cfg = tmp_path / 'tmp_cfg.json'
|
|
Config._pre_substitute_base_vars(cfg_path, tmp_cfg)
|
|
cfg_module_dict = load(tmp_cfg)
|
|
assert cfg_module_dict['item9'].startswith('_item2')
|
|
assert cfg_module_dict['item10'].startswith('_item7')
|
|
|
|
cfg_path = osp.join(self.data_path, 'config',
|
|
'yaml_config/test_base.yaml')
|
|
tmp_cfg = tmp_path / 'tmp_cfg.yaml'
|
|
Config._pre_substitute_base_vars(cfg_path, tmp_cfg)
|
|
cfg_module_dict = load(tmp_cfg)
|
|
assert cfg_module_dict['item9'].startswith('_item2')
|
|
assert cfg_module_dict['item10'].startswith('_item7')
|
|
|
|
def test_substitute_base_vars(self):
|
|
cfg = dict(
|
|
item4='_item1.12345',
|
|
item5=dict(item3='1', item2='_item2_.fswf'),
|
|
item0=('_item0_.12ed21wq', 1))
|
|
cfg_base = dict(item1=0, item2=[1, 2, 3], item0=(1, 2, 3))
|
|
base_var_dict = {
|
|
'_item1.12345': 'item1',
|
|
'_item2_.fswf': 'item2',
|
|
'_item0_.12ed21wq': 'item0'
|
|
}
|
|
cfg = Config._substitute_base_vars(cfg, base_var_dict, cfg_base)
|
|
assert cfg['item4'] == cfg_base['item1']
|
|
assert cfg['item5']['item2'] == cfg_base['item2']
|
|
|
|
def test_file2dict(self, tmp_path):
|
|
|
|
# test error format config
|
|
tmp_cfg = tmp_path / 'tmp_cfg.xml'
|
|
tmp_cfg.write_text('exist')
|
|
# invalid config format
|
|
with pytest.raises(IOError):
|
|
Config.fromfile(tmp_cfg)
|
|
# invalid config file path
|
|
with pytest.raises(FileNotFoundError):
|
|
Config.fromfile('no_such_file.py')
|
|
|
|
self._simple_load()
|
|
self._predefined_vars()
|
|
self._environment_vars()
|
|
self._base_variables()
|
|
self._merge_from_base()
|
|
self._code_in_config()
|
|
self._merge_from_multiple_bases()
|
|
self._merge_delete()
|
|
self._merge_intermediate_variable()
|
|
self._merge_recursive_bases()
|
|
self._deprecation()
|
|
|
|
def test_get_cfg_path_local(self):
|
|
filename = 'py_config/simple_config.py'
|
|
filename = osp.join(self.data_path, 'config', filename)
|
|
cfg_name = './base.py'
|
|
cfg_path, scope = Config._get_cfg_path(cfg_name, filename)
|
|
assert scope is None
|
|
osp.isfile(cfg_path)
|
|
|
|
@pytest.mark.skipif(
|
|
not is_installed('mmdet') or not is_installed('mmcls'),
|
|
reason='mmdet and mmcls should be installed')
|
|
def test_get_cfg_path_external(self):
|
|
filename = 'py_config/simple_config.py'
|
|
filename = osp.join(self.data_path, 'config', filename)
|
|
|
|
cfg_name = 'mmdet::faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py'
|
|
cfg_path, scope = Config._get_cfg_path(cfg_name, filename)
|
|
assert scope == 'mmdet'
|
|
osp.isfile(cfg_path)
|
|
|
|
cfg_name = 'mmcls::cspnet/cspresnet50_8xb32_in1k.py'
|
|
cfg_path, scope = Config._get_cfg_path(cfg_name, filename)
|
|
assert scope == 'mmcls'
|
|
osp.isfile(cfg_path)
|
|
|
|
def _simple_load(self):
|
|
# test load simple config
|
|
for file_format in ['py', 'json', 'yaml']:
|
|
for name in ['simple.config', 'simple_config']:
|
|
filename = f'{file_format}_config/{name}.{file_format}'
|
|
|
|
cfg_file = osp.join(self.data_path, 'config', filename)
|
|
cfg_dict, cfg_text, env_variables = Config._file2dict(cfg_file)
|
|
assert isinstance(cfg_text, str)
|
|
assert isinstance(cfg_dict, dict)
|
|
assert isinstance(env_variables, dict)
|
|
|
|
def _get_file_path(self, file_path):
|
|
if platform.system() == 'Windows':
|
|
return file_path.replace('\\', '/')
|
|
else:
|
|
return file_path
|
|
|
|
def _predefined_vars(self):
|
|
# test parse predefined_var in config
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_predefined_var.py')
|
|
path = osp.join(self.data_path, 'config/py_config')
|
|
|
|
path = Path(path).as_posix()
|
|
cfg_dict_dst = dict(
|
|
item1='test_predefined_var.py',
|
|
item2=path,
|
|
item3='abc_test_predefined_var')
|
|
|
|
assert Config._file2dict(cfg_file)[0]['item1'] == cfg_dict_dst['item1']
|
|
assert Config._file2dict(cfg_file)[0]['item2'] == cfg_dict_dst['item2']
|
|
assert Config._file2dict(cfg_file)[0]['item3'] == cfg_dict_dst['item3']
|
|
|
|
# test `use_predefined_variable=False`
|
|
cfg_dict_ori = dict(
|
|
item1='{{fileBasename}}',
|
|
item2='{{ fileDirname}}',
|
|
item3='abc_{{ fileBasenameNoExtension }}')
|
|
|
|
assert Config._file2dict(cfg_file,
|
|
False)[0]['item1'] == cfg_dict_ori['item1']
|
|
assert Config._file2dict(cfg_file,
|
|
False)[0]['item2'] == cfg_dict_ori['item2']
|
|
assert Config._file2dict(cfg_file,
|
|
False)[0]['item3'] == cfg_dict_ori['item3']
|
|
|
|
# test test_predefined_var.yaml
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/yaml_config/test_predefined_var.yaml')
|
|
|
|
# test `use_predefined_variable=False`
|
|
assert Config._file2dict(cfg_file,
|
|
False)[0]['item1'] == '{{ fileDirname }}'
|
|
assert Config._file2dict(cfg_file)[0]['item1'] == self._get_file_path(
|
|
osp.dirname(cfg_file))
|
|
|
|
# test test_predefined_var.json
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/json_config/test_predefined_var.json')
|
|
|
|
assert Config.fromfile(cfg_file, False)['item1'] == '{{ fileDirname }}'
|
|
assert Config.fromfile(cfg_file)['item1'] == self._get_file_path(
|
|
osp.dirname(cfg_file))
|
|
|
|
def _environment_vars(self):
|
|
# test parse predefined_var in config
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_environment_var.py')
|
|
|
|
with pytest.raises(KeyError):
|
|
Config._file2dict(cfg_file)
|
|
|
|
os.environ['ITEM1'] = '60'
|
|
cfg_dict_dst = dict(item1='60', item2='default_value', item3=80)
|
|
assert Config._file2dict(cfg_file)[0]['item1'] == cfg_dict_dst['item1']
|
|
assert Config._file2dict(cfg_file)[0]['item2'] == cfg_dict_dst['item2']
|
|
assert Config._file2dict(cfg_file)[0]['item3'] == cfg_dict_dst['item3']
|
|
|
|
os.environ['ITEM2'] = 'new_value'
|
|
os.environ['ITEM3'] = '50'
|
|
cfg_dict_dst = dict(item1='60', item2='new_value', item3=50)
|
|
assert Config._file2dict(cfg_file)[0]['item1'] == cfg_dict_dst['item1']
|
|
assert Config._file2dict(cfg_file)[0]['item2'] == cfg_dict_dst['item2']
|
|
assert Config._file2dict(cfg_file)[0]['item3'] == cfg_dict_dst['item3']
|
|
|
|
os.environ.pop('ITEM1')
|
|
os.environ.pop('ITEM2')
|
|
os.environ.pop('ITEM3')
|
|
|
|
def _merge_from_base(self):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_merge_from_base_single.py')
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
|
|
assert cfg_dict['item1'] == [2, 3]
|
|
assert cfg_dict['item2']['a'] == 1
|
|
assert cfg_dict['item3'] is False
|
|
assert cfg_dict['item4'] == 'test_base'
|
|
# item3 is a dict in the child config but a boolean in base config
|
|
with pytest.raises(TypeError):
|
|
Config.fromfile(
|
|
osp.join(self.data_path,
|
|
'config/py_config/test_merge_from_base_error.py'))
|
|
|
|
def _merge_from_multiple_bases(self):
|
|
cfg_file = osp.join(
|
|
self.data_path,
|
|
'config/py_config/test_merge_from_multiple_bases.py')
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
|
|
# cfg.fcfg_dictd
|
|
assert cfg_dict['item1'] == [1, 2]
|
|
assert cfg_dict['item2']['a'] == 0
|
|
assert cfg_dict['item3'] is False
|
|
assert cfg_dict['item4'] == 'test'
|
|
assert cfg_dict['item5'] == dict(a=0, b=1)
|
|
assert cfg_dict['item6'] == [dict(a=0), dict(b=1)]
|
|
assert cfg_dict['item7'] == dict(
|
|
a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))
|
|
# Redefine key
|
|
with pytest.raises(KeyError):
|
|
Config.fromfile(
|
|
osp.join(self.data_path,
|
|
'config/py_config/test_merge_from_multiple_error.py'))
|
|
|
|
def _base_variables(self):
|
|
for file in [
|
|
'py_config/test_base_variables.py',
|
|
'json_config/test_base.json', 'yaml_config/test_base.yaml'
|
|
]:
|
|
cfg_file = osp.join(self.data_path, 'config', file)
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
|
|
assert cfg_dict['item1'] == [1, 2]
|
|
assert cfg_dict['item2']['a'] == 0
|
|
assert cfg_dict['item3'] is False
|
|
assert cfg_dict['item4'] == 'test'
|
|
assert cfg_dict['item5'] == dict(a=0, b=1)
|
|
assert cfg_dict['item6'] == [dict(a=0), dict(b=1)]
|
|
assert cfg_dict['item7'] == dict(
|
|
a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))
|
|
assert cfg_dict['item8'] == file.split('/')[-1]
|
|
assert cfg_dict['item9'] == dict(a=0)
|
|
assert cfg_dict['item10'] == [3.1, 4.2, 5.3]
|
|
|
|
# test nested base
|
|
for file in [
|
|
'py_config/test_base_variables_nested.py',
|
|
'json_config/test_base_variables_nested.json',
|
|
'yaml_config/test_base_variables_nested.yaml'
|
|
]:
|
|
cfg_file = osp.join(self.data_path, 'config', file)
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
|
|
assert cfg_dict['base'] == '_base_.item8'
|
|
assert cfg_dict['item1'] == [1, 2]
|
|
assert cfg_dict['item2']['a'] == 0
|
|
assert cfg_dict['item3'] is False
|
|
assert cfg_dict['item4'] == 'test'
|
|
assert cfg_dict['item5'] == dict(a=0, b=1)
|
|
assert cfg_dict['item6'] == [dict(a=0), dict(b=1)]
|
|
assert cfg_dict['item7'] == dict(
|
|
a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))
|
|
assert cfg_dict['item8'] == 'test_base_variables.py'
|
|
assert cfg_dict['item9'] == dict(a=0)
|
|
assert cfg_dict['item10'] == [3.1, 4.2, 5.3]
|
|
assert cfg_dict['item11'] == 'test_base_variables.py'
|
|
assert cfg_dict['item12'] == dict(a=0)
|
|
assert cfg_dict['item13'] == [3.1, 4.2, 5.3]
|
|
assert cfg_dict['item14'] == [1, 2]
|
|
assert cfg_dict['item15'] == dict(
|
|
a=dict(b=dict(a=0)),
|
|
b=[False],
|
|
c=['test'],
|
|
d=[[{
|
|
'e': 0
|
|
}], [{
|
|
'a': 0
|
|
}, {
|
|
'b': 1
|
|
}]],
|
|
e=[1, 2])
|
|
|
|
# test reference assignment for py
|
|
cfg_file = osp.join(
|
|
self.data_path,
|
|
'config/py_config/test_pre_substitute_base_vars.py')
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
|
|
assert cfg_dict['item21'] == 'test_base_variables.py'
|
|
assert cfg_dict['item22'] == 'test_base_variables.py'
|
|
assert cfg_dict['item23'] == [3.1, 4.2, 5.3]
|
|
assert cfg_dict['item24'] == [3.1, 4.2, 5.3]
|
|
assert cfg_dict['item25'] == dict(
|
|
a=dict(b=[3.1, 4.2, 5.3]),
|
|
b=[[3.1, 4.2, 5.3]],
|
|
c=[[{
|
|
'e': 'test_base_variables.py'
|
|
}], [{
|
|
'a': 0
|
|
}, {
|
|
'b': 1
|
|
}]],
|
|
e='test_base_variables.py')
|
|
|
|
cfg_file = osp.join(self.data_path, 'config/py_config/test_py_base.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
assert isinstance(cfg, Config)
|
|
assert cfg.filename == cfg_file
|
|
# cfg.field
|
|
assert cfg.item1 == [1, 2]
|
|
assert cfg.item2.a == 0
|
|
assert cfg.item2.b == [5, 6]
|
|
assert cfg.item3 is False
|
|
assert cfg.item4 == 'test'
|
|
assert cfg.item5 == dict(a=0, b=1)
|
|
assert cfg.item6 == [dict(c=0), dict(b=1)]
|
|
assert cfg.item7 == dict(a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))
|
|
assert cfg.item8 == 'test_py_base.py'
|
|
assert cfg.item9 == 3.1
|
|
assert cfg.item10 == 4.2
|
|
assert cfg.item11 == 5.3
|
|
|
|
# test nested base
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_py_nested_path.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
assert isinstance(cfg, Config)
|
|
assert cfg.filename == cfg_file
|
|
# cfg.field
|
|
assert cfg.item1 == [1, 2]
|
|
assert cfg.item2.a == 0
|
|
assert cfg.item2.b == [5, 6]
|
|
assert cfg.item3 is False
|
|
assert cfg.item4 == 'test'
|
|
assert cfg.item5 == dict(a=0, b=1)
|
|
assert cfg.item6 == [dict(c=0), dict(b=1)]
|
|
assert cfg.item7 == dict(a=[0, 1, 2], b=dict(c=[3.1, 4.2, 5.3]))
|
|
assert cfg.item8 == 'test_py_base.py'
|
|
assert cfg.item9 == 3.1
|
|
assert cfg.item10 == 4.2
|
|
assert cfg.item11 == 5.3
|
|
assert cfg.item12 == 'test_py_base.py'
|
|
assert cfg.item13 == 3.1
|
|
assert cfg.item14 == [1, 2]
|
|
assert cfg.item15 == dict(
|
|
a=dict(b=dict(a=0, b=[5, 6])),
|
|
b=[False],
|
|
c=['test'],
|
|
d=[[{
|
|
'e': 0
|
|
}], [{
|
|
'c': 0
|
|
}, {
|
|
'b': 1
|
|
}]],
|
|
e=[1, 2])
|
|
|
|
# Test use global variable in config function
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_py_function_global_var.py')
|
|
cfg = Config._file2dict(cfg_file)[0]
|
|
assert cfg['item1'] == 1
|
|
assert cfg['item2'] == 2
|
|
|
|
# Test support modifying the value of dict without defining base
|
|
# config.
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_py_modify_key.py')
|
|
cfg = Config._file2dict(cfg_file)[0]
|
|
assert cfg == dict(item1=dict(a=1))
|
|
|
|
# Simulate the case that the temporary directory includes `.`, etc.
|
|
# /tmp/test.axsgr12/. This patch is to check the issue
|
|
# https://github.com/open-mmlab/mmengine/issues/788 has been solved.
|
|
class PatchedTempDirectory(tempfile.TemporaryDirectory):
|
|
|
|
def __init__(self, *args, prefix='test.', **kwargs):
|
|
super().__init__(*args, prefix=prefix, **kwargs)
|
|
|
|
with patch('mmengine.config.config.tempfile.TemporaryDirectory',
|
|
PatchedTempDirectory):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_py_modify_key.py')
|
|
cfg = Config._file2dict(cfg_file)[0]
|
|
assert cfg == dict(item1=dict(a=1))
|
|
|
|
def _merge_recursive_bases(self):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_merge_recursive_bases.py')
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
|
|
assert cfg_dict['item1'] == [2, 3]
|
|
assert cfg_dict['item2']['a'] == 1
|
|
assert cfg_dict['item3'] is False
|
|
assert cfg_dict['item4'] == 'test_recursive_bases'
|
|
|
|
def _merge_delete(self):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_merge_delete.py')
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
# cfg.field
|
|
assert cfg_dict['item1'] == dict(a=0)
|
|
assert cfg_dict['item2'] == dict(a=0, b=0)
|
|
assert cfg_dict['item3'] is True
|
|
assert cfg_dict['item4'] == 'test'
|
|
assert '_delete_' not in cfg_dict['item1']
|
|
|
|
assert type(cfg_dict['item1']) is ConfigDict
|
|
assert type(cfg_dict['item2']) is ConfigDict
|
|
|
|
def _merge_intermediate_variable(self):
|
|
|
|
cfg_file = osp.join(
|
|
self.data_path,
|
|
'config/py_config/test_merge_intermediate_variable_child.py')
|
|
cfg_dict = Config._file2dict(cfg_file)[0]
|
|
# cfg.field
|
|
assert cfg_dict['item1'] == [1, 2]
|
|
assert cfg_dict['item2'] == dict(a=0)
|
|
assert cfg_dict['item3'] is True
|
|
assert cfg_dict['item4'] == 'test'
|
|
assert cfg_dict['item_cfg'] == dict(b=2)
|
|
assert cfg_dict['item5'] == dict(cfg=dict(b=1))
|
|
assert cfg_dict['item6'] == dict(cfg=dict(b=2))
|
|
|
|
def _code_in_config(self):
|
|
cfg_file = osp.join(self.data_path,
|
|
'config/py_config/test_code_in_config.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
# cfg.field
|
|
assert cfg.cfg.item1 == [1, 2]
|
|
assert cfg.cfg.item2 == dict(a=0)
|
|
assert cfg.cfg.item3 is True
|
|
assert cfg.cfg.item4 == 'test'
|
|
assert cfg.item5 == 1
|
|
|
|
def _deprecation(self):
|
|
deprecated_cfg_files = [
|
|
osp.join(self.data_path, 'config', 'py_config/test_deprecated.py'),
|
|
osp.join(self.data_path, 'config',
|
|
'py_config/test_deprecated_base.py')
|
|
]
|
|
|
|
for cfg_file in deprecated_cfg_files:
|
|
with pytest.warns(DeprecationWarning):
|
|
cfg = Config.fromfile(cfg_file)
|
|
assert cfg.item1 == [1, 2]
|
|
|
|
def test_deepcopy(self):
|
|
cfg_file = osp.join(self.data_path, 'config',
|
|
'py_config/test_dump_pickle_support.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
new_cfg = copy.deepcopy(cfg)
|
|
|
|
assert isinstance(new_cfg, Config)
|
|
assert new_cfg._cfg_dict == cfg._cfg_dict
|
|
assert new_cfg._cfg_dict is not cfg._cfg_dict
|
|
assert new_cfg._filename == cfg._filename
|
|
assert new_cfg._text == cfg._text
|
|
|
|
def test_copy(self):
|
|
cfg_file = osp.join(self.data_path, 'config',
|
|
'py_config/test_dump_pickle_support.py')
|
|
cfg = Config.fromfile(cfg_file)
|
|
new_cfg = copy.copy(cfg)
|
|
|
|
assert isinstance(new_cfg, Config)
|
|
assert new_cfg._cfg_dict == cfg._cfg_dict
|
|
assert new_cfg._filename == cfg._filename
|
|
assert new_cfg._text == cfg._text
|
|
|
|
new_cfg = cfg.copy()
|
|
assert isinstance(new_cfg, Config)
|
|
assert new_cfg._cfg_dict == cfg._cfg_dict
|
|
assert new_cfg._filename == cfg._filename
|
|
assert new_cfg._text == cfg._text
|
|
|
|
@pytest.mark.skipif(
|
|
not is_installed('mmdet'), reason='mmdet should be installed')
|
|
def test_get_external_cfg(self):
|
|
ext_cfg_path = osp.join(self.data_path,
|
|
'config/py_config/test_get_external_cfg.py')
|
|
ext_cfg = Config.fromfile(ext_cfg_path)
|
|
assert ext_cfg._cfg_dict.model.neck == dict(
|
|
type='FPN',
|
|
in_channels=[256, 512, 1024, 2048],
|
|
out_channels=256,
|
|
num_outs=5,
|
|
)
|
|
assert '_scope_' in ext_cfg._cfg_dict.model
|
|
|
|
@pytest.mark.skipif(
|
|
not is_installed('mmdet'), reason='mmdet should be installed')
|
|
def test_build_external_package(self):
|
|
# Test load base config.
|
|
ext_cfg_path = osp.join(self.data_path,
|
|
'config/py_config/test_get_external_cfg.py')
|
|
ext_cfg = Config.fromfile(ext_cfg_path)
|
|
|
|
LOCAL_MODELS = Registry('local_model', parent=MODELS, scope='test')
|
|
LOCAL_MODELS.build(ext_cfg.model)
|
|
|
|
# Test load non-base config
|
|
ext_cfg_path = osp.join(self.data_path,
|
|
'config/py_config/test_get_external_cfg2.py')
|
|
ext_cfg = Config.fromfile(ext_cfg_path)
|
|
LOCAL_MODELS.build(ext_cfg.model)
|
|
|
|
# Test override base variable.
|
|
ext_cfg_path = osp.join(self.data_path,
|
|
'config/py_config/test_get_external_cfg3.py')
|
|
ext_cfg = Config.fromfile(ext_cfg_path)
|
|
|
|
@LOCAL_MODELS.register_module()
|
|
class ToyLoss:
|
|
pass
|
|
|
|
@LOCAL_MODELS.register_module()
|
|
class ToyModel:
|
|
pass
|
|
|
|
DefaultScope.get_instance('test1', scope_name='test')
|
|
assert ext_cfg.model._scope_ == 'mmdet'
|
|
model = LOCAL_MODELS.build(ext_cfg.model)
|
|
|
|
# Local base config should not have scope.
|
|
assert '_scope_' not in ext_cfg.toy_model
|
|
toy_model = LOCAL_MODELS.build(ext_cfg.toy_model)
|
|
assert isinstance(toy_model, ToyModel)
|
|
assert model.backbone.style == 'pytorch'
|
|
assert isinstance(model.roi_head.bbox_head.loss_cls, ToyLoss)
|
|
DefaultScope._instance_dict.pop('test1')
|
|
|
|
def test_pickle(self):
|
|
# Text style config
|
|
cfg_path = osp.join(self.data_path, 'config/py_config/test_py_base.py')
|
|
cfg = Config.fromfile(cfg_path)
|
|
pickled = pickle.loads(pickle.dumps(cfg))
|
|
assert pickled.__dict__ == cfg.__dict__
|
|
|
|
cfg_path = osp.join(self.data_path,
|
|
'config/lazy_module_config/toy_model.py')
|
|
cfg = Config.fromfile(cfg_path)
|
|
pickled = pickle.loads(pickle.dumps(cfg))
|
|
assert pickled.__dict__ == cfg.__dict__
|
|
|
|
def test_lazy_import(self, tmp_path):
|
|
lazy_import_cfg_path = osp.join(
|
|
self.data_path, 'config/lazy_module_config/toy_model.py')
|
|
cfg = Config.fromfile(lazy_import_cfg_path)
|
|
cfg_dict = cfg.to_dict()
|
|
assert (cfg_dict['train_dataloader']['dataset']['type'] ==
|
|
'mmengine.testing.runner_test_case.ToyDataset')
|
|
assert (
|
|
cfg_dict['custom_hooks'][0]['type'] == 'mmengine.hooks.EMAHook')
|
|
# Dumped config
|
|
dumped_cfg_path = tmp_path / 'test_dump_lazy.py'
|
|
cfg.dump(dumped_cfg_path)
|
|
dumped_cfg = Config.fromfile(dumped_cfg_path)
|
|
|
|
copied_cfg_path = tmp_path / 'test_dump_copied_lazy.py'
|
|
cfg_copy = cfg.copy()
|
|
cfg_copy.dump(copied_cfg_path)
|
|
copied_cfg = Config.fromfile(copied_cfg_path)
|
|
|
|
def _compare_dict(a, b):
|
|
if isinstance(a, dict):
|
|
assert len(a) == len(b)
|
|
for k, v in a.items():
|
|
_compare_dict(v, b[k])
|
|
elif isinstance(a, list):
|
|
assert len(a) == len(b)
|
|
for item_a, item_b in zip(a, b):
|
|
_compare_dict(item_a, item_b)
|
|
else:
|
|
assert str(a) == str(b)
|
|
|
|
_compare_dict(cfg.to_dict(), dumped_cfg.to_dict())
|
|
_compare_dict(cfg.to_dict(), copied_cfg.to_dict())
|
|
|
|
# TODO reimplement this part of unit test when mmdetection adds the
|
|
# new config.
|
|
# if find_spec('mmdet') is not None:
|
|
# cfg = Config.fromfile(
|
|
# osp.join(self.data_path,
|
|
# 'config/lazy_module_config/load_mmdet_config.py'))
|
|
# assert cfg.model.backbone.depth == 101
|
|
# cfg.work_dir = str(tmp_path)
|
|
# else:
|
|
# pytest.skip('skip testing loading config from mmdet since mmdet '
|
|
# 'is not installed or mmdet version is too low')
|
|
|
|
# catch import error correctly
|
|
error_obj = tmp_path / 'error_obj.py'
|
|
error_obj.write_text("""from mmengine.fileio import error_obj""")
|
|
# match pattern should be double escaped
|
|
match = str(error_obj).encode('unicode_escape').decode()
|
|
with pytest.raises(ImportError, match=match):
|
|
cfg = Config.fromfile(str(error_obj))
|
|
cfg.error_obj
|
|
|
|
error_attr = tmp_path / 'error_attr.py'
|
|
error_attr.write_text("""
|
|
import mmengine
|
|
error_attr = mmengine.error_attr
|
|
""") # noqa: E122
|
|
match = str(error_attr).encode('unicode_escape').decode()
|
|
with pytest.raises(ImportError, match=match):
|
|
cfg = Config.fromfile(str(error_attr))
|
|
cfg.error_attr
|
|
|
|
error_module = tmp_path / 'error_module.py'
|
|
error_module.write_text("""import error_module""")
|
|
match = str(error_module).encode('unicode_escape').decode()
|
|
with pytest.raises(ImportError, match=match):
|
|
cfg = Config.fromfile(str(error_module))
|
|
cfg.error_module
|
|
|
|
# lazy-import and non-lazy-import should not be used mixed.
|
|
# current text config, base lazy-import config
|
|
with pytest.raises(RuntimeError, match='with read_base()'):
|
|
Config.fromfile(
|
|
osp.join(self.data_path,
|
|
'config/lazy_module_config/error_mix_using1.py'))
|
|
|
|
# Force to import in non-lazy-import mode
|
|
Config.fromfile(
|
|
osp.join(self.data_path,
|
|
'config/lazy_module_config/error_mix_using1.py'),
|
|
lazy_import=False)
|
|
|
|
# current lazy-import config, base text config
|
|
with pytest.raises(RuntimeError, match='_base_ ='):
|
|
Config.fromfile(
|
|
osp.join(self.data_path,
|
|
'config/lazy_module_config/error_mix_using2.py'))
|
|
|
|
cfg = Config.fromfile(
|
|
osp.join(self.data_path,
|
|
'config/lazy_module_config/test_mix_builtin.py'))
|
|
assert cfg.path == osp.join('a', 'b')
|
|
assert cfg.name == 'a/b'
|
|
assert cfg.suffix == '.py'
|
|
assert cfg.chained == [1, 2, 3, 4]
|
|
assert cfg.existed
|
|
assert cfg.cfgname == 'test_mix_builtin.py'
|
|
|
|
cfg_dict = cfg.to_dict()
|
|
dumped_cfg_path = tmp_path / 'test_dump_lazy.py'
|
|
cfg.dump(dumped_cfg_path)
|
|
dumped_cfg = Config.fromfile(dumped_cfg_path)
|
|
|
|
assert set(dumped_cfg.keys()) == {
|
|
'path', 'name', 'suffix', 'chained', 'existed', 'cfgname'
|
|
}
|
|
assert dumped_cfg.to_dict() == cfg.to_dict()
|
|
|
|
|
|
class TestConfigDict(TestCase):
|
|
|
|
def test_keep_custom_dict(self):
|
|
|
|
class CustomDict(dict):
|
|
...
|
|
|
|
cfg_dict = ConfigDict(dict(a=CustomDict(b=1)))
|
|
self.assertIsInstance(cfg_dict.a, CustomDict)
|
|
self.assertIsInstance(cfg_dict['a'], CustomDict)
|
|
self.assertIsInstance(cfg_dict.values()[0], CustomDict)
|
|
self.assertIsInstance(cfg_dict.items()[0][1], CustomDict)
|
|
|
|
def test_build_lazy(self):
|
|
# This unit test are divide into two parts:
|
|
# I. ConfigDict will never return a `LazyObject` instance. Only the
|
|
# built will be returned. The `LazyObject` can be accessed after
|
|
# `to_dict` is called.
|
|
|
|
# II. LazyObject will always be kept in the ConfigDict no matter what
|
|
# operation is performed, such as ``update``, ``setitem``, or
|
|
# building another ConfigDict from the current one. The updated
|
|
# ConfigDict also follow the rule of Part I
|
|
|
|
# Part I
|
|
# Keep key-value the same
|
|
raw = dict(a=1, b=dict(c=2, e=[dict(f=(2, ))]))
|
|
cfg_dict = ConfigDict(raw)
|
|
|
|
assert len(cfg_dict) == 2
|
|
assert len(cfg_dict.items()) == 2
|
|
assert len(cfg_dict.keys()) == 2
|
|
assert len(cfg_dict.values()) == 2
|
|
|
|
self.assertDictEqual(cfg_dict, raw)
|
|
|
|
# Check `items` and `values` will only return the build object
|
|
raw = dict(
|
|
a=LazyObject('mmengine'),
|
|
b=dict(
|
|
c=2,
|
|
e=[
|
|
dict(
|
|
f=dict(h=LazyObject('mmengine')),
|
|
g=LazyObject('mmengine'))
|
|
]))
|
|
cfg_dict = ConfigDict(raw)
|
|
# check `items` and values
|
|
self.assertDictEqual(cfg_dict._to_lazy_dict(), raw)
|
|
self._check(cfg_dict)
|
|
|
|
# check getattr
|
|
self.assertIs(cfg_dict.a, mmengine)
|
|
self.assertIs(cfg_dict.b.e[0].f.h, mmengine)
|
|
self.assertIs(cfg_dict.b.e[0].g, mmengine)
|
|
|
|
# check get
|
|
self.assertIs(cfg_dict.get('a'), mmengine)
|
|
self.assertIs(
|
|
cfg_dict.get('b').get('e')[0].get('f').get('h'), mmengine)
|
|
self.assertIs(cfg_dict.get('b').get('e')[0].get('g'), mmengine)
|
|
|
|
# check pop
|
|
a = cfg_dict.pop('a')
|
|
b = cfg_dict.pop('b')
|
|
e = b.pop('e')
|
|
h = e[0].pop('f')['h']
|
|
g = e[0].pop('g')
|
|
self.assertIs(a, mmengine)
|
|
self.assertIs(h, mmengine)
|
|
self.assertIs(g, mmengine)
|
|
self.assertEqual(cfg_dict, {})
|
|
self.assertEqual(b, {'c': 2})
|
|
|
|
# Part II
|
|
# check update with dict and ConfigDict
|
|
for dict_type in (dict, ConfigDict):
|
|
cfg_dict = ConfigDict(x=LazyObject('mmengine'))
|
|
cfg_dict.update(dict_type(raw))
|
|
self._check(cfg_dict)
|
|
|
|
# Create a new ConfigDict
|
|
new_dict = ConfigDict(cfg_dict)
|
|
self._check(new_dict)
|
|
|
|
# Update the ConfigDict by __setitem__ and __setattr__
|
|
new_dict['b']['h'] = LazyObject('mmengine')
|
|
new_dict['b']['k'] = dict(l=dict(n=LazyObject('mmengine')))
|
|
new_dict.b.e[0].i = LazyObject('mmengine')
|
|
new_dict.b.e[0].j = dict(l=dict(n=LazyObject('mmengine')))
|
|
self._check(new_dict)
|
|
|
|
def _check(self, cfg_dict):
|
|
self._recursive_check_lazy(cfg_dict,
|
|
lambda x: not isinstance(x, LazyObject))
|
|
self._recursive_check_lazy(cfg_dict._to_lazy_dict(),
|
|
lambda x: x is not mmengine)
|
|
self._recursive_check_lazy(
|
|
cfg_dict._to_lazy_dict(), lambda x: not isinstance(x, ConfigDict)
|
|
if isinstance(x, dict) else True)
|
|
self._recursive_check_lazy(
|
|
cfg_dict, lambda x: isinstance(x, ConfigDict)
|
|
if isinstance(x, dict) else True)
|
|
|
|
def _recursive_check_lazy(self, cfg, expr):
|
|
if isinstance(cfg, dict):
|
|
{
|
|
key: self._recursive_check_lazy(value, expr)
|
|
for key, value in cfg.items()
|
|
}
|
|
[self._recursive_check_lazy(value, expr) for value in cfg.values()]
|
|
elif isinstance(cfg, (tuple, list)):
|
|
[self._recursive_check_lazy(value, expr) for value in cfg]
|
|
else:
|
|
self.assertTrue(expr(cfg))
|