John Vandenberg has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/291155

Change subject: Add PyPy builds
......................................................................

Add PyPy builds

- unicodedata2 and lunatic-python are not PyPy compatible

Change-Id: Ide8bcef49499d7f3bb7bcbf00bde3d42936e5672
---
M .travis.yml
M pywikibot/__init__.py
M requirements.txt
M setup.py
M tests/api_tests.py
M tests/bot_tests.py
M tests/i18n_tests.py
M tests/pwb_tests.py
M tests/script_tests.py
M tests/tools_tests.py
M tox.ini
11 files changed, 156 insertions(+), 13 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core 
refs/changes/55/291155/1

diff --git a/.travis.yml b/.travis.yml
index a0186ad..332e6a0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@
 sudo: false
 
 python:
+  - 'pypy'
   - '2.7'
   - '3.3'
   - '3.4'
@@ -30,10 +31,27 @@
   # Instead install requests in the before_script step below.
   - if [[ "$PYSETUP_TEST_EXTRAS" != '1' ]]; then rm requirements.txt ; fi
 
+  # PyPy `pwb` based tests fail under plain unittest, but work under pytest
+  - if [[ "$TRAVIS_PYTHON_VERSION" = "pypy" ]]; then USE_PYTEST=1; fi
+
   # When the env variable USE_NOSE or USE_PYTEST is set, the appropriate
   # tool is used, else PYTEST is taken as the default
-  - if [[ "$PYSETUP_TEST_EXTRAS" != '1' && "$USE_NOSE" != '1' && "$USE_PYTEST" 
!= '1' ]]; then
-      export USE_PYTEST=1 ;
+  - |
+    if [[ "$USE_NOSE" != '1' && "$USE_PYTEST" != '1' ]]; then
+      if [[ "$NO_NET" == '1' ]]; then
+        export USE_PYTEST=1
+      elif [[ "$PYSETUP_TEST_EXTRAS" != '1' ]]; then
+        export USE_PYTEST=1
+      fi
+    fi
+
+  - if [[ -z "$FAMILY" ]]; then FAMILY=test; fi
+  - if [[ -z "$LANGUAGE" ]]; then LANGUAGE=test; fi
+
+  - |
+    if [[ "$NO_NET" == '1' ]]; then
+      echo "Running no tests requiring network access"
+      echo "(however generate_family_file uses network)"
     fi
 
   - if [[ "$SITE_ONLY" == '1' ]]; then
@@ -48,12 +66,29 @@
       export PYWIKIBOT2_TEST_WRITE_FAIL=1 ;
     fi
 
+  # Set the default pypy version to be 2.6.1
+  - if [[ "$PYPY_VERSION" != '' ]]; then PYPY_VERSION=2.6.1; fi
+
+  - |  # Based on Vlad Frolov's MIT licensed 
https://github.com/travis-ci/travis-ci/issues/5027#issuecomment-170939193
+    if [[ "$TRAVIS_PYTHON_VERSION" = "pypy" && "$PYPY_VERSION" != 'default' 
]]; then
+      export PYENV_ROOT="$HOME/.pyenv"
+      if [[ -f "$PYENV_ROOT/bin/pyenv" ]]; then
+        pushd "$PYENV_ROOT" && git pull && popd
+      else
+        rm -rf "$PYENV_ROOT" && git clone --depth 1 
https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
+      fi
+      "$PYENV_ROOT/bin/pyenv" install --skip-existing "pypy-$PYPY_VERSION"
+      virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" 
"$HOME/virtualenvs/pypy-$PYPY_VERSION"
+      source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
+      python --version
+    fi
+
 before_script:
   - pip install -r dev-requirements.txt
 
 script:
   # Install security packages for requests to support HTTPS in site_detect
-  - if [[ "$PYSETUP_TEST_EXTRAS" != '1' ]]; then
+  - if [[ "$PYSETUP_TEST_EXTRAS" != '1' && "$NO_NET" != '1' ]]; then
       pip install mwoauth -r requests-requirements.txt ;
     fi
 
@@ -100,12 +135,17 @@
       nosetests --version ;
       if [[ "$SITE_ONLY" == "1" ]]; then
         python setup.py nosetests --tests tests --verbosity=2 -a 
"family=$FAMILY,code=$LANGUAGE" --with-trim --with-coverage --cover-package=. ;
+      elif [[ "$NO_NET" == "1" ]]; then
+        python setup.py nosetests --tests tests --verbosity=2 -a "!net" 
--with-trim --with-coverage --cover-package=. ;
       else
         python setup.py nosetests --tests tests --verbosity=2 --with-trim 
--with-coverage --cover-package=. ;
       fi ;
     elif [[ "$USE_PYTEST" == "1" ]]; then
+      py.test --version ;
       if [[ "$SITE_ONLY" == "1" ]]; then
         python setup.py pytest --addopts="-vvv -s --timeout=$TEST_TIMEOUT 
--cov=. -a \"family=='$FAMILY' and code=='$LANGUAGE'\"" ;
+      elif [[ "$NO_NET" == "1" ]]; then
+        python setup.py pytest --addopts="-vvv -s --timeout=$TEST_TIMEOUT 
--cov=. -a \"not net\"" ;
       else
         python setup.py pytest --addopts="-vvv -s --timeout=$TEST_TIMEOUT 
--cov=." ;
       fi
@@ -134,6 +174,14 @@
 
 matrix:
   include:
+    # Test the Ubuntu precise pypy (2.5) without network
+    - python: 'pypy'
+      env: PYPY_VERSION=default NO_NET=1 USE_PYTEST=1
+    - python: 'pypy'
+      env: PYPY_VERSION=default NO_NET=1 USE_NOSE=1
+    # Test the Ubuntu precise pypy (2.5) with network; fails with T136382
+    - python: 'pypy'
+      env: PYPY_VERSION=default
     - python: '2.7_with_system_site_packages'  # equivalent to virtualenv: 
system_site_packages: true
       env: LANGUAGE=he FAMILY=wikivoyage DIST=precise-sudo 
PYWIKIBOT2_TEST_NO_RC=1
       dist: precise
@@ -174,6 +222,16 @@
       env: LANGUAGE=ar FAMILY=wiktionary PYWIKIBOT2_TEST_NO_RC=1
     - python: '2.6'
       env: LANGUAGE=wikidata FAMILY=wikidata SITE_ONLY=1
+    # Test no-net flag
+    - python: '2.6'
+      env: NO_NET=1
+    - python: 'pypy'
+      env: PYPY_VERSION=5.0.1 PYSETUP_TEST_EXTRAS=1 NO_NET=1
+      # https://travis-ci.org/jayvdb/pywikibot-core/jobs/133171601 - strange 
errors on py.test
+    - python: 'pypy'
+      env: PYPY_VERSION=4.0.1 PYSETUP_TEST_EXTRAS=1
+    - python: 'pypy'
+      env: PYPY_VERSION=5.1.1 PYSETUP_TEST_EXTRAS=1
 
 notifications:
   email:
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index c80c9e1..d04bdd1 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -130,6 +130,12 @@
 deprecated = redirect_func(__deprecated)
 deprecate_arg = redirect_func(__deprecate_arg)
 
+try:
+    sys.pypy_version_info
+    PYPY = True
+except AttributeError:
+    PYPY = False
+
 
 class Timestamp(datetime.datetime):
 
@@ -155,6 +161,16 @@
     mediawikiTSFormat = "%Y%m%d%H%M%S"
     ISO8601Format = "%Y-%m-%dT%H:%M:%SZ"
 
+    if PYPY:
+        def replace(self, *args, **kwargs):
+            obj = super(Timestamp, self).replace(*args, **kwargs)
+            return self.__class__(
+                year=obj.year, month=obj.month, day=obj.day,
+                hour=obj.hour, minute=obj.minute, second=obj.second,
+                microsecond=obj.microsecond,
+                tzinfo=obj.tzinfo,
+            )
+
     def clone(self):
         """Clone this instance."""
         return self.replace(microsecond=self.microsecond)
diff --git a/requirements.txt b/requirements.txt
index 9b4400a..7ba4be6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -30,7 +30,7 @@
 
 ipaddr>=2.1.10 ; python_version < '3'
 
-unicodedata2>=7.0.0-2 ; python_version < '3'
+unicodedata2>=7.0.0-2 ; python_version < '3' and 
platform_python_implementation != 'PyPy'
 
 # OAuth support
 # mwoauth 0.2.4 is needed because it supports getting identity information
diff --git a/setup.py b/setup.py
index c880ec4..2fd8a02 100644
--- a/setup.py
+++ b/setup.py
@@ -25,6 +25,12 @@
 PY2 = (PYTHON_VERSION[0] == 2)
 PY26 = (PYTHON_VERSION < (2, 7))
 
+try:
+    sys.pypy_version_info
+    PYPY = True
+except AttributeError:
+    PYPY = False
+
 versions_required_message = """
 Pywikibot not available on:
 %s
@@ -72,6 +78,10 @@
     extra_deps.update({
         'csv': [csv_dep],
         'MySQL': ['oursql'],
+    })
+
+if PY2 and not PYPY:
+    extra_deps.update({
         'unicode7': ['unicodedata2>=7.0.0-2'],
     })
 
@@ -93,7 +103,7 @@
     'flickrapi>=1.4.5,<2' if PY26 else 'flickrapi')
 
 # lunatic-python is only available for Linux
-if sys.platform.startswith('linux'):
+if sys.platform.startswith('linux') and not PYPY:
     script_deps['script_wui.py'] = [irc_dep, 'lunatic-python', 'crontab']
 
 # The main pywin32 repository contains a Python 2 only setup.py with a small
@@ -187,6 +197,10 @@
 else:
     test_deps += ['six']
 
+# Before commencing unit tests, clear the last exception
+if PY2:
+    sys.exc_clear()
+
 from setuptools import setup, find_packages
 
 name = 'pywikibot'
diff --git a/tests/api_tests.py b/tests/api_tests.py
index 3e3e61a..b2d179f 100644
--- a/tests/api_tests.py
+++ b/tests/api_tests.py
@@ -624,7 +624,7 @@
         """Test setting the limit to not a number."""
         with self.assertRaisesRegex(
                 ValueError,
-                "invalid literal for int\(\) with base 10: 'test'"):
+                "invalid literal for int\(\) with base 10: u?'test'"):
             self.gen.set_maximum_items('test')
 
     def test_limit_equal_total(self):
diff --git a/tests/bot_tests.py b/tests/bot_tests.py
index c95b9d5..82f6e6f 100644
--- a/tests/bot_tests.py
+++ b/tests/bot_tests.py
@@ -83,6 +83,9 @@
         super(FakeSaveBotTestCase, self).setUp()
         self.assert_saves = getattr(self, 'default_assert_saves', 1)
         self.save_called = 0
+        # Ensure that exceptions outside the test do not break these tests
+        if PY2:
+            sys.exc_clear()
 
     def tearDown(self):
         """Tear down by asserting the counters."""
@@ -160,6 +163,7 @@
         """Get tests which are executed on exit."""
         def exit():
             exc = sys.exc_info()[0]
+            tb = sys.exc_info()[2]
             if exc is AssertionError:
                 # When an AssertionError happened we shouldn't do these
                 # assertions as they are invalid anyway and hide the actual
@@ -170,6 +174,10 @@
             if exception:
                 self.assertIs(exc, exception)
             else:
+                if exc:
+                    print('saw exception')
+                    import traceback
+                    print(traceback.format_tb(tb))
                 self.assertIsNone(exc)
                 self.assertRaises(StopIteration, next, self._page_iter)
         return exit
diff --git a/tests/i18n_tests.py b/tests/i18n_tests.py
index 22fd9b0..139c1b4 100644
--- a/tests/i18n_tests.py
+++ b/tests/i18n_tests.py
@@ -300,9 +300,9 @@
 
     def testMultipleNonNumbers(self):
         """Test error handling for multiple non-numbers."""
-        with self.assertRaisesRegex(ValueError, "invalid literal for int\(\) 
with base 10: 'drei'"):
+        with self.assertRaisesRegex(ValueError, "invalid literal for int\(\) 
with base 10: u?'drei'"):
             i18n.twntranslate('de', 'test-multiple-plurals', ["drei", "1", 1])
-        with self.assertRaisesRegex(ValueError, "invalid literal for int\(\) 
with base 10: 'elf'"):
+        with self.assertRaisesRegex(ValueError, "invalid literal for int\(\) 
with base 10: u?'elf'"):
             i18n.twntranslate('de', 'test-multiple-plurals',
                               {'action': u'Ändere', 'line': "elf", 'page': 2})
 
diff --git a/tests/pwb_tests.py b/tests/pwb_tests.py
index 94535f3..185cca5 100644
--- a/tests/pwb_tests.py
+++ b/tests/pwb_tests.py
@@ -21,6 +21,12 @@
 from tests.utils import execute, execute_pwb
 from tests.aspects import unittest, PwbTestCase
 
+try:
+    sys.pypy_version_info
+    PYPY = True
+except AttributeError:
+    PYPY = False
+
 join_pwb_tests_path = create_path_func(join_tests_path, 'pwb')
 
 
@@ -57,6 +63,8 @@
         Make sure the environment is not contaminated, and is the same as
         the environment we get when directly running a script.
         """
+        if PYPY:
+            raise unittest.SkipTest('Fails on PyPy, dumping secure env vars')
         self._do_check('print_env')
 
     def test_locals(self):
diff --git a/tests/script_tests.py b/tests/script_tests.py
index 5837e98..c2cd614 100644
--- a/tests/script_tests.py
+++ b/tests/script_tests.py
@@ -25,6 +25,12 @@
 
 archive_path = join_root_path('scripts', 'archive')
 
+try:
+    sys.pypy_version_info
+    PYPY = True
+except AttributeError:
+    PYPY = False
+
 if PY2:
     TK_IMPORT = 'Tkinter'
 else:
@@ -97,6 +103,10 @@
                                set(['login']) -
                                set(unrunnable_script_list)))
 
+if PYPY:
+    unrunnable_script_list = script_list
+
+
 script_input = {
     'catall': 'q\n',  # q for quit
     'editarticle': 'Test page\n',
diff --git a/tests/tools_tests.py b/tests/tools_tests.py
index 71f6b63..6b5e1e4 100644
--- a/tests/tools_tests.py
+++ b/tests/tools_tests.py
@@ -14,6 +14,7 @@
 import inspect
 import os.path
 import subprocess
+import sys
 import tempfile
 import warnings
 
@@ -24,6 +25,12 @@
     unittest, require_modules, DeprecationTestCase, TestCase, MetaTestCaseClass
 )
 from tests.utils import expected_failure_if, add_metaclass
+
+try:
+    sys.pypy_version_info
+    PYPY = True
+except AttributeError:
+    PYPY = False
 
 
 class ContextManagerWrapperTestCase(TestCase):
@@ -538,9 +545,9 @@
         deduper = tools.filter_unique(self.strs, container=deduped, key=hash)
         self._test_dedup_str(deduped, deduper, hash)
 
-    @expected_failure_if(not tools.PY2)
+    @expected_failure_if(not tools.PY2 or PYPY)
     def test_str_id(self):
-        """Test str using id as key fails on Python 3."""
+        """Test str using id as key fails on Python 3 and PyPy 2."""
         # str in Python 3 behave like objects.
         deduped = set()
         deduper = tools.filter_unique(self.strs, container=deduped, key=id)
diff --git a/tox.ini b/tox.ini
index eb77d65..fcf1718 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,13 +3,13 @@
 minversion = 1.7.2
 skipsdist = True
 skip_missing_interpreters = True
-envlist = flake8,pyflakes-{py26,py3,pypy},doctest-{py27,py34},py26,py27,py34
+envlist = 
flake8,pyflakes-{py26,py3,pypy},doctest-{py27,py34},py26,py27,py34,nose-pypy,pytest-pypy
 
 [tox:jenkins]
 # Override default for WM Jenkins
 # Others are run in their own individual jobs on WM Jenkins
 # Wikimedia Jenkins does not have Python 2.6
-envlist = flake8,pyflakes-{py3,pypy}
+envlist = flake8,pyflakes-{py3,pypy},nose-pypy,pytest-pypy
 
 [params]
 doctest_skip = --ignore-files=(gui\.py|botirc\.py|rcstream\.py)
@@ -79,12 +79,34 @@
 commands =
     python -W error::UserWarning -m generate_user_files -family:test 
-lang:test -v
     nosetests --version
-    nosetests --with-detecthttp -v -a '!net' tests
+    nosetests --with-detecthttp -v --attr '!net' tests
 deps =
     nose
     nose-detecthttp>=0.1.3
     six
 
+[testenv:nose-pypy]
+basepython = pypy
+commands =
+    python -W error::UserWarning -m generate_user_files -family:test 
-lang:test -v
+    nosetests --version
+    nosetests --with-detecthttp -v --eval-attr 'not net' tests
+deps =
+    nose
+    nose-detecthttp>=0.1.3
+    six
+
+[testenv:pytest-pypy]
+basepython = pypy
+commands =
+    python -W error::UserWarning -m generate_user_files -family:test 
-lang:test -v
+    pytest --version
+    pytest -vv -a 'not net'
+deps =
+    pytest
+    pytest-attrib
+    six
+
 [testenv:doctest]
 commands =
     python -W error::UserWarning -m generate_user_files -family:test 
-lang:test -v

-- 
To view, visit https://gerrit.wikimedia.org/r/291155
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ide8bcef49499d7f3bb7bcbf00bde3d42936e5672
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to