jenkins-bot has submitted this change and it was merged.
Change subject: Verify each script compiles and runs ......................................................................
Verify each script compiles and runs
Adds a set of tests covering compilation and execution of scripts where possible, with limited verification of the output of each script.
Invoke tests for all dependencies using: $ PYSETUP_TEST_EXTRAS=yes python setup.py test
Change-Id: I836b641c5e315abc0c8eebf130885294d26ffd01 --- M .travis.yml M setup.py A tests/script_tests.py 3 files changed, 280 insertions(+), 4 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/.travis.yml b/.travis.yml index dbd5764..91b1031 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ - '2.7' - '2.6'
+before_install: + - sudo apt-get update -qq + - sudo apt-get install -y python-imaging-tk liblua5.1-dev + install: - mkdir ~/.pywikibot
@@ -34,7 +38,7 @@ - secure: kofInMlisiTBt9o/Ustc/vySlkKfxGzGCX2LwA1D2waym8sDTS0o5gMJ5LsrT/BUKwZbe1vLozPHqZrrkQvsdTml+DpZuotzdILs0m0f3BUoexEC6OON5IDljuxFyETrD1Ug44ih5Mc4lVFOdTcBzg501ZmswGwQrBvg/OyEFfE= matrix: - LANGUAGE=en FAMILY=wikipedia - - LANGUAGE=ar FAMILY=wikipedia + - LANGUAGE=ar FAMILY=wikipedia PYSETUP_TEST_EXTRAS=1 - LANGUAGE=test FAMILY=wikidata
notifications: diff --git a/setup.py b/setup.py index 069dc46..6682b3f 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@
import sys import os +import itertools
from ez_setup import use_setuptools use_setuptools() @@ -22,11 +23,24 @@ test_deps = [] testcollector = "tests"
+dependencies = ['httplib2>=0.6.0'] + +extra_deps = {} + +script_deps = { + 'script_wui.py': ['irc', 'lunatic-python', 'crontab'], + # Note: None of the 'lunatic-python' repos on github support MS Windows. + 'flickrripper.py': ['PIL', 'flickrapi'], + # Note: 'PIL' is not available via pip2.7 on MS Windows, + # however it is available with setuptools. +} + if sys.version_info[0] == 2: if sys.version_info < (2, 6, 5): raise RuntimeError("ERROR: Pywikibot only runs under Python 2.6.5 or higher") elif sys.version_info[1] == 6: - test_deps = ['unittest2'] + test_deps.append('unittest2') + script_deps['replicate_wiki.py'] = ['argparse'] testcollector = "tests.utils.collector"
if sys.version_info[0] == 3: @@ -38,7 +52,6 @@ print("ERROR: Python 3.3 or higher is required!") sys.exit(1)
-dependencies = ['httplib2>=0.6.0'] if os.name != 'nt': # See bug 66010, Windows users will have issues # when trying to build the C modules. @@ -60,6 +73,13 @@ python = python.replace("pythonw.exe", "python.exe") # for Windows subprocess.call([python, "generate_user_files.py"])
+ +extra_deps.update(script_deps) +# Add script dependencies as test dependencies, +# so scripts can be compiled in test suite. +if 'PYSETUP_TEST_EXTRAS' in os.environ: + test_deps += list(itertools.chain(*(script_deps.values()))) + setup( name='pywikibot', version='2.0b1', @@ -73,8 +93,11 @@ for package in find_packages() if package.startswith('pywikibot.')], install_requires=dependencies, + extras_require=extra_deps, dependency_links=[ - 'https://git.wikimedia.org/zip/?r=pywikibot/externals/httplib2.git&format...' + 'https://git.wikimedia.org/zip/?r=pywikibot/externals/httplib2.git&format...', + 'git+https://github.com/AlereDevices/lunatic-python.git#egg=lunatic-python', + 'git+https://github.com/jayvdb/parse-crontab.git#egg=crontab', ], url='https://www.mediawiki.org/wiki/Pywikibot', download_url='https://github.com/wikimedia/pywikibot-core/archive/master.zip#egg=pywikibot...', diff --git a/tests/script_tests.py b/tests/script_tests.py new file mode 100644 index 0000000..523d882 --- /dev/null +++ b/tests/script_tests.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +"""Test that each script can be compiled and executed.""" +# +# (C) Pywikibot team, 2014 +# +# Distributed under the terms of the MIT license. +# +__version__ = '$Id$' + +import os +import sys +import time +import subprocess +import pywikibot +from tests.utils import unittest, PywikibotTestCase + + +base_path = os.path.split(os.path.split(__file__)[0])[0] +pwb_path = os.path.join(base_path, 'pwb.py') +scripts_path = os.path.join(base_path, 'scripts') + + +script_deps = { + 'script_wui.py': ['crontab', 'lua'], + # Note: package 'lunatic-python' provides module 'lua' + + 'flickrripper.py': ['PIL', 'flickrapi'], + # Note: 'PIL' is not available via pip2.7 on MS Windows, + # however it is available with setuptools. +} +if sys.version_info < (2, 7): + script_deps['replicate_wiki.py'] = ['argparse'] + + +def check_script_deps(script_name): + """Detect whether all dependencies are installed.""" + if script_name in script_deps: + for package_name in script_deps[script_name]: + try: + __import__(package_name) + except ImportError as e: + print('%s depends on %s, which isnt available:\n%s' + % (script_name, package_name, e)) + return False + return True + + +def runnable_script_list(scripts_path): + """List of scripts which may be executed.""" + dir_list = os.listdir(scripts_path) + script_list = [name[0:-3] for name in dir_list # strip .py + if name.endswith('.py') + and not name.startswith('_') # skip __init__.py and _* + and check_script_deps(name)] + return script_list + +script_input = { + 'catall': 'q\n', # q for quit + 'disambredir': '\n', # prompts for user to choose action to take + 'editarticle': 'Test page\n', + 'imagetransfer': 'Test page\n', + 'interwiki': 'Test page\n', + 'makecat': 'Test page\n\n', + # 'misspelling': 'q\n', # pressing 'q' doesnt work. bug 68663 + 'replace': 'foo\nbar\n\n\n', # match, replacement, + # Enter to begin, Enter for default summary. + 'shell': '\n', # exits on end of stdin + 'solve_disambiguation': 'Test page\nq\n', + 'upload': 'https://upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg%5C...', +} + +auto_run_script_list = [ + 'blockpageschecker', + 'blockreview', + 'casechecker', + 'catall', + 'category_redirect', + 'cfd', + 'clean_sandbox', + 'lonelypages', + 'makecat', + 'misspelling', + 'revertbot', + 'noreferences', + 'nowcommons', + 'script_wui', + 'shell', + 'solve_disambiguation', + 'unusedfiles', + 'upload', + 'welcome', +] + +# Expected result for no arguments +# Some of these are not pretty, but at least they are informative +# and not backtraces starting deep in the pywikibot package. +no_args_expected_results = { + 'add_text': 'NoEnoughData', + 'archivebot': 'NOTE: you must specify a template to run the bot', + 'commonscat': 'add_text.NoEnoughData', + 'create_categories': 'No pages to work on', + 'editarticle': 'Nothing changed', # This masks related bug 68645 but that + # bug is more broadly about config + # rather than editarticle. + 'freebasemappingupload': 'Cannot find ', + 'harvest_template': 'ERROR: Please specify', + 'illustrate_wikidata': 'I need a generator with pages to work on', + 'imageuncat': 'You have to specify the generator ', + 'interwiki': 'does not exist. Skipping.', # 'Test page' does not exist + 'login': 'Logged in on ', + 'replace': 'Press Enter to use this default message', + 'replicate_wiki': 'error: too few arguments', + 'script_wui': 'Pre-loading all relevant page contents', + 'shell': 'Welcome to the', + 'spamremove': 'No spam site specified', + 'transferbot': 'Target site not different from source site', # Bug 68662 + 'version': 'unicode test: ', + 'watchlist': 'Retrieving watchlist', + + # The following auto-run and typically cant be validated, + # however these strings are very likely to exist within + # the timeout of 5 seconds. + 'makecat': '(Default is [[', + 'revertbot': 'Fetching new batch of contributions', + 'upload': 'ERROR: Upload error', +} + + +def execute(command, data_in=None, timeout=0): + """Execute a command and capture outputs.""" + options = { + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE + } + if data_in is not None: + options['stdin'] = subprocess.PIPE + + p = subprocess.Popen(command, **options) + if data_in is not None: + p.stdin.write(data_in) + waited = 0 + while waited < timeout and p.poll() is None: + time.sleep(1) + waited += 1 + if timeout and p.poll() is None: + p.kill() + data_out = p.communicate() + return {'exit_code': p.returncode, + 'stdout': data_out[0], + 'stderr': data_out[1]} + + +class TestScriptMeta(type): + + """Test meta class.""" + + def __new__(cls, name, bases, dct): + """Create the new class.""" + def test_execution(script_name, args=None, expected_results=None): + def testScript(self): + cmd = [sys.executable, pwb_path, script_name] + + if args: + cmd += args + + data_in = script_input.get(script_name) + + timeout = 0 + if script_name in auto_run_script_list: + timeout = 5 + result = execute(cmd, data_in, timeout=timeout) + + if expected_results and script_name in expected_results: + if expected_results[script_name] is not None: + self.assertIn(expected_results[script_name], + result['stderr']) + elif (args and '-help' in args) or \ + script_name not in auto_run_script_list: + self.assertEqual(result['stderr'], '') + self.assertIn('Global arguments available for all', + result['stdout']) + self.assertEqual(result['exit_code'], 0) + self.assertNotIn('Traceback (most recent call last)', + result['stderr']) + return testScript + + for script_name in runnable_script_list(scripts_path): + test_name = 'test_' + script_name + '_execution' + dct[test_name] = test_execution(script_name, ['-help']) + if script_name in ['shell', 'version', + 'checkimages', # bug 68613 + 'data_ingestion', # bug 68611 + 'flickrripper', # bug 68606 (and others) + 'replicate_wiki', # bug 68664 + ]: + dct[test_name] = unittest.expectedFailure(dct[test_name]) + dct[test_name].__doc__ = 'Test running ' + script_name + '.' + + test_name = 'test_' + script_name + '_no_args' + dct[test_name] = test_execution(script_name, ['-simulate'], + no_args_expected_results) + if script_name in ['add_text', # raises custom NoEnoughData + 'checkimages', # bug 68613 + 'commonscat', # raises custom NoEnoughData + 'claimit', # bug 68657 - zero output + 'data_ingestion', # bug 68611 + 'disambredir', # quittable auto-run with + # highly variable output. + 'flickrripper', # bug 68606 (and deps) + 'imagerecat', # bug 68658 + 'imagetransfer', # bug 68659 + 'pagefromfile', # bug 68660 + 'script_wui', # bug 68797 + 'template', # bug 68661 - zero output + 'transferbot', # raises custom Exception + 'upload', # raises custom ValueError + ]: + dct[test_name] = unittest.expectedFailure(dct[test_name]) + dct[test_name].__doc__ = \ + 'Test running ' + script_name + ' without arguments.' + + return type.__new__(cls, name, bases, dct) + + +class TestScript(PywikibotTestCase): + + """Test cases for scripts.""" + + __metaclass__ = TestScriptMeta + + def setUp(self): + """Prepare the environment for running the pwb.py script.""" + self.old_pywikibot_dir = None + if 'PYWIKIBOT2_DIR' in os.environ: + self.old_pywikibot_dir = os.environ['PYWIKIBOT2_DIR'] + os.environ['PYWIKIBOT2_DIR'] = pywikibot.config.base_dir + + def tearDown(self): + """Restore the environment after running the pwb.py script.""" + del os.environ['PYWIKIBOT2_DIR'] + if self.old_pywikibot_dir: + os.environ['PYWIKIBOT2_DIR'] = self.old_pywikibot_dir + + +if __name__ == '__main__': + try: + unittest.main() + except SystemExit: + pass