]>
Commit | Line | Data |
---|---|---|
773c272d SS |
1 | #!/usr/bin/env python3 |
2 | ||
3 | # Allow direct execution | |
4 | import os | |
5 | import sys | |
6 | import unittest | |
7 | import unittest.mock | |
8 | ||
9 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
10 | ||
11 | import contextlib | |
12 | import itertools | |
13 | from pathlib import Path | |
14 | ||
15 | from yt_dlp.compat import compat_expanduser | |
16 | from yt_dlp.options import create_parser, parseOpts | |
17 | from yt_dlp.utils import Config, get_executable_path | |
18 | ||
19 | ENVIRON_DEFAULTS = { | |
20 | 'HOME': None, | |
21 | 'XDG_CONFIG_HOME': '/_xdg_config_home/', | |
22 | 'USERPROFILE': 'C:/Users/testing/', | |
23 | 'APPDATA': 'C:/Users/testing/AppData/Roaming/', | |
24 | 'HOMEDRIVE': 'C:/', | |
25 | 'HOMEPATH': 'Users/testing/', | |
26 | } | |
27 | ||
28 | ||
29 | @contextlib.contextmanager | |
30 | def set_environ(**kwargs): | |
31 | saved_environ = os.environ.copy() | |
32 | ||
33 | for name, value in {**ENVIRON_DEFAULTS, **kwargs}.items(): | |
34 | if value is None: | |
35 | os.environ.pop(name, None) | |
36 | else: | |
37 | os.environ[name] = value | |
38 | ||
39 | yield | |
40 | ||
41 | os.environ.clear() | |
42 | os.environ.update(saved_environ) | |
43 | ||
44 | ||
45 | def _generate_expected_groups(): | |
46 | xdg_config_home = os.getenv('XDG_CONFIG_HOME') or compat_expanduser('~/.config') | |
47 | appdata_dir = os.getenv('appdata') | |
48 | home_dir = compat_expanduser('~') | |
49 | return { | |
50 | 'Portable': [ | |
51 | Path(get_executable_path(), 'yt-dlp.conf'), | |
52 | ], | |
53 | 'Home': [ | |
54 | Path('yt-dlp.conf'), | |
55 | ], | |
56 | 'User': [ | |
57 | Path(xdg_config_home, 'yt-dlp.conf'), | |
58 | Path(xdg_config_home, 'yt-dlp', 'config'), | |
59 | Path(xdg_config_home, 'yt-dlp', 'config.txt'), | |
60 | *(( | |
61 | Path(appdata_dir, 'yt-dlp.conf'), | |
62 | Path(appdata_dir, 'yt-dlp', 'config'), | |
63 | Path(appdata_dir, 'yt-dlp', 'config.txt'), | |
64 | ) if appdata_dir else ()), | |
65 | Path(home_dir, 'yt-dlp.conf'), | |
66 | Path(home_dir, 'yt-dlp.conf.txt'), | |
67 | Path(home_dir, '.yt-dlp', 'config'), | |
68 | Path(home_dir, '.yt-dlp', 'config.txt'), | |
69 | ], | |
70 | 'System': [ | |
71 | Path('/etc/yt-dlp.conf'), | |
72 | Path('/etc/yt-dlp/config'), | |
73 | Path('/etc/yt-dlp/config.txt'), | |
74 | ] | |
75 | } | |
76 | ||
77 | ||
78 | class TestConfig(unittest.TestCase): | |
79 | maxDiff = None | |
80 | ||
81 | @set_environ() | |
82 | def test_config__ENVIRON_DEFAULTS_sanity(self): | |
83 | expected = make_expected() | |
84 | self.assertCountEqual( | |
85 | set(expected), expected, | |
86 | 'ENVIRON_DEFAULTS produces non unique names') | |
87 | ||
88 | def test_config_all_environ_values(self): | |
89 | for name, value in ENVIRON_DEFAULTS.items(): | |
90 | for new_value in (None, '', '.', value or '/some/dir'): | |
91 | with set_environ(**{name: new_value}): | |
92 | self._simple_grouping_test() | |
93 | ||
94 | def test_config_default_expected_locations(self): | |
95 | files, _ = self._simple_config_test() | |
96 | self.assertEqual( | |
97 | files, make_expected(), | |
98 | 'Not all expected locations have been checked') | |
99 | ||
100 | def test_config_default_grouping(self): | |
101 | self._simple_grouping_test() | |
102 | ||
103 | def _simple_grouping_test(self): | |
104 | expected_groups = make_expected_groups() | |
105 | for name, group in expected_groups.items(): | |
106 | for index, existing_path in enumerate(group): | |
107 | result, opts = self._simple_config_test(existing_path) | |
108 | expected = expected_from_expected_groups(expected_groups, existing_path) | |
109 | self.assertEqual( | |
110 | result, expected, | |
111 | f'The checked locations do not match the expected ({name}, {index})') | |
112 | self.assertEqual( | |
113 | opts.outtmpl['default'], '1', | |
114 | f'The used result value was incorrect ({name}, {index})') | |
115 | ||
116 | def _simple_config_test(self, *stop_paths): | |
117 | encountered = 0 | |
118 | paths = [] | |
119 | ||
120 | def read_file(filename, default=[]): | |
121 | nonlocal encountered | |
122 | path = Path(filename) | |
123 | paths.append(path) | |
124 | if path in stop_paths: | |
125 | encountered += 1 | |
126 | return ['-o', f'{encountered}'] | |
127 | ||
128 | with ConfigMock(read_file): | |
129 | _, opts, _ = parseOpts([], False) | |
130 | ||
131 | return paths, opts | |
132 | ||
133 | @set_environ() | |
134 | def test_config_early_exit_commandline(self): | |
135 | self._early_exit_test(0, '--ignore-config') | |
136 | ||
137 | @set_environ() | |
138 | def test_config_early_exit_files(self): | |
139 | for index, _ in enumerate(make_expected(), 1): | |
140 | self._early_exit_test(index) | |
141 | ||
142 | def _early_exit_test(self, allowed_reads, *args): | |
143 | reads = 0 | |
144 | ||
145 | def read_file(filename, default=[]): | |
146 | nonlocal reads | |
147 | reads += 1 | |
148 | ||
149 | if reads > allowed_reads: | |
150 | self.fail('The remaining config was not ignored') | |
151 | elif reads == allowed_reads: | |
152 | return ['--ignore-config'] | |
153 | ||
154 | with ConfigMock(read_file): | |
155 | parseOpts(args, False) | |
156 | ||
157 | @set_environ() | |
158 | def test_config_override_commandline(self): | |
159 | self._override_test(0, '-o', 'pass') | |
160 | ||
161 | @set_environ() | |
162 | def test_config_override_files(self): | |
163 | for index, _ in enumerate(make_expected(), 1): | |
164 | self._override_test(index) | |
165 | ||
166 | def _override_test(self, start_index, *args): | |
167 | index = 0 | |
168 | ||
169 | def read_file(filename, default=[]): | |
170 | nonlocal index | |
171 | index += 1 | |
172 | ||
173 | if index > start_index: | |
174 | return ['-o', 'fail'] | |
175 | elif index == start_index: | |
176 | return ['-o', 'pass'] | |
177 | ||
178 | with ConfigMock(read_file): | |
179 | _, opts, _ = parseOpts(args, False) | |
180 | ||
181 | self.assertEqual( | |
182 | opts.outtmpl['default'], 'pass', | |
183 | 'The earlier group did not override the later ones') | |
184 | ||
185 | ||
186 | @contextlib.contextmanager | |
187 | def ConfigMock(read_file=None): | |
188 | with unittest.mock.patch('yt_dlp.options.Config') as mock: | |
189 | mock.return_value = Config(create_parser()) | |
190 | if read_file is not None: | |
191 | mock.read_file = read_file | |
192 | ||
193 | yield mock | |
194 | ||
195 | ||
196 | def make_expected(*filepaths): | |
197 | return expected_from_expected_groups(_generate_expected_groups(), *filepaths) | |
198 | ||
199 | ||
200 | def make_expected_groups(*filepaths): | |
201 | return _filter_expected_groups(_generate_expected_groups(), filepaths) | |
202 | ||
203 | ||
204 | def expected_from_expected_groups(expected_groups, *filepaths): | |
205 | return list(itertools.chain.from_iterable( | |
206 | _filter_expected_groups(expected_groups, filepaths).values())) | |
207 | ||
208 | ||
209 | def _filter_expected_groups(expected, filepaths): | |
210 | if not filepaths: | |
211 | return expected | |
212 | ||
213 | result = {} | |
214 | for group, paths in expected.items(): | |
215 | new_paths = [] | |
216 | for path in paths: | |
217 | new_paths.append(path) | |
218 | if path in filepaths: | |
219 | break | |
220 | ||
221 | result[group] = new_paths | |
222 | ||
223 | return result | |
224 | ||
225 | ||
226 | if __name__ == '__main__': | |
227 | unittest.main() |