# -*- coding: utf-8  -*-
"""Test that each script can be compiled and executed."""
# (C) Pywikibot team, 2014
# Distributed under the terms of the MIT license.
from __future__ import absolute_import, print_function, unicode_literals
__version__ = '$Id: f9cb42246ac13ea68d5e3082fc44b6a588b0b3bc $'

import os
import sys

from pywikibot.tools import (

from tests import join_root_path
from tests.aspects import unittest, DefaultSiteTestCase, MetaTestCaseClass, PwbTestCase
from tests.utils import allowed_failure, execute_pwb, add_metaclass

scripts_path = join_root_path('scripts')

archive_path = join_root_path('scripts', 'archive')

if PY2:
    TK_IMPORT = 'Tkinter'
    TK_IMPORT = 'tkinter'

# These dependencies are not always the package name which is in setup.py.
# e.g. 'PIL.ImageTk' is a object provided by several different pypi packages,
# and setup.py requests that 'Pillow' is installed to provide 'PIL.ImageTk'.
# Here, it doesnt matter which pypi package was requested and installed.
# Here, the name given to the module which will be imported is required.
script_deps = {
    'imagecopy': [TK_IMPORT],
    'imagecopy_self': [TK_IMPORT],
    'script_wui': ['crontab', 'lua'],
    # Note: package 'lunatic-python' provides module 'lua'

    'flickrripper': ['flickrapi'],
    'imageharvest': ['BeautifulSoup'],
    'match_images': ['PIL.ImageTk'],
    'panoramiopicker': ['BeautifulSoup'],
    'states_redirect': ['pycountry'],
    'patrol': ['mwparserfromhell'],

if PYTHON_VERSION < (2, 7):
    script_deps['replicate_wiki'] = ['argparse']
    script_deps['editarticle'] = ['argparse']

if PY2:
    script_deps['data_ingestion'] = ['unicodecsv']

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]:
            except ImportError as e:
                print('%s depends on %s, which isnt available:\n%s'
                      % (script_name, package_name, e))
                return False
    return True

failed_dep_script_list = [name
                          for name in script_deps
                          if not check_script_deps(name)]

unrunnable_script_list = [
    'version',  # does not use global args
    'script_wui',   # depends on lua compiling

def list_scripts(path, exclude=None):
    """Return list of scripts in given path."""
    scripts = [name[0:-3] for name in os.listdir(path)  # strip '.py'
               if name.endswith('.py') and
               not name.startswith('_') and  # skip __init__.py and _*
               name != exclude]
    return scripts

script_list = (['login'] +
               list_scripts(scripts_path, 'login.py') +

runnable_script_list = (['login'] +
                        sorted(set(script_list) -
                               set(['login']) -

script_input = {
    'catall': 'q\n',  # q for quit
    'editarticle': 'Test page\n',
    'imageuncat': 'q\n',
    'imageharvest': 'https://upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg\n\n',
    'interwiki': 'Test page that should not exist\n',
    'misspelling': 'q\n',
    'pagefromfile': 'q\n',
    '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\n\n\n',

auto_run_script_list = [

# 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 = {
    # TODO: until done here, remember to set editor = None in user_config.py
    'editarticle': 'Nothing changed',
    'featured': '0 pages written.',
    'freebasemappingupload': 'Cannot find ',
    'harvest_template': 'ERROR: Please specify',
    'imageuncat': 'WARNING: This script is primarily written for Wikimedia Commons',
    # script_input['interwiki'] above lists a title that should not exist
    'interwiki': 'does not exist. Skipping.',
    'imageharvest': 'From what URL should I get the images',
    'login': 'Logged in on ',
    'pagefromfile': 'Please enter the file name',
    'panoramiopicker': 'Panoramiopicker is a tool to transfer Panaramio ',
    'replace': 'Press Enter to use this automatic message',
    'script_wui': 'Pre-loading all relevant page contents',
    'shell': ('>>> ', 'Welcome to the'),
    'transferbot': 'Target site not different from source site',
    'unusedfiles': ('Working on', None),
    '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.
    'revertbot': 'Fetching new batch of contributions',
    'upload': 'ERROR: Upload error',

if not PY2:
    no_args_expected_results['replicate_wiki'] = (
        'error: the following arguments are required: destination')
    no_args_expected_results['replicate_wiki'] = 'error: too few arguments'

enable_autorun_tests = (
    os.environ.get('PYWIKIBOT2_TEST_AUTORUN', '0') == '1')

def collector(loader=unittest.loader.defaultTestLoader):
    """Load the default tests."""
    # Note: Raising SkipTest during load_tests will
    # cause the loader to fallback to its own
    # discover() ordering of unit tests.

    if unrunnable_script_list:
        print('Skipping execution of unrunnable scripts:\n  %r'
              % unrunnable_script_list)

    if not enable_autorun_tests:
        print('Skipping execution of auto-run scripts '
              '(set PYWIKIBOT2_TEST_AUTORUN=1 to enable):\n  %r'
              % auto_run_script_list)

    tests = (['test__login'] +
             ['test_' + name
              for name in sorted(script_list)
              if name != 'login' and
              name not in unrunnable_script_list

    test_list = ['tests.script_tests.TestScriptHelp.' + name
                 for name in tests]

    tests = (['test__login'] +
             ['test_' + name
              for name in sorted(script_list)
              if name != 'login' and
              name not in failed_dep_script_list and
              name not in unrunnable_script_list and
              (enable_autorun_tests or name not in auto_run_script_list)

    test_list += ['tests.script_tests.TestScriptSimulate.' + name
                  for name in tests]

    tests = loader.loadTestsFromNames(test_list)
    suite = unittest.TestSuite()
    return suite

def load_tests(loader=unittest.loader.defaultTestLoader,
               tests=None, pattern=None):
    """Load the default modules."""
    return collector(loader)

class TestScriptMeta(MetaTestCaseClass):

    """Test meta class."""

    def __new__(cls, name, bases, dct):
        """Create the new class."""
        def test_execution(script_name, args=[]):
            is_autorun = '-help' not in args and script_name in auto_run_script_list

            def test_skip_script(self):
                raise unittest.SkipTest(
                    'Skipping execution of auto-run scripts (set '
                    'PYWIKIBOT2_TEST_AUTORUN=1 to enable) "{0}"'.format(script_name))

            def testScript(self):
                cmd = [script_name]

                if args:
                    cmd += args

                data_in = script_input.get(script_name)

                timeout = 0
                if is_autorun:
                    timeout = 5

                if self._results and script_name in self._results:
                    error = self._results[script_name]
                    if isinstance(error, StringTypes):
                        stdout = None
                        stdout, error = error
                    stdout = None
                    error = None

                test_overrides = {}
                if not hasattr(self, 'net') or not self.net:
                    test_overrides['pywikibot.Site'] = 'None'

                result = execute_pwb(cmd, data_in, timeout=timeout, error=error,

                stderr = result['stderr'].splitlines()
                stderr_sleep = [l for l in stderr
                                if l.startswith('Sleeping for ')]
                stderr_other = [l for l in stderr
                                if not l.startswith('Sleeping for ')]
                if stderr_sleep:

                if result['exit_code'] == -9:
                    print(' killed', end='  ')

                if error:
                    self.assertIn(error, result['stderr'])

                    exit_codes = [0, 1, 2, -9]
                elif not is_autorun:
                    if stderr_other == []:
                        stderr_other = None
                    if stderr_other is not None:
                        self.assertIn('Use -help for further information.',
                        self.assertNotIn('-help', args)
                        self.assertIn('Global arguments available for all',

                    exit_codes = [0]
                    # auto-run
                    exit_codes = [0, -9]

                    if (not result['stdout'] and not result['stderr']):
                        print(' auto-run script unresponsive after %d seconds'
                              % timeout, end=' ')
                    elif 'SIMULATION: edit action blocked' in result['stderr']:
                        print(' auto-run script simulated edit blocked',
                              end='  ')
                        print(' auto-run script stderr within %d seconds: %r'
                              % (timeout, result['stderr']), end='  ')

                self.assertNotIn('Traceback (most recent call last)',
                self.assertNotIn('deprecated', result['stderr'].lower())

                # If stdout doesnt include global help..
                if 'Global arguments available for all' not in result['stdout']:
                    # Specifically look for deprecated
                    self.assertNotIn('deprecated', result['stdout'].lower())
                    if result['stdout'] == '':
                        result['stdout'] = None
                    # But also complain if there is any stdout
                    if stdout is not None and result['stdout'] is not None:
                        self.assertIn(stdout, result['stdout'])

                self.assertIn(result['exit_code'], exit_codes)


            if not enable_autorun_tests and is_autorun:
                return test_skip_script
            return testScript

        argument = '-' + dct['_argument']

        for script_name in script_list:
            # force login to be the first, alphabetically, so the login
            # message does not unexpectedly occur during execution of
            # another script.
            # unrunnable script tests are disabled by default in load_tests()

            if script_name == 'login':
                test_name = 'test__login'
                test_name = 'test_' + script_name

            cls.add_method(dct, test_name,
                           test_execution(script_name, [argument]),
                           'Test running %s %s.' % (script_name, argument))

            if script_name in dct['_expected_failures']:
                dct[test_name] = unittest.expectedFailure(dct[test_name])
            elif script_name in dct['_allowed_failures']:
                dct[test_name] = allowed_failure(dct[test_name])

            # Disable test by default in nosetests
            if script_name in unrunnable_script_list:
                # flag them as an expectedFailure due to py.test (T135594)
                dct[test_name] = unittest.expectedFailure(dct[test_name])
                dct[test_name].__test__ = False

        return super(TestScriptMeta, cls).__new__(cls, name, bases, dct)

class TestScriptHelp(PwbTestCase):

    """Test cases for running scripts with -help.

    All scripts should not create a Site for -help, so net = False.

    __metaclass__ = TestScriptMeta

    net = False

    _expected_failures = failed_dep_script_list
    _allowed_failures = []

    _argument = 'help'
    _results = None

class TestScriptSimulate(DefaultSiteTestCase, PwbTestCase):

    """Test cases for scripts.

    This class sets the nose 'user' attribute on every test, thereby ensuring
    that the test runner has a username for the default site, and so that
    Site.login() is called in the test runner, which means that the scripts
    run in pwb can automatically login using the saved cookies.

    __metaclass__ = TestScriptMeta

    user = True

    _expected_failures = [
        'catall',          # stdout user interaction
        'flickrripper',    # Requires a flickr api key
        'upload',          # raises custom ValueError
    ] + failed_dep_script_list

    _allowed_failures = [
        # T94681
        # T77965
        # T94680: uses exit code 1

    _argument = 'simulate'
    _results = no_args_expected_results

if __name__ == '__main__':  # pragma: no cover
    except SystemExit: