Coverage for mlos_bench/mlos_bench/tests/services/config_persistence_test.py: 100%

60 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-21 01:50 +0000

1# 

2# Copyright (c) Microsoft Corporation. 

3# Licensed under the MIT License. 

4# 

5"""Unit tests for configuration persistence service.""" 

6 

7import os 

8from importlib.resources import files 

9 

10import pytest 

11 

12from mlos_bench.config.schemas import ConfigSchema 

13from mlos_bench.services.config_persistence import ConfigPersistenceService 

14from mlos_bench.util import path_join 

15 

16# pylint: disable=redefined-outer-name 

17 

18 

19@pytest.fixture 

20def config_persistence_service() -> ConfigPersistenceService: 

21 """Test fixture for ConfigPersistenceService.""" 

22 return ConfigPersistenceService( 

23 { 

24 "config_path": [ 

25 "./non-existent-dir/test/foo/bar", # Non-existent config path 

26 ".", # cwd 

27 str( 

28 files("mlos_bench.tests.config").joinpath("") 

29 ), # Test configs (relative to mlos_bench/tests) 

30 # Shouldn't be necessary since we automatically add this. 

31 # str(files("mlos_bench.config").joinpath("")), # Stock configs 

32 ] 

33 } 

34 ) 

35 

36 

37def test_cwd_in_explicit_search_path(config_persistence_service: ConfigPersistenceService) -> None: 

38 """Check that CWD is in the search path in the correct place.""" 

39 # pylint: disable=protected-access 

40 assert config_persistence_service._config_path is not None 

41 cwd = path_join(os.getcwd(), abs_path=True) 

42 assert config_persistence_service._config_path.index(cwd) == 1 

43 with pytest.raises(ValueError): 

44 config_persistence_service._config_path.index(cwd, 2) 

45 

46 

47def test_cwd_in_default_search_path() -> None: 

48 """Checks that the CWD is prepended to the search path if not explicitly present.""" 

49 # pylint: disable=protected-access 

50 config_persistence_service = ConfigPersistenceService() 

51 assert config_persistence_service._config_path is not None 

52 cwd = path_join(os.getcwd(), abs_path=True) 

53 assert config_persistence_service._config_path.index(cwd) == 0 

54 with pytest.raises(ValueError): 

55 config_persistence_service._config_path.index(cwd, 1) 

56 

57 

58def test_resolve_stock_path(config_persistence_service: ConfigPersistenceService) -> None: 

59 """Check if we can actually find a file somewhere in `config_path`.""" 

60 # pylint: disable=protected-access 

61 assert config_persistence_service._config_path is not None 

62 assert ConfigPersistenceService.BUILTIN_CONFIG_PATH in config_persistence_service._config_path 

63 file_path = "storage/in-memory.jsonc" 

64 path = config_persistence_service.resolve_path(file_path) 

65 assert path.endswith(file_path) 

66 assert os.path.exists(path) 

67 assert os.path.samefile( 

68 ConfigPersistenceService.BUILTIN_CONFIG_PATH, 

69 os.path.commonpath([ConfigPersistenceService.BUILTIN_CONFIG_PATH, path]), 

70 ) 

71 

72 

73def test_resolve_path(config_persistence_service: ConfigPersistenceService) -> None: 

74 """Check if we can actually find a file somewhere in `config_path`.""" 

75 file_path = "tunable-values/tunable-values-example.jsonc" 

76 path = config_persistence_service.resolve_path(file_path) 

77 assert path.endswith(file_path) 

78 assert os.path.exists(path) 

79 

80 

81def test_resolve_path_fail(config_persistence_service: ConfigPersistenceService) -> None: 

82 """Check if non-existent file resolves without using `config_path`.""" 

83 file_path = "foo/non-existent-config.json" 

84 path = config_persistence_service.resolve_path(file_path) 

85 assert not os.path.exists(path) 

86 assert path == file_path 

87 

88 

89def test_load_config(config_persistence_service: ConfigPersistenceService) -> None: 

90 """Check if we can successfully load a config file located relative to 

91 `config_path`. 

92 """ 

93 tunables_data = config_persistence_service.load_config( 

94 "tunable-values/tunable-values-example.jsonc", 

95 ConfigSchema.TUNABLE_VALUES, 

96 ) 

97 assert tunables_data is not None 

98 assert isinstance(tunables_data, dict) 

99 assert len(tunables_data) >= 1 

100 

101 

102def test_load_bad_config_path(config_persistence_service: ConfigPersistenceService) -> None: 

103 """Check if we can successfully load a config file located relative to 

104 `config_path`. 

105 """ 

106 with pytest.raises(FileNotFoundError) as exc_info: 

107 _ = config_persistence_service.load_config( 

108 "DNE/tunable-values/tunable-values-example.jsonc-DNE-}]", 

109 ConfigSchema.TUNABLE_VALUES, 

110 ) 

111 assert "No such file or directory" in str(exc_info.value) 

112 

113 

114def test_load_config_string(config_persistence_service: ConfigPersistenceService) -> None: 

115 """Check if we can load a valid json string as well.""" 

116 json_str = """ 

117 { 

118 "tunable_param_1": "value_1", 

119 "tunable_param_2": "value_2", 

120 } 

121 """ 

122 tunables_data = config_persistence_service.load_config(json_str, ConfigSchema.TUNABLE_VALUES) 

123 assert tunables_data is not None 

124 assert isinstance(tunables_data, dict) 

125 assert len(tunables_data) >= 1 

126 

127 

128def test_load_bad_config_string(config_persistence_service: ConfigPersistenceService) -> None: 

129 """Check how we handle loading a bad config string.""" 

130 json_str = """ 

131 { 

132 "tunable_param_1": "value_1", 

133 "tunable_param_2": "value_2", 

134 //} // Missing closing brace 

135 """ 

136 with pytest.raises(ValueError) as exc_info: 

137 _ = config_persistence_service.load_config(json_str, ConfigSchema.TUNABLE_VALUES) 

138 assert "Failed to parse config from JSON string" in str(exc_info.value)