diff options
| -rw-r--r-- | .github/workflows/core.yml | 8 | ||||
| -rw-r--r-- | .github/workflows/download.yml | 8 | ||||
| -rw-r--r-- | .github/workflows/quick-test.yml | 8 | ||||
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | CONTRIBUTING.md | 5 | ||||
| -rw-r--r-- | Makefile | 18 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | devscripts/run_tests.bat | 21 | ||||
| -rwxr-xr-x | devscripts/run_tests.sh | 37 | ||||
| -rw-r--r-- | pytest.ini | 4 | ||||
| -rw-r--r-- | test/helper.py | 8 | ||||
| -rw-r--r-- | test/test_InfoExtractor.py | 4 | ||||
| -rw-r--r-- | test/test_age_restriction.py | 4 | ||||
| -rw-r--r-- | test/test_download.py | 6 | ||||
| -rw-r--r-- | test/test_iqiyi_sdk_interpreter.py | 3 | ||||
| -rw-r--r-- | test/test_post_hooks.py | 3 | ||||
| -rw-r--r-- | test/test_socks.py | 3 | ||||
| -rw-r--r-- | test/test_subtitles.py | 19 | ||||
| -rw-r--r-- | test/test_write_annotations.py | 3 | ||||
| -rw-r--r-- | test/test_youtube_lists.py | 3 | ||||
| -rw-r--r-- | test/test_youtube_signature.py | 4 | ||||
| -rw-r--r-- | tox.ini | 2 | 
22 files changed, 97 insertions, 78 deletions
| 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 @@ -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 @@ -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 - -# 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" - -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 +#!/bin/sh + +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 + +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__)) @@ -1,5 +1,7 @@  [tox]  envlist = py26,py27,py33,py34,py35 + +# Needed?  [testenv]  deps =     nose | 
