aboutsummaryrefslogtreecommitdiff
path: root/test/test_traversal.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_traversal.py')
-rw-r--r--test/test_traversal.py190
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()