]> jfr.im git - yt-dlp.git/blame - test/test_YoutubeDL.py
YoutubeDL: ignore indexes from 'playlist_items' that are not in the list (fixes ...
[yt-dlp.git] / test / test_YoutubeDL.py
CommitLineData
e028d0d1
JMF
1#!/usr/bin/env python
2
89087418
PH
3from __future__ import unicode_literals
4
5d254f77
PH
5# Allow direct execution
6import os
e028d0d1
JMF
7import sys
8import unittest
5d254f77 9sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
e028d0d1 10
0217c783
PH
11import copy
12
c57f7757 13from test.helper import FakeYDL, assertRegexpMatches
26e63931 14from youtube_dl import YoutubeDL
3d4a70b8 15from youtube_dl.extractor import YoutubeIE
2b4ecde2 16from youtube_dl.postprocessor.common import PostProcessor
531980d8 17from youtube_dl.utils import match_filter_func
e028d0d1 18
8508557e
JMF
19TEST_URL = 'http://localhost/sample.mp4'
20
e028d0d1
JMF
21
22class YDL(FakeYDL):
f4d96df0
PH
23 def __init__(self, *args, **kwargs):
24 super(YDL, self).__init__(*args, **kwargs)
e028d0d1 25 self.downloaded_info_dicts = []
f4d96df0 26 self.msgs = []
5d254f77 27
e028d0d1
JMF
28 def process_info(self, info_dict):
29 self.downloaded_info_dicts.append(info_dict)
30
f4d96df0
PH
31 def to_screen(self, msg):
32 self.msgs.append(msg)
33
5d254f77 34
3537b93d
PH
35def _make_result(formats, **kwargs):
36 res = {
37 'formats': formats,
38 'id': 'testid',
39 'title': 'testttitle',
40 'extractor': 'testex',
41 }
42 res.update(**kwargs)
43 return res
44
45
e028d0d1
JMF
46class TestFormatSelection(unittest.TestCase):
47 def test_prefer_free_formats(self):
48 # Same resolution => download webm
49 ydl = YDL()
50 ydl.params['prefer_free_formats'] = True
5d254f77 51 formats = [
8508557e
JMF
52 {'ext': 'webm', 'height': 460, 'url': TEST_URL},
53 {'ext': 'mp4', 'height': 460, 'url': TEST_URL},
5d254f77 54 ]
3537b93d 55 info_dict = _make_result(formats)
3d4a70b8
PH
56 yie = YoutubeIE(ydl)
57 yie._sort_formats(info_dict['formats'])
e028d0d1
JMF
58 ydl.process_ie_result(info_dict)
59 downloaded = ydl.downloaded_info_dicts[0]
89087418 60 self.assertEqual(downloaded['ext'], 'webm')
e028d0d1
JMF
61
62 # Different resolution => download best quality (mp4)
63 ydl = YDL()
64 ydl.params['prefer_free_formats'] = True
5d254f77 65 formats = [
8508557e
JMF
66 {'ext': 'webm', 'height': 720, 'url': TEST_URL},
67 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL},
5d254f77 68 ]
89087418 69 info_dict['formats'] = formats
3d4a70b8
PH
70 yie = YoutubeIE(ydl)
71 yie._sort_formats(info_dict['formats'])
e028d0d1
JMF
72 ydl.process_ie_result(info_dict)
73 downloaded = ydl.downloaded_info_dicts[0]
89087418 74 self.assertEqual(downloaded['ext'], 'mp4')
e028d0d1 75
1c783bca 76 # No prefer_free_formats => prefer mp4 and flv for greater compatibility
e028d0d1
JMF
77 ydl = YDL()
78 ydl.params['prefer_free_formats'] = False
5d254f77 79 formats = [
8508557e
JMF
80 {'ext': 'webm', 'height': 720, 'url': TEST_URL},
81 {'ext': 'mp4', 'height': 720, 'url': TEST_URL},
82 {'ext': 'flv', 'height': 720, 'url': TEST_URL},
5d254f77 83 ]
89087418 84 info_dict['formats'] = formats
3d4a70b8
PH
85 yie = YoutubeIE(ydl)
86 yie._sort_formats(info_dict['formats'])
87 ydl.process_ie_result(info_dict)
88 downloaded = ydl.downloaded_info_dicts[0]
89087418 89 self.assertEqual(downloaded['ext'], 'mp4')
3d4a70b8
PH
90
91 ydl = YDL()
92 ydl.params['prefer_free_formats'] = False
93 formats = [
8508557e
JMF
94 {'ext': 'flv', 'height': 720, 'url': TEST_URL},
95 {'ext': 'webm', 'height': 720, 'url': TEST_URL},
3d4a70b8 96 ]
89087418 97 info_dict['formats'] = formats
3d4a70b8
PH
98 yie = YoutubeIE(ydl)
99 yie._sort_formats(info_dict['formats'])
e028d0d1
JMF
100 ydl.process_ie_result(info_dict)
101 downloaded = ydl.downloaded_info_dicts[0]
89087418 102 self.assertEqual(downloaded['ext'], 'flv')
e028d0d1 103
a9c58ad9
JMF
104 def test_format_selection(self):
105 formats = [
8508557e
JMF
106 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
107 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL},
108 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL},
109 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},
a9c58ad9 110 ]
3537b93d 111 info_dict = _make_result(formats)
a9c58ad9 112
89087418 113 ydl = YDL({'format': '20/47'})
8e3e0322 114 ydl.process_ie_result(info_dict.copy())
a9c58ad9 115 downloaded = ydl.downloaded_info_dicts[0]
89087418 116 self.assertEqual(downloaded['format_id'], '47')
a9c58ad9 117
89087418 118 ydl = YDL({'format': '20/71/worst'})
8e3e0322 119 ydl.process_ie_result(info_dict.copy())
a9c58ad9 120 downloaded = ydl.downloaded_info_dicts[0]
89087418 121 self.assertEqual(downloaded['format_id'], '35')
a9c58ad9
JMF
122
123 ydl = YDL()
8e3e0322 124 ydl.process_ie_result(info_dict.copy())
a9c58ad9 125 downloaded = ydl.downloaded_info_dicts[0]
89087418 126 self.assertEqual(downloaded['format_id'], '2')
a9c58ad9 127
89087418 128 ydl = YDL({'format': 'webm/mp4'})
8e3e0322 129 ydl.process_ie_result(info_dict.copy())
49e86983 130 downloaded = ydl.downloaded_info_dicts[0]
89087418 131 self.assertEqual(downloaded['format_id'], '47')
49e86983 132
89087418 133 ydl = YDL({'format': '3gp/40/mp4'})
8e3e0322 134 ydl.process_ie_result(info_dict.copy())
49e86983 135 downloaded = ydl.downloaded_info_dicts[0]
89087418 136 self.assertEqual(downloaded['format_id'], '35')
49e86983 137
ba7678f9
PH
138 def test_format_selection_audio(self):
139 formats = [
8508557e
JMF
140 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
141 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
142 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL},
143 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},
ba7678f9 144 ]
3537b93d 145 info_dict = _make_result(formats)
ba7678f9 146
89087418 147 ydl = YDL({'format': 'bestaudio'})
ba7678f9
PH
148 ydl.process_ie_result(info_dict.copy())
149 downloaded = ydl.downloaded_info_dicts[0]
89087418 150 self.assertEqual(downloaded['format_id'], 'audio-high')
ba7678f9 151
89087418 152 ydl = YDL({'format': 'worstaudio'})
ba7678f9
PH
153 ydl.process_ie_result(info_dict.copy())
154 downloaded = ydl.downloaded_info_dicts[0]
89087418 155 self.assertEqual(downloaded['format_id'], 'audio-low')
ba7678f9
PH
156
157 formats = [
8508557e
JMF
158 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
159 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},
ba7678f9 160 ]
3537b93d 161 info_dict = _make_result(formats)
ba7678f9 162
89087418 163 ydl = YDL({'format': 'bestaudio/worstaudio/best'})
ba7678f9
PH
164 ydl.process_ie_result(info_dict.copy())
165 downloaded = ydl.downloaded_info_dicts[0]
89087418 166 self.assertEqual(downloaded['format_id'], 'vid-high')
ba7678f9 167
0217c783
PH
168 def test_format_selection_audio_exts(self):
169 formats = [
170 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
171 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
172 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
173 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
174 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
175 ]
176
177 info_dict = _make_result(formats)
178 ydl = YDL({'format': 'best'})
179 ie = YoutubeIE(ydl)
180 ie._sort_formats(info_dict['formats'])
181 ydl.process_ie_result(copy.deepcopy(info_dict))
182 downloaded = ydl.downloaded_info_dicts[0]
183 self.assertEqual(downloaded['format_id'], 'aac-64')
184
185 ydl = YDL({'format': 'mp3'})
186 ie = YoutubeIE(ydl)
187 ie._sort_formats(info_dict['formats'])
188 ydl.process_ie_result(copy.deepcopy(info_dict))
189 downloaded = ydl.downloaded_info_dicts[0]
190 self.assertEqual(downloaded['format_id'], 'mp3-64')
191
192 ydl = YDL({'prefer_free_formats': True})
193 ie = YoutubeIE(ydl)
194 ie._sort_formats(info_dict['formats'])
195 ydl.process_ie_result(copy.deepcopy(info_dict))
196 downloaded = ydl.downloaded_info_dicts[0]
197 self.assertEqual(downloaded['format_id'], 'ogg-64')
198
bc6d5978
JMF
199 def test_format_selection_video(self):
200 formats = [
8508557e
JMF
201 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL},
202 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL},
203 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},
bc6d5978 204 ]
3537b93d 205 info_dict = _make_result(formats)
bc6d5978
JMF
206
207 ydl = YDL({'format': 'bestvideo'})
208 ydl.process_ie_result(info_dict.copy())
209 downloaded = ydl.downloaded_info_dicts[0]
210 self.assertEqual(downloaded['format_id'], 'dash-video-high')
211
212 ydl = YDL({'format': 'worstvideo'})
213 ydl.process_ie_result(info_dict.copy())
214 downloaded = ydl.downloaded_info_dicts[0]
215 self.assertEqual(downloaded['format_id'], 'dash-video-low')
216
3d4a70b8
PH
217 def test_youtube_format_selection(self):
218 order = [
219 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '36', '17', '13',
220 # Apple HTTP Live Streaming
221 '96', '95', '94', '93', '92', '132', '151',
222 # 3D
223 '85', '84', '102', '83', '101', '82', '100',
224 # Dash video
c11125f9 225 '137', '248', '136', '247', '135', '246',
3d4a70b8
PH
226 '245', '244', '134', '243', '133', '242', '160',
227 # Dash audio
a053c349 228 '141', '172', '140', '171', '139',
3d4a70b8
PH
229 ]
230
231 for f1id, f2id in zip(order, order[1:]):
232 f1 = YoutubeIE._formats[f1id].copy()
233 f1['format_id'] = f1id
3537b93d 234 f1['url'] = 'url:' + f1id
3d4a70b8
PH
235 f2 = YoutubeIE._formats[f2id].copy()
236 f2['format_id'] = f2id
3537b93d 237 f2['url'] = 'url:' + f2id
3d4a70b8 238
3537b93d 239 info_dict = _make_result([f1, f2], extractor='youtube')
8dd54188 240 ydl = YDL({'format': 'best/bestvideo'})
3d4a70b8
PH
241 yie = YoutubeIE(ydl)
242 yie._sort_formats(info_dict['formats'])
243 ydl.process_ie_result(info_dict)
244 downloaded = ydl.downloaded_info_dicts[0]
245 self.assertEqual(downloaded['format_id'], f1id)
246
3537b93d 247 info_dict = _make_result([f2, f1], extractor='youtube')
8dd54188 248 ydl = YDL({'format': 'best/bestvideo'})
3d4a70b8
PH
249 yie = YoutubeIE(ydl)
250 yie._sort_formats(info_dict['formats'])
251 ydl.process_ie_result(info_dict)
252 downloaded = ydl.downloaded_info_dicts[0]
253 self.assertEqual(downloaded['format_id'], f1id)
254
083c9df9
PH
255 def test_format_filtering(self):
256 formats = [
257 {'format_id': 'A', 'filesize': 500, 'width': 1000},
258 {'format_id': 'B', 'filesize': 1000, 'width': 500},
259 {'format_id': 'C', 'filesize': 1000, 'width': 400},
260 {'format_id': 'D', 'filesize': 2000, 'width': 600},
261 {'format_id': 'E', 'filesize': 3000},
262 {'format_id': 'F'},
263 {'format_id': 'G', 'filesize': 1000000},
264 ]
265 for f in formats:
266 f['url'] = 'http://_/'
267 f['ext'] = 'unknown'
268 info_dict = _make_result(formats)
269
270 ydl = YDL({'format': 'best[filesize<3000]'})
271 ydl.process_ie_result(info_dict)
272 downloaded = ydl.downloaded_info_dicts[0]
273 self.assertEqual(downloaded['format_id'], 'D')
274
275 ydl = YDL({'format': 'best[filesize<=3000]'})
276 ydl.process_ie_result(info_dict)
277 downloaded = ydl.downloaded_info_dicts[0]
278 self.assertEqual(downloaded['format_id'], 'E')
279
280 ydl = YDL({'format': 'best[filesize <= ? 3000]'})
281 ydl.process_ie_result(info_dict)
282 downloaded = ydl.downloaded_info_dicts[0]
283 self.assertEqual(downloaded['format_id'], 'F')
284
285 ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
286 ydl.process_ie_result(info_dict)
287 downloaded = ydl.downloaded_info_dicts[0]
288 self.assertEqual(downloaded['format_id'], 'B')
289
290 ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
291 ydl.process_ie_result(info_dict)
292 downloaded = ydl.downloaded_info_dicts[0]
293 self.assertEqual(downloaded['format_id'], 'C')
294
295 ydl = YDL({'format': '[filesize>?1]'})
296 ydl.process_ie_result(info_dict)
297 downloaded = ydl.downloaded_info_dicts[0]
298 self.assertEqual(downloaded['format_id'], 'G')
299
300 ydl = YDL({'format': '[filesize<1M]'})
301 ydl.process_ie_result(info_dict)
302 downloaded = ydl.downloaded_info_dicts[0]
303 self.assertEqual(downloaded['format_id'], 'E')
304
305 ydl = YDL({'format': '[filesize<1MiB]'})
306 ydl.process_ie_result(info_dict)
307 downloaded = ydl.downloaded_info_dicts[0]
308 self.assertEqual(downloaded['format_id'], 'G')
309
f20bf146
JMF
310
311class TestYoutubeDL(unittest.TestCase):
ab84349b
JMF
312 def test_subtitles(self):
313 def s_formats(lang, autocaption=False):
314 return [{
315 'ext': ext,
316 'url': 'http://localhost/video.%s.%s' % (lang, ext),
317 '_auto': autocaption,
318 } for ext in ['vtt', 'srt', 'ass']]
319 subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
320 auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
321 info_dict = {
322 'id': 'test',
323 'title': 'Test',
324 'url': 'http://localhost/video.mp4',
325 'subtitles': subtitles,
326 'automatic_captions': auto_captions,
327 'extractor': 'TEST',
328 }
329
330 def get_info(params={}):
331 params.setdefault('simulate', True)
332 ydl = YDL(params)
333 ydl.report_warning = lambda *args, **kargs: None
334 return ydl.process_video_result(info_dict, download=False)
335
336 result = get_info()
337 self.assertFalse(result.get('requested_subtitles'))
338 self.assertEqual(result['subtitles'], subtitles)
339 self.assertEqual(result['automatic_captions'], auto_captions)
340
341 result = get_info({'writesubtitles': True})
342 subs = result['requested_subtitles']
343 self.assertTrue(subs)
344 self.assertEqual(set(subs.keys()), set(['en']))
345 self.assertTrue(subs['en'].get('data') is None)
346 self.assertEqual(subs['en']['ext'], 'ass')
347
348 result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
349 subs = result['requested_subtitles']
350 self.assertEqual(subs['en']['ext'], 'srt')
351
352 result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
353 subs = result['requested_subtitles']
354 self.assertTrue(subs)
355 self.assertEqual(set(subs.keys()), set(['es', 'fr']))
356
357 result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
358 subs = result['requested_subtitles']
359 self.assertTrue(subs)
360 self.assertEqual(set(subs.keys()), set(['es', 'pt']))
361 self.assertFalse(subs['es']['_auto'])
362 self.assertTrue(subs['pt']['_auto'])
363
98c70d6f
JMF
364 result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
365 subs = result['requested_subtitles']
366 self.assertTrue(subs)
367 self.assertEqual(set(subs.keys()), set(['es', 'pt']))
368 self.assertTrue(subs['es']['_auto'])
369 self.assertTrue(subs['pt']['_auto'])
370
b6c45014
JMF
371 def test_add_extra_info(self):
372 test_dict = {
373 'extractor': 'Foo',
374 }
375 extra_info = {
376 'extractor': 'Bar',
377 'playlist': 'funny videos',
378 }
379 YDL.add_extra_info(test_dict, extra_info)
380 self.assertEqual(test_dict['extractor'], 'Foo')
381 self.assertEqual(test_dict['playlist'], 'funny videos')
382
26e63931
JMF
383 def test_prepare_filename(self):
384 info = {
89087418
PH
385 'id': '1234',
386 'ext': 'mp4',
387 'width': None,
26e63931 388 }
5f6a1245 389
26e63931
JMF
390 def fname(templ):
391 ydl = YoutubeDL({'outtmpl': templ})
392 return ydl.prepare_filename(info)
89087418
PH
393 self.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
394 self.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
26e63931 395 # Replace missing fields with 'NA'
89087418 396 self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
26e63931 397
c57f7757
PH
398 def test_format_note(self):
399 ydl = YoutubeDL()
400 self.assertEqual(ydl._format_note({}), '')
401 assertRegexpMatches(self, ydl._format_note({
402 'vbr': 10,
1c783bca 403 }), '^\s*10k$')
f4d96df0 404
2b4ecde2
JMF
405 def test_postprocessors(self):
406 filename = 'post-processor-testfile.mp4'
407 audiofile = filename + '.mp3'
408
409 class SimplePP(PostProcessor):
410 def run(self, info):
2b4ecde2
JMF
411 with open(audiofile, 'wt') as f:
412 f.write('EXAMPLE')
592e97e8 413 return [info['filepath']], info
2b4ecde2 414
592e97e8 415 def run_pp(params, PP):
2b4ecde2
JMF
416 with open(filename, 'wt') as f:
417 f.write('EXAMPLE')
418 ydl = YoutubeDL(params)
592e97e8 419 ydl.add_post_processor(PP())
2b4ecde2
JMF
420 ydl.post_process(filename, {'filepath': filename})
421
592e97e8 422 run_pp({'keepvideo': True}, SimplePP)
2b4ecde2
JMF
423 self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
424 self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
425 os.unlink(filename)
426 os.unlink(audiofile)
427
592e97e8 428 run_pp({'keepvideo': False}, SimplePP)
2b4ecde2
JMF
429 self.assertFalse(os.path.exists(filename), '%s exists' % filename)
430 self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
431 os.unlink(audiofile)
432
592e97e8
JMF
433 class ModifierPP(PostProcessor):
434 def run(self, info):
435 with open(info['filepath'], 'wt') as f:
436 f.write('MODIFIED')
437 return [], info
438
439 run_pp({'keepvideo': False}, ModifierPP)
440 self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
441 os.unlink(filename)
442
531980d8
JMF
443 def test_match_filter(self):
444 class FilterYDL(YDL):
445 def __init__(self, *args, **kwargs):
446 super(FilterYDL, self).__init__(*args, **kwargs)
447 self.params['simulate'] = True
448
449 def process_info(self, info_dict):
450 super(YDL, self).process_info(info_dict)
451
452 def _match_entry(self, info_dict, incomplete):
453 res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
454 if res is None:
455 self.downloaded_info_dicts.append(info_dict)
456 return res
457
458 first = {
459 'id': '1',
460 'url': TEST_URL,
461 'title': 'one',
462 'extractor': 'TEST',
463 'duration': 30,
464 'filesize': 10 * 1024,
465 }
466 second = {
467 'id': '2',
468 'url': TEST_URL,
469 'title': 'two',
470 'extractor': 'TEST',
471 'duration': 10,
472 'description': 'foo',
473 'filesize': 5 * 1024,
474 }
475 videos = [first, second]
476
477 def get_videos(filter_=None):
478 ydl = FilterYDL({'match_filter': filter_})
479 for v in videos:
480 ydl.process_ie_result(v, download=True)
481 return [v['id'] for v in ydl.downloaded_info_dicts]
482
483 res = get_videos()
484 self.assertEqual(res, ['1', '2'])
485
486 def f(v):
487 if v['id'] == '1':
488 return None
489 else:
490 return 'Video id is not 1'
491 res = get_videos(f)
492 self.assertEqual(res, ['1'])
493
494 f = match_filter_func('duration < 30')
495 res = get_videos(f)
496 self.assertEqual(res, ['2'])
497
498 f = match_filter_func('description = foo')
499 res = get_videos(f)
500 self.assertEqual(res, ['2'])
501
502 f = match_filter_func('description =? foo')
503 res = get_videos(f)
504 self.assertEqual(res, ['1', '2'])
505
506 f = match_filter_func('filesize > 5KiB')
507 res = get_videos(f)
508 self.assertEqual(res, ['1'])
509
2b4ecde2 510
e028d0d1
JMF
511if __name__ == '__main__':
512 unittest.main()