diff options
Diffstat (limited to 'test/test_traversal.py')
| -rw-r--r-- | test/test_traversal.py | 190 |
1 files changed, 163 insertions, 27 deletions
diff --git a/test/test_traversal.py b/test/test_traversal.py index 00a428edb..504cdee37 100644 --- a/test/test_traversal.py +++ b/test/test_traversal.py @@ -9,21 +9,32 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import itertools import re from youtube_dl.traversal import ( dict_get, get_first, + require, + subs_list_to_dict, T, traverse_obj, + unpack, + value, ) from youtube_dl.compat import ( + compat_chr as chr, compat_etree_fromstring, compat_http_cookies, + compat_map as map, compat_str, + compat_zip as zip, ) from youtube_dl.utils import ( + determine_ext, + ExtractorError, int_or_none, + join_nonempty, str_or_none, ) @@ -446,42 +457,164 @@ class TestTraversal(_TestCase): msg='`any` should allow further branching') def test_traversal_morsel(self): - values = { - 'expires': 'a', - 'path': 'b', - 'comment': 'c', - 'domain': 'd', - 'max-age': 'e', - 'secure': 'f', - 'httponly': 'g', - 'version': 'h', - 'samesite': 'i', - } - # SameSite added in Py3.8, breaks .update for 3.5-3.7 - if sys.version_info < (3, 8): - del values['samesite'] morsel = compat_http_cookies.Morsel() + # SameSite added in Py3.8, breaks .update for 3.5-3.7 + # Similarly Partitioned, Py3.14, thx Grub4k + values = dict(zip(morsel, map(chr, itertools.count(ord('a'))))) morsel.set(str('item_key'), 'item_value', 'coded_value') morsel.update(values) - values['key'] = str('item_key') - values['value'] = 'item_value' + values.update({ + 'key': str('item_key'), + 'value': 'item_value', + }), values = dict((str(k), v) for k, v in values.items()) - # make test pass even without ordered dict - value_set = set(values.values()) - for key, value in values.items(): - self.assertEqual(traverse_obj(morsel, key), value, + for key, val in values.items(): + self.assertEqual(traverse_obj(morsel, key), val, msg='Morsel should provide access to all values') - self.assertEqual(set(traverse_obj(morsel, Ellipsis)), value_set, - msg='`...` should yield all values') - self.assertEqual(set(traverse_obj(morsel, lambda k, v: True)), value_set, - msg='function key should yield all values') + values = list(values.values()) + self.assertMaybeCountEqual(traverse_obj(morsel, Ellipsis), values, + msg='`...` should yield all values') + self.assertMaybeCountEqual(traverse_obj(morsel, lambda k, v: True), values, + msg='function key should yield all values') self.assertIs(traverse_obj(morsel, [(None,), any]), morsel, msg='Morsel should not be implicitly changed to dict on usage') - def test_get_first(self): - self.assertEqual(get_first([{'a': None}, {'a': 'spam'}], 'a'), 'spam') - + def test_traversal_filter(self): + data = [None, False, True, 0, 1, 0.0, 1.1, '', 'str', {}, {0: 0}, [], [1]] + + self.assertEqual( + traverse_obj(data, (Ellipsis, filter)), + [True, 1, 1.1, 'str', {0: 0}, [1]], + '`filter` should filter falsy values') + + +class TestTraversalHelpers(_TestCase): + def test_traversal_require(self): + with self.assertRaises(ExtractorError, msg='Missing `value` should raise'): + traverse_obj(_TEST_DATA, ('None', T(require('value')))) + self.assertEqual( + traverse_obj(_TEST_DATA, ('str', T(require('value')))), 'str', + '`require` should pass through non-`None` values') + + def test_subs_list_to_dict(self): + self.assertEqual(traverse_obj([ + {'name': 'de', 'url': 'https://example.com/subs/de.vtt'}, + {'name': 'en', 'url': 'https://example.com/subs/en1.ass'}, + {'name': 'en', 'url': 'https://example.com/subs/en2.ass'}, + ], [Ellipsis, { + 'id': 'name', + 'url': 'url', + }, all, T(subs_list_to_dict)]), { + 'de': [{'url': 'https://example.com/subs/de.vtt'}], + 'en': [ + {'url': 'https://example.com/subs/en1.ass'}, + {'url': 'https://example.com/subs/en2.ass'}, + ], + }, 'function should build subtitle dict from list of subtitles') + self.assertEqual(traverse_obj([ + {'name': 'de', 'url': 'https://example.com/subs/de.ass'}, + {'name': 'de'}, + {'name': 'en', 'content': 'content'}, + {'url': 'https://example.com/subs/en'}, + ], [Ellipsis, { + 'id': 'name', + 'data': 'content', + 'url': 'url', + }, all, T(subs_list_to_dict(lang=None))]), { + 'de': [{'url': 'https://example.com/subs/de.ass'}], + 'en': [{'data': 'content'}], + }, 'subs with mandatory items missing should be filtered') + self.assertEqual(traverse_obj([ + {'url': 'https://example.com/subs/de.ass', 'name': 'de'}, + {'url': 'https://example.com/subs/en', 'name': 'en'}, + ], [Ellipsis, { + 'id': 'name', + 'ext': ['url', T(determine_ext(default_ext=None))], + 'url': 'url', + }, all, T(subs_list_to_dict(ext='ext'))]), { + 'de': [{'url': 'https://example.com/subs/de.ass', 'ext': 'ass'}], + 'en': [{'url': 'https://example.com/subs/en', 'ext': 'ext'}], + }, '`ext` should set default ext but leave existing value untouched') + self.assertEqual(traverse_obj([ + {'name': 'en', 'url': 'https://example.com/subs/en2', 'prio': True}, + {'name': 'en', 'url': 'https://example.com/subs/en1', 'prio': False}, + ], [Ellipsis, { + 'id': 'name', + 'quality': ['prio', T(int)], + 'url': 'url', + }, all, T(subs_list_to_dict(ext='ext'))]), {'en': [ + {'url': 'https://example.com/subs/en1', 'ext': 'ext'}, + {'url': 'https://example.com/subs/en2', 'ext': 'ext'}, + ]}, '`quality` key should sort subtitle list accordingly') + self.assertEqual(traverse_obj([ + {'name': 'de', 'url': 'https://example.com/subs/de.ass'}, + {'name': 'de'}, + {'name': 'en', 'content': 'content'}, + {'url': 'https://example.com/subs/en'}, + ], [Ellipsis, { + 'id': 'name', + 'url': 'url', + 'data': 'content', + }, all, T(subs_list_to_dict(lang='en'))]), { + 'de': [{'url': 'https://example.com/subs/de.ass'}], + 'en': [ + {'data': 'content'}, + {'url': 'https://example.com/subs/en'}, + ], + }, 'optionally provided lang should be used if no id available') + self.assertEqual(traverse_obj([ + {'name': 1, 'url': 'https://example.com/subs/de1'}, + {'name': {}, 'url': 'https://example.com/subs/de2'}, + {'name': 'de', 'ext': 1, 'url': 'https://example.com/subs/de3'}, + {'name': 'de', 'ext': {}, 'url': 'https://example.com/subs/de4'}, + ], [Ellipsis, { + 'id': 'name', + 'url': 'url', + 'ext': 'ext', + }, all, T(subs_list_to_dict(lang=None))]), { + 'de': [ + {'url': 'https://example.com/subs/de3'}, + {'url': 'https://example.com/subs/de4'}, + ], + }, 'non str types should be ignored for id and ext') + self.assertEqual(traverse_obj([ + {'name': 1, 'url': 'https://example.com/subs/de1'}, + {'name': {}, 'url': 'https://example.com/subs/de2'}, + {'name': 'de', 'ext': 1, 'url': 'https://example.com/subs/de3'}, + {'name': 'de', 'ext': {}, 'url': 'https://example.com/subs/de4'}, + ], [Ellipsis, { + 'id': 'name', + 'url': 'url', + 'ext': 'ext', + }, all, T(subs_list_to_dict(lang='de'))]), { + 'de': [ + {'url': 'https://example.com/subs/de1'}, + {'url': 'https://example.com/subs/de2'}, + {'url': 'https://example.com/subs/de3'}, + {'url': 'https://example.com/subs/de4'}, + ], + }, 'non str types should be replaced by default id') + + def test_unpack(self): + self.assertEqual( + unpack(lambda *x: ''.join(map(compat_str, x)))([1, 2, 3]), '123') + self.assertEqual( + unpack(join_nonempty)([1, 2, 3]), '1-2-3') + self.assertEqual( + unpack(join_nonempty, delim=' ')([1, 2, 3]), '1 2 3') + with self.assertRaises(TypeError): + unpack(join_nonempty)() + with self.assertRaises(TypeError): + unpack() + + def test_value(self): + self.assertEqual( + traverse_obj(_TEST_DATA, ('str', T(value('other')))), 'other', + '`value` should substitute specified value') + + +class TestDictGet(_TestCase): def test_dict_get(self): FALSE_VALUES = { 'none': None, @@ -504,6 +637,9 @@ class TestTraversal(_TestCase): self.assertEqual(dict_get(d, ('b', 'c', key, )), None) self.assertEqual(dict_get(d, ('b', 'c', key, ), skip_false_values=False), false_value) + def test_get_first(self): + self.assertEqual(get_first([{'a': None}, {'a': 'spam'}], 'a'), 'spam') + if __name__ == '__main__': unittest.main() |
