def test_set_lazy_resolution(logger_mock): """ Test if `@configurable` can resolve it's configuration lazily. Also, test if `get_config` only lazily returns the configuration. This test combines multiple tests because Python does not support unloading a module: https://stackoverflow.com/questions/8781257/remove-an-imported-python-module TODO: This doesn't set `set_lazy_resolution(False)` which forces a resolution for any unresolved modules. In order to add more tests like so, we'd need to create more modules. """ set_lazy_resolution(True) expected = '' add_config({'_tests.other_module.configured': HParams(arg=expected)}) assert {} == get_config() assert logger_mock.warning.call_count == 1 assert len(_get_skip_resolution()) == 1 logger_mock.reset_mock() # NOTE: Test multiple similar config calls add_config({'_tests.other_module.configured': HParams(arg=expected)}) assert len(_get_skip_resolution()) == 1 from _tests.other_module import configured assert configured() == expected assert len(get_config()) == 1 assert logger_mock.warning.call_count == 0 assert len(_get_skip_resolution()) == 0
def test_configurable__benchmark(): """ Test if `@configurable` is within the ballpark of a native decorator in performance. """ def baseline(function): @wraps(function) def decorator(*args, **kwargs): return function(*args, **kwargs) return decorator lambda_ = lambda arg: arg other_lambda_ = lambda arg: arg native = baseline(lambda_) configured = configurable(other_lambda_) add_config({configured: HParams(arg='')}) samples = 10000 # NOTE: The timer decisions are inspired by the native `timeit` module. gc.disable() iterator = itertools.repeat(None, samples) start = time.perf_counter() for _ in iterator: native('') native_elapsed = time.perf_counter() - start iterator = itertools.repeat(None, samples) start = time.perf_counter() for _ in iterator: configured() configured_elapsed = time.perf_counter() - start gc.enable() assert (configured_elapsed / samples) - (native_elapsed / samples) < 3e-05
def test_configurable__unwrap(): """ Test if `@configurable` works with the original `configured` path. """ @configurable() def configured(arg): return arg expected = '' add_config({configured.__wrapped__: HParams(arg=expected)}) assert configured() == expected
def test_configurable__double_invoke(): """ Test if `@configurable` can be called with no arguments. """ @configurable() def configured(arg): return arg expected = '' add_config({configured: HParams(arg=expected)}) assert configured() == expected
def test__configurable__regression(): """ Test if `@configurable` fails with a none-overridden `__init__` function for a global class. TODO: Support this use case. Curiously, this works for none-global classes though. """ _test__resolve_configuration__super_class.__init__ = configurable( _test__resolve_configuration__super_class.__init__) with pytest.raises(TypeError): add_config({_test__resolve_configuration__super_class.__init__: HParams()})
def test__configurable__regression(warnings_mock): """ Test if `@configurable` fails with a none-overridden `__init__` function for a global class. TODO: Support this use case. Curiously, this works for none-global classes though. """ _test__resolve_configuration__super_class.__init__ = configurable( _test__resolve_configuration__super_class.__init__) add_config({_test__resolve_configuration__super_class.__init__: HParams()}) assert warnings_mock.warn.call_count == 1
def test_configurable__get_partial(): """ Test if `@configurable#get_configured_partial` is able to export a partial with expected configuration. """ @configurable def configured(arg): return arg expected = '' add_config({configured: HParams(arg=expected)}) partial = configured.get_configured_partial() assert expected == partial()
def test_configurable__empty_configuration_warnings(logger_mock): """ Test if `@configurable` throws warnings for an empty configuration. """ @configurable def configured(): pass add_config({configured: HParams()}) logger_mock.reset_mock() configured() assert logger_mock.warning.call_count == 0
def test_config_operators(): """ Test the `log_config`, `clear_config`, `add_config` and `get_config` together. It's difficult to test them alone. """ @configurable def configured(arg): pass assert len(get_config()) == 0 add_config({configured: HParams()}) log_config() # Smoke test assert len(get_config()) == 1 clear_config() assert len(get_config()) == 0
def test_configurable__exposed_function(logger_mock, warnings_mock): """ Test if `@configurable` throws warnings if a configured function is run without its decorator. """ @configurable def configured(): pass add_config({configured: HParams()}) logger_mock.reset_mock() warnings_mock.reset_mock() configured.__wrapped__() assert logger_mock.warning.call_count == 0 assert warnings_mock.warn.call_count == 1
def test_configurable__override_with_kwarg(logger_mock, warnings_mock): """ Test if `@configurable` throws an error on a override of an `HParam` argument that's not configured. """ @configurable def configured(arg=HParam()): return arg add_config({configured: HParams()}) logger_mock.reset_mock() warnings_mock.reset_mock() configured(arg='a') assert logger_mock.warning.call_count == 0 assert warnings_mock.warn.call_count == 1
def test_configurable__override(logger_mock): """ Test if `@configurable` throws an error on a override of an `HParam` argument that's not configured. """ @configurable def configured(arg=HParam()): return arg add_config({configured: HParams()}) logger_mock.reset_mock() configured('a') assert logger_mock.warning.call_count == 1 # It shouldn't throw a second warning configured('a') assert logger_mock.warning.call_count == 1
def test_merge_configs(): """ Test the merging of two configurations via `add_config`. """ @configurable def configured(arg, arg_two): pass @configurable def other_configured(arg): pass clear_config() add_config({configured: HParams(arg='arg', arg_two='arg_two')}) add_config({other_configured: HParams()}) assert len(get_config()) == 2 assert get_config()[_get_function_signature(configured.__wrapped__)]['arg'] == 'arg' add_config({configured: HParams(arg='gra')}) assert len(get_config()) == 2 assert get_config()[_get_function_signature(configured.__wrapped__)]['arg'] == 'gra' assert get_config()[_get_function_signature(configured.__wrapped__)]['arg_two'] == 'arg_two' clear_config()
def test_add_config__empty(): """ Test if `add_config` works with an empty config. """ clear_config() add_config({}) assert {} == get_config()
def test_configurable__regression_overriding(logger_mock, warnings_mock): """ Test `@configurable` that there are no warnings with a valid config. """ add_config({_test_configurable__valid_config: HParams(hparam='')}) _test_configurable__valid_config('arg', kwarg='') assert logger_mock.warning.call_count == 0 assert warnings_mock.warn.call_count == 0