mmengine/tests/test_config/test_lazy.py

183 lines
6.7 KiB
Python
Raw Normal View History

# Copyright (c) OpenMMLab. All rights reserved.
import ast
import copy
import os
import os.path as osp
from importlib import import_module
from importlib.util import find_spec
from unittest import TestCase
import numpy
import numpy.compat
import numpy.linalg as linalg
import mmengine
from mmengine.config import Config
from mmengine.config.lazy import LazyAttr, LazyObject
from mmengine.config.utils import ImportTransformer, _gather_abs_import_lazyobj
from mmengine.fileio import LocalBackend, PetrelBackend
class TestImportTransformer(TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.data_dir = osp.join( # type: ignore
osp.dirname(__file__), '..', 'data', 'config',
'lazy_module_config')
super().setUpClass()
def test_lazy_module(self):
cfg_path = osp.join(self.data_dir, 'test_ast_transform.py')
with open(cfg_path) as f:
codestr = f.read()
codeobj = ast.parse(codestr)
global_dict = {
'LazyObject': LazyObject,
}
base_dict = {
'._base_.default_runtime': {
'default_scope': 'test_config'
},
'._base_.scheduler': {
'val_cfg': {}
},
}
codeobj = ImportTransformer(global_dict, base_dict).visit(codeobj)
codeobj, _ = _gather_abs_import_lazyobj(codeobj)
codeobj = ast.fix_missing_locations(codeobj)
exec(compile(codeobj, cfg_path, mode='exec'), global_dict, global_dict)
# 1. absolute import
# 1.1 import module as LazyObject
lazy_numpy = global_dict['numpy']
self.assertIsInstance(lazy_numpy, LazyObject)
# 1.2 getattr as LazyAttr
self.assertIsInstance(lazy_numpy.linalg, LazyAttr)
self.assertIsInstance(lazy_numpy.compat, LazyAttr)
# 1.3 Build module from LazyObject. amp and functional can be accessed
imported_numpy = lazy_numpy.build()
self.assertIs(imported_numpy.linalg, linalg)
self.assertIs(imported_numpy.compat, numpy.compat)
# 1.4 Build module from LazyAttr
imported_linalg = lazy_numpy.linalg.build()
imported_compat = lazy_numpy.compat.build()
self.assertIs(imported_compat, numpy.compat)
self.assertIs(imported_linalg, linalg)
# 1.5 import ... as, and build module from LazyObject
lazy_linalg = global_dict['linalg']
self.assertIsInstance(lazy_linalg, LazyObject)
self.assertIs(lazy_linalg.build(), linalg)
self.assertIsInstance(lazy_linalg.norm, LazyAttr)
self.assertIs(lazy_linalg.norm.build(), linalg.norm)
# 1.6 import built in module
imported_os = global_dict['os']
self.assertIs(imported_os, os)
# 2. Relative import
# 2.1 from ... import ...
lazy_local_backend = global_dict['local']
self.assertIsInstance(lazy_local_backend, LazyObject)
self.assertIs(lazy_local_backend.build(), LocalBackend)
# 2.2 from ... import ... as ...
lazy_petrel_backend = global_dict['PetrelBackend']
self.assertIsInstance(lazy_petrel_backend, LazyObject)
self.assertIs(lazy_petrel_backend.build(), PetrelBackend)
# 2.3 from ... import builtin module or obj from `mmengine.Config`
self.assertIs(global_dict['find_module'], find_spec)
self.assertIs(global_dict['Config'], Config)
# 3 test import base config
# 3.1 simple from ... import and from ... import ... as
self.assertEqual(global_dict['scope'], 'test_config')
self.assertDictEqual(global_dict['val_cfg'], {})
# 4. Error catching
cfg_path = osp.join(self.data_dir,
'test_ast_transform_error_catching1.py')
with open(cfg_path) as f:
codestr = f.read()
codeobj = ast.parse(codestr)
global_dict = {'LazyObject': LazyObject}
with self.assertRaisesRegex(
RuntimeError,
r'Illegal syntax in config! `from xxx import \*`'):
codeobj = ImportTransformer(global_dict).visit(codeobj)
class TestLazyObject(TestCase):
def test_init(self):
LazyObject('mmengine')
LazyObject('mmengine.fileio')
LazyObject('mmengine.fileio', 'LocalBackend')
# module must be str
with self.assertRaises(TypeError):
LazyObject(1)
# imported must be a sequence of string or None
with self.assertRaises(TypeError):
LazyObject('mmengine', ['error_type'])
def test_build(self):
lazy_mmengine = LazyObject('mmengine')
self.assertIs(lazy_mmengine.build(), mmengine)
lazy_mmengine_fileio = LazyObject('mmengine.fileio')
self.assertIs(lazy_mmengine_fileio.build(),
import_module('mmengine.fileio'))
lazy_local_backend = LazyObject('mmengine.fileio', 'LocalBackend')
self.assertIs(lazy_local_backend.build(), LocalBackend)
# TODO: The commented test is required, we need to test the built
# LazyObject can access the `mmengine.dataset`. We need to clean the
# environment to make sure the `dataset` is not imported before, and
# it is triggered by lazy_mmengine.build(). However, if we simply
# pop the `mmengine.dataset` will lead to other tests failed, of which
# reason is still unknown. We need to figure out the reason and fix it
# in the latter
# sys.modules.pop('mmengine.config')
# sys.modules.pop('mmengine.fileio')
# sys.modules.pop('mmengine')
# lazy_mmengine = LazyObject(['mmengine', 'mmengine.dataset'])
# self.assertIs(lazy_mmengine.build().dataset,
# import_module('mmengine.config'))
copied = copy.deepcopy(lazy_local_backend)
self.assertDictEqual(copied.__dict__, lazy_local_backend.__dict__)
with self.assertRaises(RuntimeError):
lazy_mmengine()
with self.assertRaises(ImportError):
LazyObject('unknown').build()
class TestLazyAttr(TestCase):
# Since LazyAttr should only be built from LazyObect, we only test
# the build method here.
def test_build(self):
lazy_mmengine = LazyObject('mmengine')
local_backend = lazy_mmengine.fileio.LocalBackend
self.assertIs(local_backend.build(), LocalBackend)
copied = copy.deepcopy(local_backend)
self.assertDictEqual(copied.__dict__, local_backend.__dict__)
with self.assertRaises(RuntimeError):
local_backend()
with self.assertRaisesRegex(
ImportError,
'Failed to import mmengine.fileio.LocalBackend.unknown'):
local_backend.unknown.build()