jenkins-bot has submitted this change and it was merged.
Change subject: Automatic tox testing
......................................................................
Automatic tox testing
- Do not run flake8-docstrings by default, as it does not pass
- unittest2 is needed for py26 env
- use PYWIKIBOT2_NO_USER_CONFIG=2 to prevent pwb warnings
when user-config.py is not found.
- Document testing using tox and CircleCI, including
workaround for CircleCI bug preventing use of py26.
Change-Id: I07694854f1f44a100e0222b464dd4a007acda01d
---
M generate_user_files.py
M pywikibot/config2.py
M tests/README.rst
M tox.ini
4 files changed, 54 insertions(+), 16 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/generate_user_files.py b/generate_user_files.py
index f29860a..5176b97 100755
--- a/generate_user_files.py
+++ b/generate_user_files.py
@@ -343,25 +343,27 @@
"""
global base_dir
- default_args = (config.family, config.mylang, None)
+ # set the config family and mylang values to an invalid state so that
+ # the script can detect that the command line arguments -family & -lang
+ # were used and and handle_args has updated these config values,
+ # and 'force' mode can be activated below.
+ (config.family, config.mylang) = ('wikipedia', None)
local_args = pywikibot.handle_args(args)
if local_args:
pywikibot.output('Unknown arguments: %s' % ' '.join(local_args))
return False
- username = config.usernames[config.family].get(config.mylang)
- args = (config.family, config.mylang, username)
-
- if args != default_args:
+ if config.mylang is not None:
force = True
pywikibot.output(u'Automatically generating user-config.py')
else:
force = False
+ # Force default site of en.wikipedia
+ (config.family, config.mylang) = ('wikipedia', 'en')
- # Force default
- if config.family == 'wikipedia' and config.mylang == 'language':
- args = ('wikipedia', 'en', username)
+ username = config.usernames[config.family].get(config.mylang)
+ args = (config.family, config.mylang, username)
while not force or config.verbose_output:
pywikibot.output(u'\nYour default user directory is "%s"' % base_dir)
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index b8898fd..774a818 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -1056,9 +1056,10 @@
# Fix up default site
-if family == 'wikipedia' and mylang == 'language' and _no_user_config != '2':
- print("WARNING: family and mylang are not set.\n"
- "Defaulting to family='test' and mylang='test'.")
+if family == 'wikipedia' and mylang == 'language':
+ if _no_user_config != '2':
+ print("WARNING: family and mylang are not set.\n"
+ "Defaulting to family='test' and mylang='test'.")
family = mylang = 'test'
# SECURITY WARNINGS
diff --git a/tests/README.rst b/tests/README.rst
index ed96b88..9c80c36 100644
--- a/tests/README.rst
+++ b/tests/README.rst
@@ -21,7 +21,7 @@
All tests
---------
-The entire suite of tests may be run in three ways from the root directory:
+The entire suite of tests may be run in the following ways from the root directory:
setup.py
~~~~~~~~
@@ -48,6 +48,13 @@
::
nosetests -v
+
+tox
+~~~
+
+::
+
+ tox
Specific tests
--------------
@@ -144,11 +151,34 @@
3. create a project in ci.appveyor.com
4. go to https://ci.appveyor.com/project/<username>/pywikibot-core/settings
and enter the custom configuration .yml filename: .appveyor.yml
-4. push changes into the forked git repository
-5. watch the build at https://ci.appveyor.com/<username>/pywikibot-core/history
+5. push changes into the forked git repository
+6. watch the build at https://ci.appveyor.com/<username>/pywikibot-core/history
The 'user' tests are not yet enabled on appveyor builds.
+CircleCI
+========
+
+After changes are published into a github repository, tests may be run on
+CircleCI Ubuntu servers.
+
+1. create a github and circleci account
+2. fork the main github repository
+3. create a project in circleci.com
+4. go to https://circleci.com/gh/<username>/pywikibot-core/edit#env-vars
+ and add the following variables:
+ PYWIKIBOT2_NO_USER_CONFIG=2
+ TOXENV=py27,py34
+5. push changes into the forked git repository
+6. watch the build at https://circleci.com/gh/<username>/pywikibot-core
+
+PYWIKIBOT2_NO_USER_CONFIG=2 is needed because 'python setup.py test' is run.
+
+TOXENV=py27,py34 is a workaround because CircleCI runs 'tox',
+but there is a bug in the CircleCI default 'py26' implementation.
+
+This approach does not include 'user' tests.
+
Environment variables
=====================
diff --git a/tox.ini b/tox.ini
index 6a052c3..e2acf3f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,17 +1,22 @@
[tox]
minversion = 1.6
skipsdist = True
-envlist = flake8,flake83,flake8-docstrings,py26,py27,py34
+envlist = flake8,flake8-py3,flake8-docstrings-mandatory,py26,py27,py34
[params]
nose_skip = --ignore-files=(gui\.py|botirc\.py|rcstream\.py)
[testenv]
-setenv = VIRTUAL_ENV={envdir}
+setenv =
+ VIRTUAL_ENV={envdir}
+ PYWIKIBOT2_NO_USER_CONFIG=2
usedevelop = True
commands = python setup.py test
install_command = pip install --process-dependency-links --pre {opts} {packages}
+[testenv:py26]
+deps = unittest2
+
[testenv:flake8]
commands = flake8 --ignore=D102,D103,E122,E127,E241,E402,E731 {posargs}
basepython = python2.7
--
To view, visit https://gerrit.wikimedia.org/r/233680
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I07694854f1f44a100e0222b464dd4a007acda01d
Gerrit-PatchSet: 11
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Hashar <hashar(a)free.fr>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: [IMPROV] UI: Always use color stack
......................................................................
[IMPROV] UI: Always use color stack
Previously only the colored Windows UI used a color stack because of the
transliteration. This is implementing the stack in general and each UI only
implements a different way to specify the color.
It is also removing the ambiguity between using the default color as the actual
default color and using the last color from the stack by adding a new color
`previous`.
Change-Id: Iecbfe7b633b3cb5dbe1772cbccb03641637a6612
---
M pywikibot/userinterfaces/terminal_interface_base.py
M pywikibot/userinterfaces/terminal_interface_unix.py
M pywikibot/userinterfaces/terminal_interface_win32.py
M tests/aspects.py
M tests/ui_tests.py
M tests/utils.py
6 files changed, 272 insertions(+), 89 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py
index 17bdd46..66487d5 100755
--- a/pywikibot/userinterfaces/terminal_interface_base.py
+++ b/pywikibot/userinterfaces/terminal_interface_base.py
@@ -46,10 +46,10 @@
'white',
]
-colorTagR = re.compile('\03{(?P<name>%s)}' % '|'.join(colors))
+colorTagR = re.compile('\03{(?P<name>%s|previous)}' % '|'.join(colors))
-class UI:
+class UI(object):
"""Base for terminal user interfaces."""
@@ -114,30 +114,55 @@
warnings_logger = logging.getLogger("py.warnings")
warnings_logger.addHandler(warning_handler)
- def printNonColorized(self, text, targetStream):
- """
- Write the text non colorized to the target stream.
+ def encounter_color(self, color, target_stream):
+ """Handle the next color encountered."""
+ raise NotImplementedError('The {0} class does not support '
+ 'colors.'.format(self.__class__.__name__))
- To each line which contains a color tag a ' ***' is added at the end.
- """
- lines = text.split('\n')
- for i, line in enumerate(lines):
- if i > 0:
- line = "\n" + line
- line, count = colorTagR.subn('', line)
- if count > 0:
- line += ' ***'
- if PY2:
- line = line.encode(self.encoding, 'replace')
- targetStream.write(line)
+ def _write(self, text, target_stream):
+ """Optionally encode and write the text to the target stream."""
+ if PY2:
+ text = text.encode(self.encoding, 'replace')
+ target_stream.write(text)
- printColorized = printNonColorized
+ def support_color(self, target_stream):
+ """Return whether the target stream does support colors."""
+ return False
- def _print(self, text, targetStream):
- if config.colorized_output:
- self.printColorized(text, targetStream)
- else:
- self.printNonColorized(text, targetStream)
+ def _print(self, text, target_stream):
+ """Write the text to the target stream handling the colors."""
+ colorized = config.colorized_output and self.support_color(target_stream)
+ colored_line = False
+ # Color tags might be cascaded, e.g. because of transliteration.
+ # Therefore we need this stack.
+ color_stack = ['default']
+ text_parts = colorTagR.split(text) + ['default']
+ for index, (text, next_color) in enumerate(zip(text_parts[::2],
+ text_parts[1::2])):
+ current_color = color_stack[-1]
+ if next_color == 'previous':
+ if len(color_stack) > 1: # keep the last element in the stack
+ color_stack.pop()
+ next_color = color_stack[-1]
+ else:
+ color_stack.append(next_color)
+
+ if current_color != next_color:
+ colored_line = True
+ if colored_line and not colorized:
+ if '\n' in text: # Normal end of line
+ text = text.replace('\n', ' ***\n', 1)
+ colored_line = False
+ elif index == len(text_parts) // 2 - 1: # Or end of text
+ text += ' ***'
+ colored_line = False
+
+ # print the text up to the tag.
+ self._write(text, target_stream)
+
+ if current_color != next_color and colorized:
+ # set the new color, but only if they change
+ self.encounter_color(color_stack[-1], target_stream)
def output(self, text, toStdout=False, targetStream=None):
"""
@@ -176,7 +201,7 @@
# transliteration was successful. The replacement
# could consist of multiple letters.
# mark the transliterated letters in yellow.
- transliteratedText += '\03{lightyellow}%s\03{default}' \
+ transliteratedText += '\03{lightyellow}%s\03{previous}' \
% transliterated
# memorize if we replaced a single letter by multiple
# letters.
diff --git a/pywikibot/userinterfaces/terminal_interface_unix.py b/pywikibot/userinterfaces/terminal_interface_unix.py
index 60d8cb2..b9b4ca3 100755
--- a/pywikibot/userinterfaces/terminal_interface_unix.py
+++ b/pywikibot/userinterfaces/terminal_interface_unix.py
@@ -37,18 +37,17 @@
"""User interface for unix terminals."""
- def printColorized(self, text, targetStream):
- """Print the text colorized using the Unix colors."""
- totalcount = 0
- for key, value in unixColors.items():
- ckey = '\03{%s}' % key
- totalcount += text.count(ckey)
- text = text.replace(ckey, value)
+ def support_color(self, target_stream):
+ """Return that the target stream supports colors."""
+ return True
- if totalcount > 0:
- # just to be sure, reset the color
- text += unixColors['default']
+ def encounter_color(self, color, target_stream):
+ """Write the unix color directly to the stream."""
+ self._write(unixColors[color], target_stream)
+ def _write(self, text, target_stream):
+ """Optionally encode and write the text to the target stream."""
+ targetStream = target_stream
if sys.version_info[0] == 2:
# .encoding does not mean we can write unicode
# to the stream pre-2.7.
diff --git a/pywikibot/userinterfaces/terminal_interface_win32.py b/pywikibot/userinterfaces/terminal_interface_win32.py
index 600a713..13cef1a 100755
--- a/pywikibot/userinterfaces/terminal_interface_win32.py
+++ b/pywikibot/userinterfaces/terminal_interface_win32.py
@@ -11,7 +11,6 @@
import re
-from pywikibot.tools import PY2
from pywikibot.userinterfaces import terminal_interface_base
try:
@@ -68,43 +67,14 @@
self.argv = argv
self.encoding = 'utf-8'
- def printColorized(self, text, targetStream):
- """Print the text colorized to the target stream."""
- std_out_handle = ctypes.windll.kernel32.GetStdHandle(-11)
- # Color tags might be cascaded, e.g. because of transliteration.
- # Therefore we need this stack.
- colorStack = []
- tagM = True
- while tagM:
- tagM = colorTagR.search(text)
- if tagM:
- # print the text up to the tag.
- text_before_tag = text[:tagM.start()]
- if PY2:
- text_before_tag = text_before_tag.encode(self.encoding, 'replace')
- targetStream.write(text_before_tag)
- newColor = tagM.group('name')
- if newColor == 'default':
- if len(colorStack) > 0:
- colorStack.pop()
- if len(colorStack) > 0:
- lastColor = colorStack[-1]
- else:
- lastColor = 'default'
- ctypes.windll.kernel32.SetConsoleTextAttribute(
- std_out_handle, windowsColors[lastColor])
- else:
- colorStack.append(newColor)
- # set the new color
- ctypes.windll.kernel32.SetConsoleTextAttribute(
- std_out_handle, windowsColors[newColor])
- text = text[tagM.end():]
- # print the rest of the text
- if PY2:
- text = text.encode(self.encoding, 'replace')
- targetStream.write(text)
- # just to be sure, reset the color
- ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors['default'])
+ def support_color(self, target_stream):
+ """Return whether the target stream supports actually color."""
+ return getattr(target_stream, '_hConsole', None) is not None
+
+ def encounter_color(self, color, target_stream):
+ """Set the new color."""
+ ctypes.windll.kernel32.SetConsoleTextAttribute(
+ target_stream._hConsole, windowsColors[color])
def _raw_input(self):
data = self.stdin.readline()
diff --git a/tests/aspects.py b/tests/aspects.py
index de3ca79..750ea38 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -741,7 +741,8 @@
if (('sites' not in dct and 'site' not in dct) or
('site' in dct and not dct['site'])):
# Prevent use of pywikibot.Site
- bases = tuple([DisableSiteMixin] + list(bases))
+ if all(not issubclass(base, DisableSiteMixin) for base in bases):
+ bases = tuple([DisableSiteMixin] + list(bases))
# 'pwb' tests will _usually_ require a site. To ensure the
# test class dependencies are declarative, this requires the
diff --git a/tests/ui_tests.py b/tests/ui_tests.py
index 46ac664..983fa3e 100644
--- a/tests/ui_tests.py
+++ b/tests/ui_tests.py
@@ -60,8 +60,13 @@
from pywikibot.bot import (
ui, DEBUG, VERBOSE, INFO, STDOUT, INPUT, WARNING, ERROR, CRITICAL
)
+from pywikibot.tools import PY2
+from pywikibot.userinterfaces import (
+ terminal_interface_win32, terminal_interface_base, terminal_interface_unix,
+)
-from tests.utils import unittest
+from tests.aspects import TestCase
+from tests.utils import unittest, FakeModule
if sys.version_info[0] > 2:
unicode = str
@@ -425,7 +430,7 @@
self.assertEqual(newstdout.getvalue(), '')
self.assertEqual(
newstderr.getvalue(),
- 'text \x1b[95mlight purple text\x1b[0m text\n\x1b[0m')
+ 'text \x1b[95mlight purple text\x1b[0m text\n')
def testOutputNoncolorizedText(self):
pywikibot.config.colorized_output = False
@@ -436,18 +441,8 @@
'text light purple text text ***\n')
str2 = ('normal text \03{lightpurple} light purple ' +
- '\03{lightblue} light blue \03{default} light purple ' +
+ '\03{lightblue} light blue \03{previous} light purple ' +
'\03{default} normal text')
-
- @unittest.expectedFailure
- def testOutputColorCascade(self):
- pywikibot.output(self.str2)
- self.assertEqual(newstdout.getvalue(), '')
- self.assertEqual(
- newstderr.getvalue(),
- 'normal text \x1b[35;1m light purple ' +
- '\x1b[94m light blue \x1b[35;1m light purple ' +
- '\x1b[0m normal text\n\x1b[0m')
def testOutputColorCascade_incorrect(self):
"""Test incorrect behavior of testOutputColorCascade."""
@@ -456,8 +451,8 @@
self.assertEqual(
newstderr.getvalue(),
'normal text \x1b[95m light purple ' +
- '\x1b[94m light blue \x1b[0m light purple ' +
- '\x1b[0m normal text\n\x1b[0m')
+ '\x1b[94m light blue \x1b[95m light purple ' +
+ '\x1b[0m normal text\n')
@unittest.skipUnless(os.name == 'posix', 'requires Unix console')
@@ -502,7 +497,7 @@
'abcd \x1b[93mA\x1b[0m\x1b[93mB\x1b[0m\x1b[93mG\x1b[0m'
'\x1b[93mD\x1b[0m \x1b[93ma\x1b[0m\x1b[93mb\x1b[0m\x1b[93mg'
'\x1b[0m\x1b[93md\x1b[0m \x1b[93ma\x1b[0m\x1b[93mi\x1b[0m'
- '\x1b[93mu\x1b[0m\x1b[93me\x1b[0m\x1b[93mo\x1b[0m\n\x1b[0m')
+ '\x1b[93mu\x1b[0m\x1b[93me\x1b[0m\x1b[93mo\x1b[0m\n')
@unittest.skipUnless(os.name == 'nt', 'requires Windows console')
@@ -679,6 +674,181 @@
self.assertEqual(lines, [u'Alpha', u'Bετα', u'Гамма', u'دلتا', u''])
+class FakeUITest(TestCase):
+
+ """Test case to allow doing uncolorized general UI tests."""
+
+ net = False
+
+ expected = 'Hello world you! ***'
+ expect_color = False
+ ui_class = terminal_interface_base.UI
+
+ def setUp(self):
+ """Create dummy instances for the test and patch encounter_color."""
+ super(FakeUITest, self).setUp()
+ if PY2:
+ self.stream = io.BytesIO()
+ else:
+ self.stream = io.StringIO()
+ self.ui_obj = self.ui_class()
+ self._orig_encounter_color = self.ui_obj.encounter_color
+ self.ui_obj.encounter_color = self._encounter_color
+ self._index = 0
+
+ def tearDown(self):
+ """Unpatch the encounter_color method."""
+ self.ui_obj.encounter_color = self._orig_encounter_color
+ super(FakeUITest, self).tearDown()
+ self.assertEqual(self._index,
+ len(self._colors) if self.expect_color else 0)
+
+ def _getvalue(self):
+ """Get the value of the stream and also decode it on Python 2."""
+ value = self.stream.getvalue()
+ if PY2:
+ value = value.decode(self.ui_obj.encoding)
+ return value
+
+ def _encounter_color(self, color, target_stream):
+ """Patched encounter_color method."""
+ assert False, 'This method should not be invoked'
+
+ def test_no_color(self):
+ """Test a string without any colors."""
+ self._colors = tuple()
+ self.ui_obj._print('Hello world you!', self.stream)
+ self.assertEqual(self._getvalue(), 'Hello world you!')
+
+ def test_one_color(self):
+ """Test a string using one color."""
+ self._colors = (('red', 6), ('default', 10))
+ self.ui_obj._print('Hello \03{red}world you!', self.stream)
+ self.assertEqual(self._getvalue(), self.expected)
+
+ def test_flat_color(self):
+ """Test using colors with defaulting in between."""
+ self._colors = (('red', 6), ('default', 6), ('yellow', 3), ('default', 1))
+ self.ui_obj._print('Hello \03{red}world \03{default}you\03{yellow}!',
+ self.stream)
+ self.assertEqual(self._getvalue(), self.expected)
+
+ def test_stack_with_pop_color(self):
+ """Test using stacked colors and just poping the latest color."""
+ self._colors = (('red', 6), ('yellow', 6), ('red', 3), ('default', 1))
+ self.ui_obj._print('Hello \03{red}world \03{yellow}you\03{previous}!',
+ self.stream)
+ self.assertEqual(self._getvalue(), self.expected)
+
+ def test_stack_implicit_color(self):
+ """Test using stacked colors without poping any."""
+ self._colors = (('red', 6), ('yellow', 6), ('default', 4))
+ self.ui_obj._print('Hello \03{red}world \03{yellow}you!', self.stream)
+ self.assertEqual(self._getvalue(), self.expected)
+
+ def test_one_color_newline(self):
+ """Test with trailing new line and one color."""
+ self._colors = (('red', 6), ('default', 11))
+ self.ui_obj._print('Hello \03{red}world you!\n', self.stream)
+ self.assertEqual(self._getvalue(), self.expected + '\n')
+
+
+class FakeUIColorizedTestBase(TestCase):
+
+ """Base class for test cases requiring that colorized output is active."""
+
+ expect_color = True
+
+ def setUp(self):
+ """Force colorized_output to True."""
+ super(FakeUIColorizedTestBase, self).setUp()
+ self._old_config = pywikibot.config2.colorized_output
+ pywikibot.config2.colorized_output = True
+
+ def tearDown(self):
+ """Undo colorized_output configuration."""
+ pywikibot.config2.colorized_output = self._old_config
+ super(FakeUIColorizedTestBase, self).tearDown()
+
+
+class FakeUnixTest(FakeUIColorizedTestBase, FakeUITest):
+
+ """Test case to allow doing colorized Unix tests in any environment."""
+
+ net = False
+
+ expected = 'Hello world you!'
+ ui_class = terminal_interface_unix.UnixUI
+
+ def _encounter_color(self, color, target_stream):
+ """Verify that the written data, color and stream are correct."""
+ self.assertIs(target_stream, self.stream)
+ expected_color = self._colors[self._index][0]
+ self._index += 1
+ self.assertEqual(color, expected_color)
+ self.assertEqual(len(self.stream.getvalue()),
+ sum(e[1] for e in self._colors[:self._index]))
+
+
+class FakeWin32Test(FakeUIColorizedTestBase, FakeUITest):
+
+ """
+ Test case to allow doing colorized Win32 tests in any environment.
+
+ This only patches the ctypes import in the terminal_interface_win32 module.
+ As the Win32CtypesUI is using the std-streams from another import these will
+ be unpatched.
+ """
+
+ net = False
+
+ expected = 'Hello world you!'
+ ui_class = terminal_interface_win32.Win32CtypesUI
+
+ def setUp(self):
+ """Patch the ctypes import and initialize a stream and UI instance."""
+ super(FakeWin32Test, self).setUp()
+ self._orig_ctypes = terminal_interface_win32.ctypes
+ ctypes = FakeModule.create_dotted('ctypes.windll.kernel32')
+ ctypes.windll.kernel32.SetConsoleTextAttribute = self._handle_setattr
+ terminal_interface_win32.ctypes = ctypes
+ self.stream._hConsole = object()
+
+ def tearDown(self):
+ """Unpatch the ctypes import and check that all colors were used."""
+ terminal_interface_win32.ctypes = self._orig_ctypes
+ super(FakeWin32Test, self).tearDown()
+
+ def _encounter_color(self, color, target_stream):
+ """Call the original method."""
+ self._orig_encounter_color(color, target_stream)
+
+ def _handle_setattr(self, handle, attribute):
+ """Dummy method to handle SetConsoleTextAttribute."""
+ self.assertIs(handle, self.stream._hConsole)
+ color = self._colors[self._index][0]
+ self._index += 1
+ color = terminal_interface_win32.windowsColors[color]
+ self.assertEqual(attribute, color)
+ self.assertEqual(len(self.stream.getvalue()),
+ sum(e[1] for e in self._colors[:self._index]))
+
+
+class FakeWin32UncolorizedTest(FakeWin32Test):
+
+ """Test case to allow doing uncolorized Win32 tests in any environment."""
+
+ net = False
+
+ expected = 'Hello world you! ***'
+ expect_color = False
+
+ def setUp(self):
+ """Change the local stream's console to None to disable colors."""
+ super(FakeWin32UncolorizedTest, self).setUp()
+ self.stream._hConsole = None
+
+
if __name__ == "__main__":
try:
try:
diff --git a/tests/utils.py b/tests/utils.py
index d7c3119..79a9244 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -19,6 +19,7 @@
import warnings
from collections import Mapping
+from types import ModuleType
from warnings import warn
if sys.version_info[0] > 2:
@@ -126,6 +127,23 @@
return False
+class FakeModule(ModuleType):
+
+ """An empty fake module."""
+
+ @classmethod
+ def create_dotted(cls, name):
+ """Create a chain of modules based on the name separated by periods."""
+ modules = name.split('.')
+ mod = None
+ for mod_name in modules[::-1]:
+ module = cls(str(mod_name))
+ if mod:
+ setattr(module, mod.__name__, mod)
+ mod = module
+ return mod
+
+
class WarningSourceSkipContextManager(warnings.catch_warnings):
"""
--
To view, visit https://gerrit.wikimedia.org/r/195100
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Iecbfe7b633b3cb5dbe1772cbccb03641637a6612
Gerrit-PatchSet: 10
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: Fix ComparableMixin
......................................................................
Fix ComparableMixin
The implementation in 6c6b185e failed for inequality,
resulting in 'Namespace 0 < int 0' returning True.
Change-Id: Ia00ab2dd68938b0520253f7017d685aa03c36592
---
M pywikibot/tools/__init__.py
M tests/namespace_tests.py
2 files changed, 25 insertions(+), 13 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index aced6c0..03fe23d 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -137,27 +137,27 @@
"""Mixin class to allow comparing to other objects which are comparable."""
def __lt__(self, other):
- """Compare if other is less than self."""
- return other >= self._cmpkey()
-
- def __le__(self, other):
- """Compare if other is less equals self."""
+ """Compare if self is less than other."""
return other > self._cmpkey()
+ def __le__(self, other):
+ """Compare if self is less equals other."""
+ return other >= self._cmpkey()
+
def __eq__(self, other):
- """Compare if other is equal to self."""
+ """Compare if self is equal to other."""
return other == self._cmpkey()
def __ge__(self, other):
- """Compare if other is greater equals self."""
- return other < self._cmpkey()
-
- def __gt__(self, other):
- """Compare if other is greater than self."""
+ """Compare if self is greater equals other."""
return other <= self._cmpkey()
+ def __gt__(self, other):
+ """Compare if self is greater than other."""
+ return other < self._cmpkey()
+
def __ne__(self, other):
- """Compare if other is not equal to self."""
+ """Compare if self is not equal to other."""
return other != self._cmpkey()
diff --git a/tests/namespace_tests.py b/tests/namespace_tests.py
index f2e2373..024ba7f 100644
--- a/tests/namespace_tests.py
+++ b/tests/namespace_tests.py
@@ -132,13 +132,17 @@
self.assertEqual(a, 0)
self.assertEqual(a, '')
+ self.assertFalse(a < 0)
+ self.assertFalse(a > 0)
self.assertNotEqual(a, None)
+
+ self.assertGreater(a, -1)
x = Namespace(id=6, custom_name=u'dummy', canonical_name=u'File',
aliases=[u'Image', u'Immagine'])
y = Namespace(id=6, custom_name=u'ملف', canonical_name=u'File',
aliases=[u'Image', u'Immagine'])
- z = Namespace(id=7, custom_name=u'dummy', canonical_name=u'File',
+ z = Namespace(id=7, custom_name=u'dummy 7', canonical_name=u'File',
aliases=[u'Image', u'Immagine'])
self.assertEqual(x, x)
@@ -157,11 +161,19 @@
self.assertEqual(x, u'image')
self.assertEqual(x, u'Image')
+ self.assertFalse(x < 6)
+ self.assertFalse(x > 6)
+
self.assertEqual(y, u'ملف')
self.assertLess(a, x)
+ self.assertLess(x, z)
+ self.assertLessEqual(a, x)
self.assertGreater(x, a)
+ self.assertGreater(x, 0)
self.assertGreater(z, x)
+ self.assertGreaterEqual(x, a)
+ self.assertGreaterEqual(y, x)
self.assertIn(6, [x, y, z])
self.assertNotIn(8, [x, y, z])
--
To view, visit https://gerrit.wikimedia.org/r/234521
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia00ab2dd68938b0520253f7017d685aa03c36592
Gerrit-PatchSet: 2
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>