From 66f2739be7cda3d610095cb8c1b1c9edbf006967 Mon Sep 17 00:00:00 2001 From: bendikro Date: Sun, 21 Sep 2014 16:37:29 +0200 Subject: [PATCH] Added .travis.yml (for travis-ci) and tox.ini files Targets: * Runs the unit-tests for python 2.7 * Tests unit-test coverage * Try to build docs * Code style checks: * flake8 * isort Codes changes: * Fixed tests for httpdownloader (using tmp dir) * Implemented a couple of tests for Stats plugin but they fail to run on travis Issues: * Can't get py26 to work because of installing libtorrent through apt and the option system_site_packages fails for 2.6. --- .travis.yml | 35 +++++ deluge/common.py | 1 + .../Stats/deluge/plugins/stats/test.sh | 6 - .../Stats/deluge/plugins/stats/test_total.py | 22 --- .../plugins/stats/{test.py => tests/.test.py} | 0 .../plugins/stats/{ => tests}/test.html | 0 .../deluge/plugins/stats/tests/test_stats.py | 44 ++++++ deluge/tests/test_client.py | 5 +- deluge/tests/test_config.py | 3 +- deluge/tests/test_core.py | 3 +- deluge/tests/test_httpdownloader.py | 47 +++--- deluge/tests/test_torrent.py | 3 +- deluge/tests/test_tracker_icons.py | 3 +- deluge/ui/web/json_api.py | 2 +- docs/source/conf.py | 6 +- setup.cfg | 4 - setup.py | 22 ++- tox.ini | 141 ++++++++++++++++++ 18 files changed, 285 insertions(+), 62 deletions(-) create mode 100644 .travis.yml delete mode 100644 deluge/plugins/Stats/deluge/plugins/stats/test.sh delete mode 100644 deluge/plugins/Stats/deluge/plugins/stats/test_total.py rename deluge/plugins/Stats/deluge/plugins/stats/{test.py => tests/.test.py} (100%) rename deluge/plugins/Stats/deluge/plugins/stats/{ => tests}/test.html (100%) create mode 100644 deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..55b630c1c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +language: python + +python: +# - "2.6" + - "2.7" + +# command to install dependencies +install: + - pip install tox + - lsb_release -a + - sudo add-apt-repository ppa:deluge-team/ppa -y + - sudo apt-get update + - sudo apt-get install python-libtorrent + +script: + - tox + +env: + - TOX_ENV=pydef + - TOX_ENV=plugins + - TOX_ENV=flake8 + - TOX_ENV=flake8-complexity + - TOX_ENV=isort + - TOX_ENV=docs + - TOX_ENV=testcoverage + +virtualenv: + system_site_packages: true + +before_script: + - export PYTHONPATH=$PYTHONPATH:$PWD + - python -c "import libtorrent as lt; print lt.version" + +script: + - tox -e $TOX_ENV diff --git a/deluge/common.py b/deluge/common.py index 6dc03dcf4..6f5a825c1 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -30,6 +30,7 @@ try: import dbus bus = dbus.SessionBus() dbus_fileman = bus.get_object("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1") + except: dbus_fileman = None diff --git a/deluge/plugins/Stats/deluge/plugins/stats/test.sh b/deluge/plugins/Stats/deluge/plugins/stats/test.sh deleted file mode 100644 index 937c39b5e..000000000 --- a/deluge/plugins/Stats/deluge/plugins/stats/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -while true; do - python test.py - sleep 2 -done; diff --git a/deluge/plugins/Stats/deluge/plugins/stats/test_total.py b/deluge/plugins/Stats/deluge/plugins/stats/test_total.py deleted file mode 100644 index 185e6bc43..000000000 --- a/deluge/plugins/Stats/deluge/plugins/stats/test_total.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import print_function - -from deluge.common import fsize -from deluge.ui.client import sclient - -sclient.set_core_uri() - - -def print_totals(totals): - for name, value in totals.iteritems(): - print(name, fsize(value)) - - print("overhead:") - print("up:", fsize(totals["total_upload"] - totals["total_payload_upload"])) - print("down:", fsize(totals["total_download"] - totals["total_payload_download"])) - - -print("==totals==") -print_totals(sclient.stats_get_totals()) - -print("==session totals==") -print_totals(sclient.stats_get_session_totals()) diff --git a/deluge/plugins/Stats/deluge/plugins/stats/test.py b/deluge/plugins/Stats/deluge/plugins/stats/tests/.test.py similarity index 100% rename from deluge/plugins/Stats/deluge/plugins/stats/test.py rename to deluge/plugins/Stats/deluge/plugins/stats/tests/.test.py diff --git a/deluge/plugins/Stats/deluge/plugins/stats/test.html b/deluge/plugins/Stats/deluge/plugins/stats/tests/test.html similarity index 100% rename from deluge/plugins/Stats/deluge/plugins/stats/test.html rename to deluge/plugins/Stats/deluge/plugins/stats/tests/test.html diff --git a/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py b/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py new file mode 100644 index 000000000..888a54338 --- /dev/null +++ b/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py @@ -0,0 +1,44 @@ +import twisted.internet.defer as defer +from twisted.trial import unittest + +import deluge.component as component +from deluge.common import fsize +from deluge.tests import common as tests_common +from deluge.ui.client import client + + +def print_totals(totals): + for name, value in totals.iteritems(): + print(name, fsize(value)) + + print("overhead:") + print("up:", fsize(totals["total_upload"] - totals["total_payload_upload"])) + print("down:", fsize(totals["total_download"] - totals["total_payload_download"])) + + +class StatsTestCase(unittest.TestCase): + + def setUp(self): # NOQA + defer.setDebugging(True) + tests_common.set_tmp_config_dir() + client.start_classic_mode() + client.core.enable_plugin("Stats") + + def tearDown(self): # NOQA + client.stop_classic_mode() + + def on_shutdown(result): + component._ComponentRegistry.components = {} + return component.shutdown().addCallback(on_shutdown) + + def test_client_totals(self): + def callback(args): + print_totals(args) + d = client.stats.get_totals() + d.addCallback(callback) + + def test_session_totals(self): + def callback(args): + print_totals(args) + d = client.stats.get_session_totals() + d.addCallback(callback) diff --git a/deluge/tests/test_client.py b/deluge/tests/test_client.py index 9d865bcfa..2d56bc95f 100644 --- a/deluge/tests/test_client.py +++ b/deluge/tests/test_client.py @@ -2,10 +2,11 @@ from twisted.internet import defer from twisted.internet.error import CannotListenError from twisted.trial import unittest -import deluge.tests.common as common from deluge import error from deluge.core.authmanager import AUTH_LEVEL_ADMIN -from deluge.ui.client import Client, client, DaemonSSLProxy +from deluge.ui.client import client, Client, DaemonSSLProxy + +from . import common class NoVersionSendingDaemonSSLProxy(DaemonSSLProxy): diff --git a/deluge/tests/test_config.py b/deluge/tests/test_config.py index 4707352fd..5fa4d159a 100644 --- a/deluge/tests/test_config.py +++ b/deluge/tests/test_config.py @@ -7,7 +7,8 @@ from twisted.trial import unittest import deluge.config from deluge.config import Config -from deluge.tests.common import set_tmp_config_dir + +from .common import set_tmp_config_dir DEFAULTS = {"string": "foobar", "int": 1, "float": 0.435, "bool": True, "unicode": u"foobar"} diff --git a/deluge/tests/test_core.py b/deluge/tests/test_core.py index 812238dec..2c22febf4 100644 --- a/deluge/tests/test_core.py +++ b/deluge/tests/test_core.py @@ -13,11 +13,12 @@ from twisted.web.static import File import deluge.component as component import deluge.error -import deluge.tests.common as common from deluge.core.core import Core from deluge.core.rpcserver import RPCServer from deluge.ui.web.common import compress +from . import common + warnings.filterwarnings("ignore", category=RuntimeWarning) warnings.resetwarnings() diff --git a/deluge/tests/test_httpdownloader.py b/deluge/tests/test_httpdownloader.py index 8bb0cd79b..e2e71b55b 100644 --- a/deluge/tests/test_httpdownloader.py +++ b/deluge/tests/test_httpdownloader.py @@ -1,3 +1,4 @@ +import tempfile import warnings from email.utils import formatdate @@ -8,11 +9,12 @@ from twisted.trial import unittest from twisted.web.http import NOT_MODIFIED from twisted.web.server import Site -import deluge.tests.common as common from deluge.httpdownloader import download_file from deluge.log import setup_logger from deluge.ui.web.common import compress +from . import common + try: from twisted.web.resource import Resource except ImportError: @@ -25,6 +27,11 @@ warnings.resetwarnings() rpath = common.rpath +temp_dir = tempfile.mkdtemp() + + +def fname(name): + return "%s/%s" % (temp_dir, name) class TestRedirectResource(Resource): @@ -132,13 +139,13 @@ class DownloadFileTestCase(unittest.TestCase): return filename def test_download(self): - d = download_file("http://localhost:%d/" % self.listen_port, "index.html") - d.addCallback(self.assertEqual, "index.html") + d = download_file("http://localhost:%d/" % self.listen_port, fname("index.html")) + d.addCallback(self.assertEqual, fname("index.html")) return d def test_download_without_required_cookies(self): url = "http://localhost:%d/cookie" % self.listen_port - d = download_file(url, "none") + d = download_file(url, fname("none")) d.addCallback(self.fail) d.addErrback(self.assertIsInstance, Failure) return d @@ -146,68 +153,68 @@ class DownloadFileTestCase(unittest.TestCase): def test_download_with_required_cookies(self): url = "http://localhost:%d/cookie" % self.listen_port cookie = {"cookie": "password=deluge"} - d = download_file(url, "monster", headers=cookie) - d.addCallback(self.assertEqual, "monster") + d = download_file(url, fname("monster"), headers=cookie) + d.addCallback(self.assertEqual, fname("monster")) d.addCallback(self.assertContains, "COOKIE MONSTER!") return d def test_download_with_rename(self): url = "http://localhost:%d/rename?filename=renamed" % self.listen_port - d = download_file(url, "original") - d.addCallback(self.assertEqual, "renamed") + d = download_file(url, fname("original")) + d.addCallback(self.assertEqual, fname("renamed")) d.addCallback(self.assertContains, "This file should be called renamed") return d def test_download_with_rename_exists(self): - open('renamed', 'w').close() + open(fname('renamed'), 'w').close() url = "http://localhost:%d/rename?filename=renamed" % self.listen_port - d = download_file(url, "original") - d.addCallback(self.assertEqual, "renamed-1") + d = download_file(url, fname("original")) + d.addCallback(self.assertEqual, fname("renamed-1")) d.addCallback(self.assertContains, "This file should be called renamed") return d def test_download_with_rename_sanitised(self): url = "http://localhost:%d/rename?filename=/etc/passwd" % self.listen_port - d = download_file(url, "original") - d.addCallback(self.assertEqual, "passwd") + d = download_file(url, fname("original")) + d.addCallback(self.assertEqual, fname("passwd")) d.addCallback(self.assertContains, "This file should be called /etc/passwd") return d def test_download_with_rename_prevented(self): url = "http://localhost:%d/rename?filename=spam" % self.listen_port - d = download_file(url, "forced", force_filename=True) - d.addCallback(self.assertEqual, "forced") + d = download_file(url, fname("forced"), force_filename=True) + d.addCallback(self.assertEqual, fname("forced")) d.addCallback(self.assertContains, "This file should be called spam") return d def test_download_with_gzip_encoding(self): url = "http://localhost:%d/gzip?msg=success" % self.listen_port - d = download_file(url, "gzip_encoded") + d = download_file(url, fname("gzip_encoded")) d.addCallback(self.assertContains, "success") return d def test_download_with_gzip_encoding_disabled(self): url = "http://localhost:%d/gzip?msg=fail" % self.listen_port - d = download_file(url, "gzip_encoded", allow_compression=False) + d = download_file(url, fname("gzip_encoded"), allow_compression=False) d.addCallback(self.failIfContains, "fail") return d def test_page_redirect(self): url = 'http://localhost:%d/redirect' % self.listen_port - d = download_file(url, "none") + d = download_file(url, fname("none")) d.addCallback(self.fail) d.addErrback(self.assertIsInstance, Failure) return d def test_page_not_found(self): - d = download_file("http://localhost:%d/page/not/found" % self.listen_port, "none") + d = download_file("http://localhost:%d/page/not/found" % self.listen_port, fname("none")) d.addCallback(self.fail) d.addErrback(self.assertIsInstance, Failure) return d def test_page_not_modified(self): headers = {'If-Modified-Since': formatdate(usegmt=True)} - d = download_file("http://localhost:%d/" % self.listen_port, "index.html", headers=headers) + d = download_file("http://localhost:%d/" % self.listen_port, fname("index.html"), headers=headers) d.addCallback(self.fail) d.addErrback(self.assertIsInstance, Failure) return d diff --git a/deluge/tests/test_torrent.py b/deluge/tests/test_torrent.py index 9ccdc33c9..53e0356a3 100644 --- a/deluge/tests/test_torrent.py +++ b/deluge/tests/test_torrent.py @@ -7,12 +7,13 @@ from twisted.trial import unittest import deluge.component as component import deluge.core.torrent -import deluge.tests.common as common from deluge._libtorrent import lt from deluge.core.core import Core from deluge.core.rpcserver import RPCServer from deluge.core.torrent import Torrent +from . import common + config_setup = False core = None rpcserver = None diff --git a/deluge/tests/test_tracker_icons.py b/deluge/tests/test_tracker_icons.py index 794dfa3fb..bed268bf3 100644 --- a/deluge/tests/test_tracker_icons.py +++ b/deluge/tests/test_tracker_icons.py @@ -3,9 +3,10 @@ import os from twisted.trial import unittest import deluge.ui.tracker_icons -from deluge.tests.common import set_tmp_config_dir from deluge.ui.tracker_icons import TrackerIcon, TrackerIcons +from .common import set_tmp_config_dir + set_tmp_config_dir() icons = TrackerIcons() diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py index 449d59eb7..254d3b465 100644 --- a/deluge/ui/web/json_api.py +++ b/deluge/ui/web/json_api.py @@ -29,7 +29,7 @@ from deluge import component, httpdownloader from deluge.common import is_magnet from deluge.configmanager import ConfigManager, get_config_dir from deluge.ui import common as uicommon -from deluge.ui.client import Client, client +from deluge.ui.client import client, Client from deluge.ui.coreconfig import CoreConfig from deluge.ui.sessionproxy import SessionProxy from deluge.ui.web.common import _, compress diff --git a/docs/source/conf.py b/docs/source/conf.py index 571a20f47..3af1381f2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,6 +14,7 @@ import os import sys from datetime import date +import mock import pkg_resources try: @@ -22,6 +23,7 @@ except ImportError: get_version = None on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_travis = os.environ.get('TRAVIS', None) == 'true' # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it @@ -55,7 +57,7 @@ MOCK_MODULES = ['deluge.ui.languages', 'deluge.ui.countries', 'deluge.ui.gtkui.g 'twisted.web', 'twisted.web.client', 'twisted.web.error', 'win32gui', 'win32api', 'win32con', '_winreg'] -if on_rtd: +if on_rtd or on_travis: MOCK_MODULES += ['libtorrent', 'pygtk', "gtk", "gobject", "gtk.gdk", "pango", "cairo", "pangocairo"] for mod_name in MOCK_MODULES: @@ -67,7 +69,7 @@ for mod_name in MOCK_MODULES: # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinxcontrib.napoleon'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinxcontrib.napoleon', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/setup.cfg b/setup.cfg index da7306d4e..0c3407922 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,10 +14,6 @@ includes = glib, gio, cairo, pango, pangocairo, atk, gobject, gtk.keysyms, zope.interface, mako.cache, email.mime, libtorrent, gtkosx_application frameworks = CoreFoundation, Foundation, AppKit -[flake8] -max-line-length = 120 -builtins = _,__request__ - [isort] known_standard_library=unicodedata line_length=120 diff --git a/setup.py b/setup.py index e0e1d53a5..a9dbb0d4d 100755 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ from distutils.command.build import build as _build from distutils.command.clean import clean as _clean from setuptools import find_packages, setup +from setuptools.command.test import test as TestCommand import msgfmt from version import get_version @@ -35,6 +36,23 @@ def windows_check(): desktop_data = 'deluge/ui/data/share/applications/deluge.desktop' +class PyTest(TestCommand): + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = [] + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + import pytest + errcode = pytest.main(self.test_args) + sys.exit(errcode) + + class BuildTranslations(cmd.Command): description = 'Compile .po files into .mo files & create .desktop file' @@ -269,7 +287,8 @@ cmdclass = { 'build_docs': BuildDocs, 'clean_plugins': CleanPlugins, 'clean': Clean, - 'egg_info_plugins': EggInfoPlugins + 'egg_info_plugins': EggInfoPlugins, + 'test': PyTest, } # Data files to be installed to the system @@ -333,6 +352,7 @@ setup( url="http://deluge-torrent.org", license="GPLv3", cmdclass=cmdclass, + tests_require=['pytest'], data_files=_data_files, package_data={"deluge": ["ui/gtkui/glade/*.glade", "ui/gtkui/glade/*.ui", diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..4643ddc21 --- /dev/null +++ b/tox.ini @@ -0,0 +1,141 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[flake8] +max-line-length = 120 +builtins = _,__request__ +exclude = .tox, .git, dist, build +ignore = E123,E133,E226,E241,E242 + +[tox] +envlist = py27, py26, flake8, isort, docs + +[testenv] +commands = {envpython} setup.py test +sitepackages=True +deps = + twisted + service_identity + mako + chardet + pyopenssl + pyxdg + pytest +whitelist_externals= + py.test +setenv = + PYTHONPATH = {env:PYTHONPATH}:{env:PWD} + +[pytest] +python_functions=test_ +norecursedirs=.tox .git dist build +pep8maxlinelength = 120 +whitelist_externals= + {[testenv]whitelist_externals} +commands= + py.test deluge + +[testenv:testcoverage] +install_command=pip install {opts} {packages} +deps = + {[testenv]deps} + pytest-cov + coverage +whitelist_externals= + {[testenv]whitelist_externals} + coverage +commands= + coverage run --branch --source=deluge -m py.test deluge/tests/ + coverage report + # For creating html report + # coverage html -d docs/build/htmlcoverage + +[testenv:pydef] +commands= + python -c "import libtorrent as lt; print lt.version" + py.test deluge/tests + +[testenv:plugins] +commands= + py.test deluge/plugins + +[testenv:py26] +basepython=python2.6 +commands= + {[testenv:pydef]commands} + +[testenv:py27] +basepython=python2.7 +commands= + {[testenv:pydef]commands} + +[testenv:isort] +deps = + {[testenv]deps} + isort +whitelist_externals= + {[testenv]whitelist_externals} + isort +commands= + python -c "import subprocess, sys; output = subprocess.check_output('isort --recursive --diff --stdout deluge docs/ *.py', shell=True); print output; sys.exit(len(output) != 0)" + +[testenv:flake8] +setenv = + {[testenv]setenv} +whitelist_externals= + {[testenv]whitelist_externals} + flake8 +deps = + {[testenv]deps} + flake8 + pep8-naming +commands= + flake8 deluge + +[testenv:flake8-complexity] +setenv = + {[testenv]setenv} +whitelist_externals= + {[testenv]whitelist_externals} + flake8 + sh +deps = + {[testenv:flake8]deps} + mccabe +commands= + sh -c "flake8 --max-complexity 10 deluge || true" + +[testenv:docscoverage] +changedir=docs +install_command=pip install {opts} {packages} +deps = + {[testenv]deps} + sphinx + sphinxcontrib-napoleon + coverage + pytest-cov +whitelist_externals= + {[testenv]whitelist_externals} + mkdir + sphinx-build +commands= + mkdir -p build/doccoverage + sphinx-build -W -b coverage -d build/doctrees source build/doccoverage + py.test --doctest-glob='*.rst' + +[testenv:docs] +changedir=docs +install_command=pip install {opts} --allow-external PIL --allow-unverified PIL {packages} +whitelist_externals= + {[testenv]whitelist_externals} + sphinx-build +deps = + {[testenv]deps} + sphinx + sphinxcontrib-napoleon + PIL +commands= + python -c "import sphinxcontrib.napoleon" + sphinx-build -E -W -b html -d build/doctrees source build/html