test_config.py 6.6 KB

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