diff options
Diffstat (limited to 'test/test_utils.py')
| -rw-r--r-- | test/test_utils.py | 153 | 
1 files changed, 124 insertions, 29 deletions
diff --git a/test/test_utils.py b/test/test_utils.py index 5fab05f7c..1fc16ed05 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -79,10 +79,12 @@ from youtube_dl.utils import (      rot47,      shell_quote,      smuggle_url, +    str_or_none,      str_to_int,      strip_jsonp,      strip_or_none,      subtitles_filename, +    T,      timeconvert,      traverse_obj,      try_call, @@ -1566,6 +1568,7 @@ Line 1          self.assertEqual(variadic('spam', allowed_types=[dict]), 'spam')      def test_traverse_obj(self): +        str = compat_str          _TEST_DATA = {              100: 100,              1.2: 1.2, @@ -1598,8 +1601,8 @@ Line 1          # Test Ellipsis behavior          self.assertCountEqual(traverse_obj(_TEST_DATA, Ellipsis), -                              (item for item in _TEST_DATA.values() if item is not None), -                              msg='`...` should give all values except `None`') +                              (item for item in _TEST_DATA.values() if item not in (None, {})), +                              msg='`...` should give all non discarded values')          self.assertCountEqual(traverse_obj(_TEST_DATA, ('urls', 0, Ellipsis)), _TEST_DATA['urls'][0].values(),                                msg='`...` selection for dicts should select all values')          self.assertEqual(traverse_obj(_TEST_DATA, (Ellipsis, Ellipsis, 'url')), @@ -1607,13 +1610,51 @@ Line 1                           msg='nested `...` queries should work')          self.assertCountEqual(traverse_obj(_TEST_DATA, (Ellipsis, Ellipsis, 'index')), range(4),                                msg='`...` query result should be flattened') +        self.assertEqual(traverse_obj(iter(range(4)), Ellipsis), list(range(4)), +                         msg='`...` should accept iterables')          # Test function as key          self.assertEqual(traverse_obj(_TEST_DATA, lambda x, y: x == 'urls' and isinstance(y, list)),                           [_TEST_DATA['urls']],                           msg='function as query key should perform a filter based on (key, value)') -        self.assertCountEqual(traverse_obj(_TEST_DATA, lambda _, x: isinstance(x[0], compat_str)), ('str',), -                              msg='exceptions in the query function should be caught') +        self.assertCountEqual(traverse_obj(_TEST_DATA, lambda _, x: isinstance(x[0], str)), {'str'}, +                              msg='exceptions in the query function should be catched') +        self.assertEqual(traverse_obj(iter(range(4)), lambda _, x: x % 2 == 0), [0, 2], +                         msg='function key should accept iterables') +        if __debug__: +            with self.assertRaises(Exception, msg='Wrong function signature should raise in debug'): +                traverse_obj(_TEST_DATA, lambda a: Ellipsis) +            with self.assertRaises(Exception, msg='Wrong function signature should raise in debug'): +                traverse_obj(_TEST_DATA, lambda a, b, c: Ellipsis) + +        # Test set as key (transformation/type, like `expected_type`) +        self.assertEqual(traverse_obj(_TEST_DATA, (Ellipsis, T(str.upper), )), ['STR'], +                         msg='Function in set should be a transformation') +        self.assertEqual(traverse_obj(_TEST_DATA, (Ellipsis, T(str))), ['str'], +                         msg='Type in set should be a type filter') +        self.assertEqual(traverse_obj(_TEST_DATA, T(dict)), _TEST_DATA, +                         msg='A single set should be wrapped into a path') +        self.assertEqual(traverse_obj(_TEST_DATA, (Ellipsis, T(str.upper))), ['STR'], +                         msg='Transformation function should not raise') +        self.assertEqual(traverse_obj(_TEST_DATA, (Ellipsis, T(str_or_none))), +                         [item for item in map(str_or_none, _TEST_DATA.values()) if item is not None], +                         msg='Function in set should be a transformation') +        if __debug__: +            with self.assertRaises(Exception, msg='Sets with length != 1 should raise in debug'): +                traverse_obj(_TEST_DATA, set()) +            with self.assertRaises(Exception, msg='Sets with length != 1 should raise in debug'): +                traverse_obj(_TEST_DATA, {str.upper, str}) + +        # Test `slice` as a key +        _SLICE_DATA = [0, 1, 2, 3, 4] +        self.assertEqual(traverse_obj(_TEST_DATA, ('dict', slice(1))), None, +                         msg='slice on a dictionary should not throw') +        self.assertEqual(traverse_obj(_SLICE_DATA, slice(1)), _SLICE_DATA[:1], +                         msg='slice key should apply slice to sequence') +        self.assertEqual(traverse_obj(_SLICE_DATA, slice(1, 2)), _SLICE_DATA[1:2], +                         msg='slice key should apply slice to sequence') +        self.assertEqual(traverse_obj(_SLICE_DATA, slice(1, 4, 2)), _SLICE_DATA[1:4:2], +                         msg='slice key should apply slice to sequence')          # Test alternative paths          self.assertEqual(traverse_obj(_TEST_DATA, 'fail', 'str'), 'str', @@ -1659,15 +1700,23 @@ Line 1                           {0: ['https://www.example.com/1', 'https://www.example.com/0']},                           msg='triple nesting in dict path should be treated as branches')          self.assertEqual(traverse_obj(_TEST_DATA, {0: 'fail'}), {}, -                         msg='remove `None` values when dict key') +                         msg='remove `None` values when top level dict key fails')          self.assertEqual(traverse_obj(_TEST_DATA, {0: 'fail'}, default=Ellipsis), {0: Ellipsis}, -                         msg='do not remove `None` values if `default`') -        self.assertEqual(traverse_obj(_TEST_DATA, {0: 'dict'}), {0: {}}, -                         msg='do not remove empty values when dict key') -        self.assertEqual(traverse_obj(_TEST_DATA, {0: 'dict'}, default=Ellipsis), {0: {}}, -                         msg='do not remove empty values when dict key and a default') -        self.assertEqual(traverse_obj(_TEST_DATA, {0: ('dict', Ellipsis)}), {0: []}, -                         msg='if branch in dict key not successful, return `[]`') +                         msg='use `default` if key fails and `default`') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: 'dict'}), {}, +                         msg='remove empty values when dict key') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: 'dict'}, default=Ellipsis), {0: Ellipsis}, +                         msg='use `default` when dict key and `default`') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: {0: 'fail'}}), {}, +                         msg='remove empty values when nested dict key fails') +        self.assertEqual(traverse_obj(None, {0: 'fail'}), {}, +                         msg='default to dict if pruned') +        self.assertEqual(traverse_obj(None, {0: 'fail'}, default=Ellipsis), {0: Ellipsis}, +                         msg='default to dict if pruned and default is given') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: {0: 'fail'}}, default=Ellipsis), {0: {0: Ellipsis}}, +                         msg='use nested `default` when nested dict key fails and `default`') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: ('dict', Ellipsis)}), {}, +                         msg='remove key if branch in dict key not successful')          # Testing default parameter behavior          _DEFAULT_DATA = {'None': None, 'int': 0, 'list': []} @@ -1691,20 +1740,55 @@ Line 1                           msg='if branched but not successful return `[]`, not `default`')          self.assertEqual(traverse_obj(_DEFAULT_DATA, ('list', Ellipsis)), [],                           msg='if branched but object is empty return `[]`, not `default`') +        self.assertEqual(traverse_obj(None, Ellipsis), [], +                         msg='if branched but object is `None` return `[]`, not `default`') +        self.assertEqual(traverse_obj({0: None}, (0, Ellipsis)), [], +                         msg='if branched but state is `None` return `[]`, not `default`') + +        branching_paths = [ +            ('fail', Ellipsis), +            (Ellipsis, 'fail'), +            100 * ('fail',) + (Ellipsis,), +            (Ellipsis,) + 100 * ('fail',), +        ] +        for branching_path in branching_paths: +            self.assertEqual(traverse_obj({}, branching_path), [], +                             msg='if branched but state is `None`, return `[]` (not `default`)') +            self.assertEqual(traverse_obj({}, 'fail', branching_path), [], +                             msg='if branching in last alternative and previous did not match, return `[]` (not `default`)') +            self.assertEqual(traverse_obj({0: 'x'}, 0, branching_path), 'x', +                             msg='if branching in last alternative and previous did match, return single value') +            self.assertEqual(traverse_obj({0: 'x'}, branching_path, 0), 'x', +                             msg='if branching in first alternative and non-branching path does match, return single value') +            self.assertEqual(traverse_obj({}, branching_path, 'fail'), None, +                             msg='if branching in first alternative and non-branching path does not match, return `default`')          # Testing expected_type behavior          _EXPECTED_TYPE_DATA = {'str': 'str', 'int': 0} -        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'str', expected_type=compat_str), 'str', -                         msg='accept matching `expected_type` type') -        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'str', expected_type=int), None, -                         msg='reject non matching `expected_type` type') -        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'int', expected_type=lambda x: compat_str(x)), '0', -                         msg='transform type using type function') -        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'str', -                                      expected_type=lambda _: 1 / 0), None, -                         msg='wrap expected_type function in try_call') -        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, Ellipsis, expected_type=compat_str), ['str'], -                         msg='eliminate items that expected_type fails on') +        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'str', expected_type=str), +                         'str', msg='accept matching `expected_type` type') +        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'str', expected_type=int), +                         None, msg='reject non matching `expected_type` type') +        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'int', expected_type=lambda x: str(x)), +                         '0', msg='transform type using type function') +        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, 'str', expected_type=lambda _: 1 / 0), +                         None, msg='wrap expected_type function in try_call') +        self.assertEqual(traverse_obj(_EXPECTED_TYPE_DATA, Ellipsis, expected_type=str), +                         ['str'], msg='eliminate items that expected_type fails on') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: 100, 1: 1.2}, expected_type=int), +                         {0: 100}, msg='type as expected_type should filter dict values') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: 100, 1: 1.2, 2: 'None'}, expected_type=str_or_none), +                         {0: '100', 1: '1.2'}, msg='function as expected_type should transform dict values') +        self.assertEqual(traverse_obj(_TEST_DATA, ({0: 1.2}, 0, {int_or_none}), expected_type=int), +                         1, msg='expected_type should not filter non final dict values') +        self.assertEqual(traverse_obj(_TEST_DATA, {0: {0: 100, 1: 'str'}}, expected_type=int), +                         {0: {0: 100}}, msg='expected_type should transform deep dict values') +        self.assertEqual(traverse_obj(_TEST_DATA, [({0: '...'}, {0: '...'})], expected_type=type(Ellipsis)), +                         [{0: Ellipsis}, {0: Ellipsis}], msg='expected_type should transform branched dict values') +        self.assertEqual(traverse_obj({1: {3: 4}}, [(1, 2), 3], expected_type=int), +                         [4], msg='expected_type regression for type matching in tuple branching') +        self.assertEqual(traverse_obj(_TEST_DATA, ['data', Ellipsis], expected_type=int), +                         [], msg='expected_type regression for type matching in dict result')          # Test get_all behavior          _GET_ALL_DATA = {'key': [0, 1, 2]} @@ -1749,14 +1833,23 @@ Line 1                                        _traverse_string=True), '.',                           msg='traverse into converted data if `traverse_string`')          self.assertEqual(traverse_obj(_TRAVERSE_STRING_DATA, ('str', Ellipsis), -                                      _traverse_string=True), list('str'), -                         msg='`...` branching into string should result in list') +                                      _traverse_string=True), 'str', +                         msg='`...` should result in string (same value) if `traverse_string`') +        self.assertEqual(traverse_obj(_TRAVERSE_STRING_DATA, ('str', slice(0, None, 2)), +                                      _traverse_string=True), 'sr', +                         msg='`slice` should result in string if `traverse_string`') +        self.assertEqual(traverse_obj(_TRAVERSE_STRING_DATA, ('str', lambda i, v: i or v == "s"), +                                      _traverse_string=True), 'str', +                         msg='function should result in string if `traverse_string`')          self.assertEqual(traverse_obj(_TRAVERSE_STRING_DATA, ('str', (0, 2)),                                        _traverse_string=True), ['s', 'r'], -                         msg='branching into string should result in list') -        self.assertEqual(traverse_obj(_TRAVERSE_STRING_DATA, ('str', lambda _, x: x), -                                      _traverse_string=True), list('str'), -                         msg='function branching into string should result in list') +                         msg='branching should result in list if `traverse_string`') +        self.assertEqual(traverse_obj({}, (0, Ellipsis), _traverse_string=True), [], +                         msg='branching should result in list if `traverse_string`') +        self.assertEqual(traverse_obj({}, (0, lambda x, y: True), _traverse_string=True), [], +                         msg='branching should result in list if `traverse_string`') +        self.assertEqual(traverse_obj({}, (0, slice(1)), _traverse_string=True), [], +                         msg='branching should result in list if `traverse_string`')          # Test is_user_input behavior          _IS_USER_INPUT_DATA = {'range8': list(range(8))} @@ -1793,6 +1886,8 @@ Line 1                           msg='failing str key on a `re.Match` should return `default`')          self.assertEqual(traverse_obj(mobj, 8), None,                           msg='failing int key on a `re.Match` should return `default`') +        self.assertEqual(traverse_obj(mobj, lambda k, _: k in (0, 'group')), ['0123', '3'], +                         msg='function on a `re.Match` should give group name as well')      def test_get_first(self):          self.assertEqual(get_first([{'a': None}, {'a': 'spam'}], 'a'), 'spam')  | 
