-
-
Notifications
You must be signed in to change notification settings - Fork 601
Expand file tree
/
Copy pathtest_config_loader.py
More file actions
256 lines (219 loc) · 9.2 KB
/
Copy pathtest_config_loader.py
File metadata and controls
256 lines (219 loc) · 9.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import os
from pathlib import Path
from config.config_manager import (
DEFAULT_EXCLUDED_DIRS,
DEFAULT_EXCLUDED_EXTENSIONS,
DEFAULT_EXCLUDED_FILES,
ConfigManager,
)
def test_config_loader():
loader = ConfigManager(
os.path.join(Path(__file__).resolve().parent, "fixtures", "config/config.yml")
)
assert loader.config.EXCLUDED_PLATFORMS == sorted({*DEFAULT_EXCLUDED_DIRS, "romm"})
assert loader.config.EXCLUDED_SINGLE_EXT == sorted(
{
*(e.lower() for e in DEFAULT_EXCLUDED_EXTENSIONS),
"xml",
}
)
assert loader.config.EXCLUDED_SINGLE_FILES == sorted(
{*DEFAULT_EXCLUDED_FILES, "info.txt"}
)
assert loader.config.EXCLUDED_MULTI_FILES == sorted(
{
*DEFAULT_EXCLUDED_DIRS,
"my_multi_file_game",
"DLC",
}
)
assert loader.config.EXCLUDED_MULTI_PARTS_EXT == sorted(
{
*(e.lower() for e in DEFAULT_EXCLUDED_EXTENSIONS),
"txt",
}
)
assert loader.config.EXCLUDED_MULTI_PARTS_FILES == sorted(
{
*DEFAULT_EXCLUDED_FILES,
"data.xml",
}
)
assert loader.config.PLATFORMS_BINDING == {"gc": "ngc"}
assert loader.config.PLATFORMS_VERSIONS == {"naomi": "arcade"}
assert loader.config.ROMS_FOLDER_NAME == "ROMS"
assert loader.config.FIRMWARE_FOLDER_NAME == "BIOS"
assert loader.config.SKIP_HASH_CALCULATION
assert loader.config.EJS_DEBUG
assert loader.config.EJS_DISABLE_AUTO_UNLOAD
assert loader.config.EJS_DISABLE_BATCH_BOOTUP
assert loader.config.EJS_CACHE_LIMIT == 1000
assert loader.config.EJS_NETPLAY_ENABLED
assert loader.config.EJS_NETPLAY_ICE_SERVERS == [
{"urls": "stun:stun.relay.metered.ca:80"},
{
"urls": "turn:global.relay.metered.ca:80",
"username": "user",
"credential": "password",
},
]
assert loader.config.EJS_SETTINGS == {
"parallel_n64": {"vsync": "disabled"},
"snes9x": {"snes9x_region": "ntsc"},
}
assert loader.config.EJS_CONTROLS == {
"snes9x": {
"_0": {0: {"value": "x", "value2": "BUTTON_2"}},
"_1": {},
"_2": {},
"_3": {},
},
}
assert loader.config.SCAN_METADATA_PRIORITY == ["ss", "launchbox"]
assert loader.config.SCAN_ARTWORK_PRIORITY == ["igdb", "ss"]
assert loader.config.SCAN_ARTWORK_PRIORITY_OVERRIDES == {
"url_cover": ["ss", "tgdb"],
"url_screenshots": ["igdb"],
}
assert loader.config.SCAN_REGION_PRIORITY == ["jp", "eu", "wor"]
assert loader.config.SCAN_LANGUAGE_PRIORITY == ["jp", "es"]
assert loader.config.GAMELIST_MEDIA_THUMBNAIL == "box3d"
assert loader.config.GAMELIST_MEDIA_IMAGE == "title_screen"
def test_empty_config_loader():
loader = ConfigManager(
os.path.join(
Path(__file__).resolve().parent, "fixtures", "config/empty_config.yml"
)
)
assert loader.config.EXCLUDED_PLATFORMS == sorted(DEFAULT_EXCLUDED_DIRS)
assert loader.config.EXCLUDED_SINGLE_EXT == sorted(
{e.lower() for e in DEFAULT_EXCLUDED_EXTENSIONS}
)
assert loader.config.EXCLUDED_SINGLE_FILES == sorted(DEFAULT_EXCLUDED_FILES)
assert loader.config.EXCLUDED_MULTI_FILES == sorted(DEFAULT_EXCLUDED_DIRS)
assert loader.config.EXCLUDED_MULTI_PARTS_EXT == sorted(
{e.lower() for e in DEFAULT_EXCLUDED_EXTENSIONS}
)
assert loader.config.EXCLUDED_MULTI_PARTS_FILES == sorted(DEFAULT_EXCLUDED_FILES)
assert loader.config.PLATFORMS_BINDING == {}
assert loader.config.PLATFORMS_VERSIONS == {}
assert loader.config.ROMS_FOLDER_NAME == "roms"
assert loader.config.FIRMWARE_FOLDER_NAME == "bios"
assert not loader.config.SKIP_HASH_CALCULATION
assert not loader.config.EJS_DEBUG
assert loader.config.EJS_CACHE_LIMIT is None
assert not loader.config.EJS_DISABLE_AUTO_UNLOAD
assert not loader.config.EJS_DISABLE_BATCH_BOOTUP
assert not loader.config.EJS_NETPLAY_ENABLED
assert loader.config.EJS_NETPLAY_ICE_SERVERS == []
assert loader.config.EJS_SETTINGS == {}
assert loader.config.EJS_CONTROLS == {}
assert loader.config.SCAN_ARTWORK_PRIORITY_OVERRIDES == {}
assert loader.config.GAMELIST_MEDIA_THUMBNAIL == "box2d"
assert loader.config.GAMELIST_MEDIA_IMAGE == "screenshot"
def test_missing_config_file_is_created(tmp_path):
config_file = tmp_path / "config" / "config.yml"
loader = ConfigManager(str(config_file))
assert config_file.parent.exists()
assert config_file.exists()
assert config_file.read_text() == ""
assert loader.config.CONFIG_FILE_MOUNTED
assert loader.config.CONFIG_FILE_WRITABLE
def test_forward_compat_unknown_values_are_tolerated():
"""A newer release may ship sample configs that reference media types
this version doesn't yet recognize. The loader should drop unknowns and
fall back to defaults rather than exiting."""
loader = ConfigManager(
os.path.join(
Path(__file__).resolve().parent,
"fixtures",
"config/forward_compat_config.yml",
)
)
# Unknown entries in scan.media are filtered out; known ones survive.
assert loader.config.SCAN_MEDIA == ["box2d", "screenshot"]
# Unknown thumbnail/image values fall back to their defaults.
assert loader.config.GAMELIST_MEDIA_THUMBNAIL == "box2d"
assert loader.config.GAMELIST_MEDIA_IMAGE == "screenshot"
def test_malformed_yaml_falls_back_to_defaults():
"""A YAML parse error should log critically and leave the app on
defaults, not crash."""
loader = ConfigManager(
os.path.join(
Path(__file__).resolve().parent,
"fixtures",
"config/malformed_config.yml",
)
)
assert loader.config.ROMS_FOLDER_NAME == "roms"
assert loader.config.FIRMWARE_FOLDER_NAME == "bios"
assert loader.config.SCAN_MEDIA == ["box2d", "screenshot", "manual"]
# The parse error is surfaced so the UI can warn the user their whole
# config (not just the broken part) was discarded.
assert loader.config.CONFIG_FILE_PARSE_ERROR is not None
def test_valid_config_has_no_parse_error():
"""A syntactically valid config should not report a parse error."""
loader = ConfigManager(
os.path.join(Path(__file__).resolve().parent, "fixtures", "config/config.yml")
)
assert loader.config.CONFIG_FILE_PARSE_ERROR is None
def test_mixed_gamelist_syntax_surfaces_parse_error(tmp_path):
"""Regression for #3708: mixing the old (`scan.export`) and new
(`scan.gamelist.export`) syntax produces invalid YAML. The whole config is
discarded and defaults are used, so the parse error must be surfaced rather
than silently swallowed."""
config_file = tmp_path / "config.yml"
config_file.write_text(
"scan:\n"
" gamelist:\n"
" export: true\n"
" - gamelist_xml: true\n"
" media:\n"
" - box2d\n"
" - video\n"
" - manual\n"
)
loader = ConfigManager(str(config_file))
assert loader.config.CONFIG_FILE_PARSE_ERROR is not None
# The user's scan.media list is lost, falling back to defaults.
assert loader.config.SCAN_MEDIA == ["box2d", "screenshot", "manual"]
def test_parse_error_is_cleared_when_config_is_missing(tmp_path):
"""A stale parse error from a malformed config must not persist once the
file is gone. The loader is a singleton, so a later reload that skips YAML
parsing (e.g. the file was deleted) has to clear the flag."""
config_file = tmp_path / "config.yml"
config_file.write_text("scan:\n - broken: true\n media: [box2d]\n")
loader = ConfigManager(str(config_file))
assert loader.config.CONFIG_FILE_PARSE_ERROR is not None
# get_config's FileNotFoundError branch must clear the stale error.
config_file.unlink()
loader.get_config()
assert loader.config.CONFIG_FILE_PARSE_ERROR is None
def test_parse_error_is_cleared_when_config_is_recreated(tmp_path):
"""Reloading via a missing file goes through _create_missing_config_file,
which must also clear a stale parse error from a prior malformed load."""
broken_file = tmp_path / "config.yml"
broken_file.write_text("scan:\n - broken: true\n media: [box2d]\n")
ConfigManager(str(broken_file))
# Reusing the singleton with a missing path exercises the __init__
# FileNotFoundError -> _create_missing_config_file recreate path.
broken_file.unlink()
loader = ConfigManager(str(broken_file))
assert loader.config.CONFIG_FILE_PARSE_ERROR is None
def test_config_updates_serialize_gamelist_media_as_plain_strings(tmp_path):
config_file = tmp_path / "config.yml"
config_file.write_text(
"scan:\n"
" gamelist:\n"
" media:\n"
" thumbnail: box2d\n"
" image: screenshot\n"
)
loader = ConfigManager(str(config_file))
loader.add_platform_binding("atarist", "atari-st")
config_text = config_file.read_text()
assert "!!python/object" not in config_text
assert "thumbnail: box2d" in config_text
assert "image: screenshot" in config_text
reloaded = ConfigManager(str(config_file))
assert reloaded.config.PLATFORMS_BINDING == {"atarist": "atari-st"}