diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index be932275a..f2d31c134 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -23,11 +23,9 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install nose - run: pip install nose + - name: Install pytest + run: pip install pytest - name: Run tests continue-on-error: False - env: - YTDL_TEST_SET: core - run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} + run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} core # Linter is in quick-test diff --git a/.github/workflows/download.yml b/.github/workflows/download.yml index 9e650d2dc..3b63fdd35 100644 --- a/.github/workflows/download.yml +++ b/.github/workflows/download.yml @@ -21,10 +21,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install nose - run: pip install nose + - name: Install pytest + run: pip install pytest - name: Run tests continue-on-error: true - env: - YTDL_TEST_SET: download - run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} + run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} download diff --git a/.github/workflows/quick-test.yml b/.github/workflows/quick-test.yml index 584cd5f2a..7d409dfc4 100644 --- a/.github/workflows/quick-test.yml +++ b/.github/workflows/quick-test.yml @@ -12,11 +12,9 @@ jobs: with: python-version: 3.9 - name: Install test requirements - run: pip install nose pycryptodome + run: pip install pytest pycryptodome - name: Run tests - env: - YTDL_TEST_SET: core - run: ./devscripts/run_tests.sh + run: ./devscripts/run_tests.sh core flake8: name: Linter if: "!contains(github.event.head_commit.message, 'ci skip all')" @@ -30,4 +28,4 @@ jobs: - name: Install flake8 run: pip install flake8 - name: Run flake8 - run: flake8 . \ No newline at end of file + run: flake8 . diff --git a/.gitignore b/.gitignore index 711bffaba..7ed34448a 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ cookies.txt # Python *.pyc *.pyo +.pytest_cache wine-py2exe/ py2exe.log build/ @@ -79,6 +80,7 @@ README.txt *.tar.gz *.zsh *.spec +test/testdata/player-*.js # Binary /youtube-dl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef18bb4bc..5faf97b10 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,16 +81,17 @@ To run the test, simply invoke your favorite test runner, or execute a test file python -m unittest discover python test/test_download.py nosetests + pytest See item 6 of [new extractor tutorial](#adding-support-for-a-new-site) for how to run extractor specific test cases. If you want to create a build of youtube-dl yourself, you'll need -* python +* python3 * make (only GNU make is supported) * pandoc * zip -* nosetests +* pytest ### Adding support for a new site diff --git a/Makefile b/Makefile index aad312362..fb7b8a0cc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ pypi-files: AUTHORS Changelog.md LICENSE README.md README.txt supportedsites com .PHONY: all clean install test tar pypi-files completions ot offlinetest codetest supportedsites clean-test: - rm -rf *.dump *.part* *.ytdl *.info.json *.mp4 *.m4a *.flv *.mp3 *.avi *.mkv *.webm *.3gp *.wav *.ape *.swf *.jpg *.png *.frag *.frag.urls *.frag.aria2 + rm -rf *.dump *.part* *.ytdl *.info.json *.mp4 *.m4a *.flv *.mp3 *.avi *.mkv *.webm *.3gp *.wav *.ape *.swf *.jpg *.png *.frag *.frag.urls *.frag.aria2 test/testdata/player-*.js clean-dist: rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS .mailmap clean-cache: @@ -49,23 +49,11 @@ codetest: flake8 . test: - #nosetests --with-coverage --cover-package=yt_dlp --cover-html --verbose --processes 4 test - nosetests --verbose test + $(PYTHON) -m pytest $(MAKE) codetest -# Keep this list in sync with devscripts/run_tests.sh offlinetest: codetest - $(PYTHON) -m nose --verbose test \ - --exclude test_age_restriction.py \ - --exclude test_download.py \ - --exclude test_iqiyi_sdk_interpreter.py \ - --exclude test_overwrites.py \ - --exclude test_socks.py \ - --exclude test_subtitles.py \ - --exclude test_write_annotations.py \ - --exclude test_youtube_lists.py \ - --exclude test_youtube_signature.py \ - --exclude test_post_hooks.py + $(PYTHON) -m pytest -k "not download" yt-dlp: yt_dlp/*.py yt_dlp/*/*.py mkdir -p zip diff --git a/README.md b/README.md index d879dc4c4..b4e7e6e86 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ You can also build the executable without any version info or metadata by using: Note that pyinstaller [does not support](https://github.com/pyinstaller/pyinstaller#requirements-and-tested-platforms) Python installed from the Windows store without using a virtual environment **For Unix**: -You will need the required build tools: `python`, `make` (GNU), `pandoc`, `zip`, `nosetests` +You will need the required build tools: `python`, `make` (GNU), `pandoc`, `zip`, `pytest` Then simply run `make`. You can also run `make yt-dlp` instead to compile only the binary without updating any of the additional files **Note**: In either platform, `devscripts\update-version.py` can be used to automatically update the version number diff --git a/devscripts/run_tests.bat b/devscripts/run_tests.bat index 531af4066..f12ae1c1b 100644 --- a/devscripts/run_tests.bat +++ b/devscripts/run_tests.bat @@ -1,17 +1,16 @@ +@setlocal @echo off +cd /d %~dp0.. -rem Keep this list in sync with the `offlinetest` target in Makefile -set DOWNLOAD_TESTS="age_restriction^|download^|iqiyi_sdk_interpreter^|socks^|subtitles^|write_annotations^|youtube_lists^|youtube_signature^|post_hooks" - -if "%YTDL_TEST_SET%" == "core" ( - set test_set="-I test_("%DOWNLOAD_TESTS%")\.py" - set multiprocess_args="" -) else if "%YTDL_TEST_SET%" == "download" ( - set test_set="-I test_(?!"%DOWNLOAD_TESTS%").+\.py" - set multiprocess_args="--processes=4 --process-timeout=540" +if ["%~1"]==[""] ( + set "test_set=" +) else if ["%~1"]==["core"] ( + set "test_set=-k "not download"" +) else if ["%~1"]==["download"] ( + set "test_set=-k download" ) else ( - echo YTDL_TEST_SET is not set or invalid + echo.Invalid test type "%~1". Use "core" ^| "download" exit /b 1 ) -nosetests test --verbose %test_set:"=% %multiprocess_args:"=% +pytest %test_set% diff --git a/devscripts/run_tests.sh b/devscripts/run_tests.sh index b5a56facb..99ab0a793 100755 --- a/devscripts/run_tests.sh +++ b/devscripts/run_tests.sh @@ -1,22 +1,15 @@ -#!/bin/bash +#!/bin/sh -# Keep this list in sync with the `offlinetest` target in Makefile -DOWNLOAD_TESTS="age_restriction|download|iqiyi_sdk_interpreter|overwrites|socks|subtitles|write_annotations|youtube_lists|youtube_signature|post_hooks" +if [ -z $1 ]; then + test_set='test' +elif [ $1 = 'core' ]; then + test_set='not download' +elif [ $1 = 'download' ]; then + test_set='download' +else + echo 'Invalid test type "'$1'". Use "core" | "download"' + exit 1 +fi -test_set="" -multiprocess_args="" - -case "$YTDL_TEST_SET" in - core) - test_set="-I test_($DOWNLOAD_TESTS)\.py" - ;; - download) - test_set="-I test_(?!$DOWNLOAD_TESTS).+\.py" - multiprocess_args="--processes=4 --process-timeout=540" - ;; - *) - break - ;; -esac - -nosetests test --verbose $test_set $multiprocess_args +echo python3 -m pytest -k $test_set +python3 -m pytest -k "$test_set" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..52feb4aba --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +addopts = -ra -v --strict-markers +markers = + download diff --git a/test/helper.py b/test/helper.py index 963c40508..b40ffe3ce 100644 --- a/test/helper.py +++ b/test/helper.py @@ -22,6 +22,14 @@ from yt_dlp.utils import ( ) +if "pytest" in sys.modules: + import pytest + is_download_test = pytest.mark.download +else: + def is_download_test(testClass): + return testClass + + def get_params(override=None): PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json") diff --git a/test/test_InfoExtractor.py b/test/test_InfoExtractor.py index 9b6672a1d..cbca22c91 100644 --- a/test/test_InfoExtractor.py +++ b/test/test_InfoExtractor.py @@ -35,13 +35,13 @@ class InfoExtractorTestRequestHandler(compat_http_server.BaseHTTPRequestHandler) assert False -class TestIE(InfoExtractor): +class DummyIE(InfoExtractor): pass class TestInfoExtractor(unittest.TestCase): def setUp(self): - self.ie = TestIE(FakeYDL()) + self.ie = DummyIE(FakeYDL()) def test_ie_key(self): self.assertEqual(get_info_extractor(YoutubeIE.ie_key()), YoutubeIE) diff --git a/test/test_age_restriction.py b/test/test_age_restriction.py index af89f29ff..70f9f4845 100644 --- a/test/test_age_restriction.py +++ b/test/test_age_restriction.py @@ -7,8 +7,7 @@ import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from test.helper import try_rm - +from test.helper import try_rm, is_download_test from yt_dlp import YoutubeDL @@ -32,6 +31,7 @@ def _download_restricted(url, filename, age): return res +@is_download_test class TestAgeRestriction(unittest.TestCase): def _assert_restricted(self, url, filename, age, old_age=None): self.assertTrue(_download_restricted(url, filename, old_age)) diff --git a/test/test_download.py b/test/test_download.py index 23d733f44..e4485ce81 100644 --- a/test/test_download.py +++ b/test/test_download.py @@ -10,12 +10,13 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from test.helper import ( assertGreaterEqual, + expect_info_dict, expect_warnings, get_params, gettestcases, - expect_info_dict, - try_rm, + is_download_test, report_warning, + try_rm, ) @@ -64,6 +65,7 @@ def _file_md5(fn): defs = gettestcases() +@is_download_test class TestDownload(unittest.TestCase): # Parallel testing in nosetests. See # http://nose.readthedocs.org/en/latest/doc_tests/test_multiprocess/multiprocess.html diff --git a/test/test_iqiyi_sdk_interpreter.py b/test/test_iqiyi_sdk_interpreter.py index e6ed9d628..ee039f898 100644 --- a/test/test_iqiyi_sdk_interpreter.py +++ b/test/test_iqiyi_sdk_interpreter.py @@ -8,7 +8,7 @@ import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from test.helper import FakeYDL +from test.helper import FakeYDL, is_download_test from yt_dlp.extractor import IqiyiIE @@ -31,6 +31,7 @@ class WarningLogger(object): pass +@is_download_test class TestIqiyiSDKInterpreter(unittest.TestCase): def test_iqiyi_sdk_interpreter(self): ''' diff --git a/test/test_post_hooks.py b/test/test_post_hooks.py index 3f9a61c1e..1555a23e0 100644 --- a/test/test_post_hooks.py +++ b/test/test_post_hooks.py @@ -7,7 +7,7 @@ import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from test.helper import get_params, try_rm +from test.helper import get_params, try_rm, is_download_test import yt_dlp.YoutubeDL from yt_dlp.utils import DownloadError @@ -22,6 +22,7 @@ TEST_ID = 'gr51aVj-mLg' EXPECTED_NAME = 'gr51aVj-mLg' +@is_download_test class TestPostHooks(unittest.TestCase): def setUp(self): self.stored_name_1 = None diff --git a/test/test_socks.py b/test/test_socks.py index 76aabb27f..cf1f613ab 100644 --- a/test/test_socks.py +++ b/test/test_socks.py @@ -14,6 +14,7 @@ import subprocess from test.helper import ( FakeYDL, get_params, + is_download_test, ) from yt_dlp.compat import ( compat_str, @@ -21,6 +22,7 @@ from yt_dlp.compat import ( ) +@is_download_test class TestMultipleSocks(unittest.TestCase): @staticmethod def _check_params(attrs): @@ -76,6 +78,7 @@ class TestMultipleSocks(unittest.TestCase): params['secondary_server_ip']) +@is_download_test class TestSocks(unittest.TestCase): _SKIP_SOCKS_TEST = True diff --git a/test/test_subtitles.py b/test/test_subtitles.py index f7f356832..0c5b49ee8 100644 --- a/test/test_subtitles.py +++ b/test/test_subtitles.py @@ -7,7 +7,7 @@ import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from test.helper import FakeYDL, md5 +from test.helper import FakeYDL, md5, is_download_test from yt_dlp.extractor import ( @@ -30,6 +30,7 @@ from yt_dlp.extractor import ( ) +@is_download_test class BaseTestSubtitles(unittest.TestCase): url = None IE = None @@ -55,6 +56,7 @@ class BaseTestSubtitles(unittest.TestCase): return dict((l, sub_info['data']) for l, sub_info in subtitles.items()) +@is_download_test class TestYoutubeSubtitles(BaseTestSubtitles): url = 'QRS8MkLhQmM' IE = YoutubeIE @@ -111,6 +113,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles): self.assertFalse(subtitles) +@is_download_test class TestDailymotionSubtitles(BaseTestSubtitles): url = 'http://www.dailymotion.com/video/xczg00' IE = DailymotionIE @@ -134,6 +137,7 @@ class TestDailymotionSubtitles(BaseTestSubtitles): self.assertFalse(subtitles) +@is_download_test class TestTedSubtitles(BaseTestSubtitles): url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html' IE = TEDIE @@ -149,6 +153,7 @@ class TestTedSubtitles(BaseTestSubtitles): self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang) +@is_download_test class TestVimeoSubtitles(BaseTestSubtitles): url = 'http://vimeo.com/76979871' IE = VimeoIE @@ -170,6 +175,7 @@ class TestVimeoSubtitles(BaseTestSubtitles): self.assertFalse(subtitles) +@is_download_test class TestWallaSubtitles(BaseTestSubtitles): url = 'http://vod.walla.co.il/movie/2705958/the-yes-men' IE = WallaIE @@ -191,6 +197,7 @@ class TestWallaSubtitles(BaseTestSubtitles): self.assertFalse(subtitles) +@is_download_test class TestCeskaTelevizeSubtitles(BaseTestSubtitles): url = 'http://www.ceskatelevize.cz/ivysilani/10600540290-u6-uzasny-svet-techniky' IE = CeskaTelevizeIE @@ -212,6 +219,7 @@ class TestCeskaTelevizeSubtitles(BaseTestSubtitles): self.assertFalse(subtitles) +@is_download_test class TestLyndaSubtitles(BaseTestSubtitles): url = 'http://www.lynda.com/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html' IE = LyndaIE @@ -224,6 +232,7 @@ class TestLyndaSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['en']), '09bbe67222259bed60deaa26997d73a7') +@is_download_test class TestNPOSubtitles(BaseTestSubtitles): url = 'http://www.npo.nl/nos-journaal/28-08-2014/POW_00722860' IE = NPOIE @@ -236,6 +245,7 @@ class TestNPOSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4') +@is_download_test class TestMTVSubtitles(BaseTestSubtitles): url = 'http://www.cc.com/video-clips/p63lk0/adam-devine-s-house-party-chasing-white-swans' IE = ComedyCentralIE @@ -251,6 +261,7 @@ class TestMTVSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['en']), '78206b8d8a0cfa9da64dc026eea48961') +@is_download_test class TestNRKSubtitles(BaseTestSubtitles): url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1' IE = NRKTVIE @@ -263,6 +274,7 @@ class TestNRKSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['no']), '544fa917d3197fcbee64634559221cc2') +@is_download_test class TestRaiPlaySubtitles(BaseTestSubtitles): IE = RaiPlayIE @@ -283,6 +295,7 @@ class TestRaiPlaySubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['it']), '4b3264186fbb103508abe5311cfcb9cd') +@is_download_test class TestVikiSubtitles(BaseTestSubtitles): url = 'http://www.viki.com/videos/1060846v-punch-episode-18' IE = VikiIE @@ -295,6 +308,7 @@ class TestVikiSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['en']), '53cb083a5914b2d84ef1ab67b880d18a') +@is_download_test class TestThePlatformSubtitles(BaseTestSubtitles): # from http://www.3playmedia.com/services-features/tools/integrations/theplatform/ # (see http://theplatform.com/about/partners/type/subtitles-closed-captioning/) @@ -309,6 +323,7 @@ class TestThePlatformSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['en']), '97e7670cbae3c4d26ae8bcc7fdd78d4b') +@is_download_test class TestThePlatformFeedSubtitles(BaseTestSubtitles): url = 'http://feed.theplatform.com/f/7wvmTC/msnbc_video-p-test?form=json&pretty=true&range=-40&byGuid=n_hardball_5biden_140207' IE = ThePlatformFeedIE @@ -321,6 +336,7 @@ class TestThePlatformFeedSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['en']), '48649a22e82b2da21c9a67a395eedade') +@is_download_test class TestRtveSubtitles(BaseTestSubtitles): url = 'http://www.rtve.es/alacarta/videos/los-misterios-de-laura/misterios-laura-capitulo-32-misterio-del-numero-17-2-parte/2428621/' IE = RTVEALaCartaIE @@ -335,6 +351,7 @@ class TestRtveSubtitles(BaseTestSubtitles): self.assertEqual(md5(subtitles['es']), '69e70cae2d40574fb7316f31d6eb7fca') +@is_download_test class TestDemocracynowSubtitles(BaseTestSubtitles): url = 'http://www.democracynow.org/shows/2015/7/3' IE = DemocracynowIE diff --git a/test/test_write_annotations.py b/test/test_write_annotations.py index fa31be0cc..7e4d8bc5a 100644 --- a/test/test_write_annotations.py +++ b/test/test_write_annotations.py @@ -8,7 +8,7 @@ import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from test.helper import get_params, try_rm +from test.helper import get_params, try_rm, is_download_test import io @@ -38,6 +38,7 @@ ANNOTATIONS_FILE = TEST_ID + '.annotations.xml' EXPECTED_ANNOTATIONS = ['Speech bubble', 'Note', 'Title', 'Spotlight', 'Label'] +@is_download_test class TestAnnotations(unittest.TestCase): def setUp(self): # Clear old files diff --git a/test/test_youtube_lists.py b/test/test_youtube_lists.py index 528b75334..e831393e4 100644 --- a/test/test_youtube_lists.py +++ b/test/test_youtube_lists.py @@ -7,7 +7,7 @@ import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from test.helper import FakeYDL +from test.helper import FakeYDL, is_download_test from yt_dlp.extractor import ( @@ -17,6 +17,7 @@ from yt_dlp.extractor import ( ) +@is_download_test class TestYoutubeLists(unittest.TestCase): def assertIsPlaylist(self, info): """Make sure the info has '_type' set to 'playlist'""" diff --git a/test/test_youtube_signature.py b/test/test_youtube_signature.py index 1a5063bab..dcf6ab60d 100644 --- a/test/test_youtube_signature.py +++ b/test/test_youtube_signature.py @@ -12,7 +12,7 @@ import io import re import string -from test.helper import FakeYDL +from test.helper import FakeYDL, is_download_test from yt_dlp.extractor import YoutubeIE from yt_dlp.compat import compat_str, compat_urlretrieve @@ -65,6 +65,7 @@ _TESTS = [ ] +@is_download_test class TestPlayerInfo(unittest.TestCase): def test_youtube_extract_player_info(self): PLAYER_URLS = ( @@ -87,6 +88,7 @@ class TestPlayerInfo(unittest.TestCase): self.assertEqual(player_id, expected_player_id) +@is_download_test class TestSignature(unittest.TestCase): def setUp(self): TEST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/tox.ini b/tox.ini index 4e7143fc6..d4e80a368 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,7 @@ [tox] envlist = py26,py27,py33,py34,py35 + +# Needed? [testenv] deps = nose