Coverage for mlos_bench/mlos_bench/tests/config/cli/test_load_cli_config_examples.py: 97%
60 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 00:44 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 00:44 +0000
1#
2# Copyright (c) Microsoft Corporation.
3# Licensed under the MIT License.
4#
5"""Tests for loading storage config examples."""
7import logging
8import sys
9from typing import List
11import pytest
13from mlos_bench.config.schemas import ConfigSchema
14from mlos_bench.environments import Environment
15from mlos_bench.launcher import Launcher
16from mlos_bench.optimizers import Optimizer
17from mlos_bench.schedulers import Scheduler
18from mlos_bench.services.config_persistence import ConfigPersistenceService
19from mlos_bench.storage import Storage
20from mlos_bench.tests import check_class_name
21from mlos_bench.tests.config import BUILTIN_TEST_CONFIG_PATH, locate_config_examples
22from mlos_bench.util import path_join
24if sys.version_info < (3, 10):
25 from importlib_resources import files
26else:
27 from importlib.resources import files
30_LOG = logging.getLogger(__name__)
31_LOG.setLevel(logging.DEBUG)
34# Get the set of configs to test.
35CONFIG_TYPE = "cli"
38def filter_configs(configs_to_filter: List[str]) -> List[str]:
39 """If necessary, filter out json files that aren't for the module we're testing."""
40 return configs_to_filter
43configs = [
44 *locate_config_examples(
45 ConfigPersistenceService.BUILTIN_CONFIG_PATH,
46 CONFIG_TYPE,
47 filter_configs,
48 ),
49 *locate_config_examples(
50 BUILTIN_TEST_CONFIG_PATH,
51 CONFIG_TYPE,
52 filter_configs,
53 ),
54]
55assert configs
58@pytest.mark.skip(reason="Use full Launcher test (below) instead now.")
59@pytest.mark.parametrize("config_path", configs)
60def test_load_cli_config_examples(
61 config_loader_service: ConfigPersistenceService,
62 config_path: str,
63) -> None: # pragma: no cover
64 """Tests loading a config example."""
65 # pylint: disable=too-complex
66 config = config_loader_service.load_config(config_path, ConfigSchema.CLI)
67 assert isinstance(config, dict)
69 if config_paths := config.get("config_path"):
70 assert isinstance(config_paths, list)
71 config_paths.reverse()
72 for path in config_paths:
73 config_loader_service._config_path.insert(0, path) # pylint: disable=protected-access
75 # Foreach arg that references another file, see if we can at least load that too.
76 args_to_skip = {
77 "config_path", # handled above
78 "log_file",
79 "log_level",
80 "experiment_id",
81 "trial_id",
82 "teardown",
83 }
84 for arg in config:
85 if arg in args_to_skip:
86 continue
88 if arg == "globals":
89 for path in config[arg]:
90 sub_config = config_loader_service.load_config(path, ConfigSchema.GLOBALS)
91 assert isinstance(sub_config, dict)
92 elif arg == "environment":
93 sub_config = config_loader_service.load_config(config[arg], ConfigSchema.ENVIRONMENT)
94 assert isinstance(sub_config, dict)
95 elif arg == "optimizer":
96 sub_config = config_loader_service.load_config(config[arg], ConfigSchema.OPTIMIZER)
97 assert isinstance(sub_config, dict)
98 elif arg == "storage":
99 sub_config = config_loader_service.load_config(config[arg], ConfigSchema.STORAGE)
100 assert isinstance(sub_config, dict)
101 elif arg == "tunable_values":
102 for path in config[arg]:
103 sub_config = config_loader_service.load_config(path, ConfigSchema.TUNABLE_VALUES)
104 assert isinstance(sub_config, dict)
105 else:
106 raise NotImplementedError(f"Unhandled arg {arg} in config {config_path}")
109@pytest.mark.parametrize("config_path", configs)
110def test_load_cli_config_examples_via_launcher(
111 config_loader_service: ConfigPersistenceService,
112 config_path: str,
113) -> None:
114 """Tests loading a config example via the Launcher."""
115 config = config_loader_service.load_config(config_path, ConfigSchema.CLI)
116 assert isinstance(config, dict)
118 # Try to load the CLI config by instantiating a launcher.
119 # To do this we need to make sure to give it a few extra paths and globals
120 # to look for for our examples.
121 cli_args = (
122 # pylint: disable=inconsistent-quotes
123 f"--config {config_path}"
124 f" --config-path {files('mlos_bench.config')} "
125 f" --config-path {files('mlos_bench.tests.config')}"
126 f" --config-path {path_join(str(files('mlos_bench.tests.config')), 'globals')}"
127 f" --globals {files('mlos_bench.tests.config')}/experiments/experiment_test_config.jsonc"
128 )
129 launcher = Launcher(description=__name__, long_text=config_path, argv=cli_args.split())
130 assert launcher
132 # Check that some parts of that config are loaded.
134 assert ConfigPersistenceService.BUILTIN_CONFIG_PATH in launcher.config_loader.config_paths
135 if config_paths := config.get("config_path"):
136 assert isinstance(config_paths, list)
137 for path in config_paths:
138 # Note: Checks that the order is maintained are handled in launcher_parse_args.py
139 assert any(
140 config_path.endswith(path) for config_path in launcher.config_loader.config_paths
141 ), f"Expected {path} to be in {launcher.config_loader.config_paths}"
143 if "experiment_id" in config:
144 assert launcher.global_config["experiment_id"] == config["experiment_id"]
145 if "trial_id" in config:
146 assert launcher.global_config["trial_id"] == config["trial_id"]
148 expected_log_level = logging.getLevelName(config.get("log_level", "INFO"))
149 if isinstance(expected_log_level, int):
150 expected_log_level = logging.getLevelName(expected_log_level)
151 current_log_level = logging.getLevelName(logging.root.getEffectiveLevel())
152 assert current_log_level == expected_log_level
154 # TODO: Check that the log_file handler is set correctly.
156 expected_teardown = config.get("teardown", True)
157 assert launcher.teardown == expected_teardown
159 # Note: Testing of "globals" processing handled in launcher_parse_args_test.py
161 # Instead of just checking that the config is loaded, check that the
162 # Launcher loaded the expected types as well.
164 assert isinstance(launcher.environment, Environment)
165 env_config = launcher.config_loader.load_config(
166 config["environment"],
167 ConfigSchema.ENVIRONMENT,
168 )
169 assert check_class_name(launcher.environment, env_config["class"])
171 assert isinstance(launcher.optimizer, Optimizer)
172 if "optimizer" in config:
173 opt_config = launcher.config_loader.load_config(
174 config["optimizer"],
175 ConfigSchema.OPTIMIZER,
176 )
177 assert check_class_name(launcher.optimizer, opt_config["class"])
179 assert isinstance(launcher.storage, Storage)
180 if "storage" in config:
181 storage_config = launcher.config_loader.load_config(
182 config["storage"],
183 ConfigSchema.STORAGE,
184 )
185 assert check_class_name(launcher.storage, storage_config["class"])
187 assert isinstance(launcher.scheduler, Scheduler)
188 if "scheduler" in config:
189 scheduler_config = launcher.config_loader.load_config(
190 config["scheduler"],
191 ConfigSchema.SCHEDULER,
192 )
193 assert check_class_name(launcher.scheduler, scheduler_config["class"])
195 # TODO: Check that the launcher assigns the tunables values as expected.