Commit 1f145578 authored by Florian Fuchs's avatar Florian Fuchs

Merged ~barry/mailman.client/bilingual:

  * Added support for Python3.4
  * Use tox and vcrpy for testing
parents 05c9e859 53f8e95e
build
dist
mailman.client.egg-info
.tox
__pycache__
*.egg-info
include *.py
global-include *.txt
include *.py MANIFEST.in
global-include *.txt *.rst *.yaml
include Makefile
prune _build
......@@ -41,9 +41,8 @@ Project details
You may download the latest version of the package from the Python
`Cheese Shop`_ or from Launchpad_.
You can also install it via ``easy_install`` or ``pip``.
You can also install it via ``pip``.
% sudo easy_install mailman.client
% sudo pip install mailman.client
See the Launchpad project page for access to the Bazaar branch, bug report,
......
======================
Running the test suite
======================
The test suite is run with the `tox`_ tool, which allows it to be run against
multiple versions of Python. There are two modes to the test suite:
* *Record mode* which is used to record the HTTP traffic against a live
Mailman 3 REST server.
* *Replay mode* which allows you to run the test suite off-line, without
running the REST server.
Whenever you add tests for other parts of the REST API, you need to run the
suite once in record mode to generate the YAML file of HTTP requests and
responses.
Then you can run the test suite in replay mode as often as you want, and
Mailman 3 needn't even be installed on your system.
Since this branch ships with a recording file, you don't need to run in record
mode to start with.
Replay mode
===========
To run the test suite in replay mode (the default), just run the following::
$ tox
This will attempt to run the test suite against Python 2.6, 2.7, 3.2, 3.3, and
3.4, or whatever combination of those that are available on your system.
Record mode
===========
Start by branching the Mailman 3 code base, then you should install it into a
virtual environment. The easiest way to do this is with `tox`::
$ tox --notest -r
Now, use the virtual environment that `tox` creates to create a template `var`
directory in the current directory::
$ .tox/py34/bin/mailman info
Now you need to modify the ``var/etc/mailman.cfg`` configuration file, so that
it contains the following::
[devmode]
enabled: yes
recipient: you@yourdomain.com
[mta]
smtp_port: 9025
lmtp_port: 9024
incoming: mailman.testing.mta.FakeMTA
[webservice]
port: 9001
Now you can start Mailman 3::
$ .tox/py34/bin/mailman start
Back in your ``mailmanclient`` branch, run the test suite in record mode::
$ tox -e record
You should now have an updated recording file (``tape.yaml``).
If you find you need to re-run the test suite, you *must* first stop the
Mailman REST server, and then delete the ``mailman.db`` file, since it
contains state that will mess up the ``mailmanclient`` test suite::
$ cd <mailman3-branch>
$ .tox/py34/bin/mailman stop
$ rm -f var/data/mailman.db
$ .tox/py34/bin/mailman start
$ cd <mailmanclient-branch>
$ tox -e record
Once you're done recording the HTTP traffic, you can stop the Mailman 3 server
and you won't need it again. It's a good idea to commit the ``tape.yaml``
changes for other users of your branch.
.. _`tox`: https://testrun.org/tox/latest/
This diff is collapsed.
# Copyright (C) 2010 by the Free Software Foundation, Inc.
# Copyright (C) 2010-2015 by the Free Software Foundation, Inc.
#
# This file is part of mailman.client.
#
......@@ -15,34 +15,32 @@
# You should have received a copy of the GNU Lesser General Public License
# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
import distribute_setup
distribute_setup.use_setuptools()
from setup_helpers import (
description, find_doctests, get_version, long_description, require_python)
from setuptools import setup, find_packages
require_python(0x20600f0)
__version__ = get_version('src/mailmanclient/__init__.py')
setup(
name='mailmanclient',
version='1.0.0b1',
version=__version__,
packages=find_packages('src'),
package_dir = {'': 'src'},
include_package_data=True,
maintainer='Barry Warsaw',
maintainer_email='barry@list.org',
description=description('README.txt'),
description=description('README.rst'),
long_description=long_description(
'src/mailmanclient/README.txt',
'src/mailmanclient/NEWS.txt'),
'src/mailmanclient/README.rst',
'src/mailmanclient/NEWS.rst'),
license='LGPLv3',
url='http://launchpad.net/mailman.client',
download_url='https://launchpad.net/mailman.client/+download',
# Auto-conversion to Python 3.
use_2to3=True,
convert_2to3_doctests=find_doctests(),
install_requires=['httplib2', 'mock', 'WebTest', ],
)
install_requires=[
'httplib2',
'six',
],
)
# Copyright (C) 2009, 2010 by Barry A. Warsaw
# Copyright (C) 2009-2015 by the Free Software Foundation, Inc.
#
# This file is part of flufl.i18n
# This file is part of mailman.client
#
# flufl.i18n is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, version 3 of the License.
# mailman.client is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, version 3 of the License.
#
# flufl.i18n is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
# mailman.client is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with flufl.i18n. If not, see <http://www.gnu.org/licenses/>.
# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
"""setup.py helper functions."""
from __future__ import absolute_import, unicode_literals
from __future__ import print_function
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
......@@ -33,6 +32,7 @@ __all__ = [
import os
import re
import sys
import codecs
DEFAULT_VERSION_RE = re.compile(r'(?P<version>\d+\.\d(?:\.\d+)?)')
......@@ -131,9 +131,10 @@ def long_description(*filenames):
"""Provide a long description."""
res = []
for value in filenames:
if value.endswith('.txt'):
with open(value) as fp:
value = fp.read().decode('UTF-8')
base, ext = os.path.splitext(value)
if ext in ('.txt', '.rst'):
with codecs.open(value, 'r', encoding='utf-8') as fp:
value = fp.read()
res.append(value)
if not value.endswith(NL):
res.append('')
......@@ -142,6 +143,6 @@ def long_description(*filenames):
def description(filename):
"""Provide a short description."""
with open(filename) as fp:
with codecs.open(filename, 'r', encoding='utf-8') as fp:
for line in fp:
return line.strip()
# Copyright (C) 2010 by the Free Software Foundation, Inc.
# Copyright (C) 2010-2015 by the Free Software Foundation, Inc.
#
# This file is part of mailman.client.
#
......
......@@ -2,9 +2,12 @@
NEWS for mailman.client
=======================
1.0.0b1 (xxxx-xx-xx)
1.0.0b1 (2015-xx-xx)
====================
* Addedd an improved test harness using WebTest. Contributed by Aurélien Bompard.
* Port to Python 3.4.
* Run test suite with `tox`.
* Use vcrpy for HTTP testing
1.0.0a1 (2014-03-15)
......
......@@ -2,7 +2,7 @@
mailman.client - Python bindings for the Mailman 3 REST API
===========================================================
This package is called ``mailman.client``.
This package is called ``mailman.client``.
Requirements
......@@ -38,10 +38,9 @@ You can download the latest version of the package either from the Cheeseshop:
http://pypi.python.org/pypi/mailman.client
or from the Launchpad page above. Of course you can also just install it with
``pip`` or ``easy_install`` from the command line::
``pip`` from the command line::
% sudo pip mailman.client
% sudo easy_install mailman.client
You can grab the latest development copy of the code using Bazaar, from the
Launchpad home page above. See http://bazaar-vcs.org for details on the
......@@ -56,7 +55,7 @@ You may contact the author via barry@python.org.
Copyright
=========
Copyright (C) 2010 by The Free Software Foundation, Inc.
Copyright (C) 2010-2015 by The Free Software Foundation, Inc.
This file is part of mailman.client.
......
# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
# Copyright (C) 2010-2015 by the Free Software Foundation, Inc.
#
# This file is part of mailman.client.
#
......@@ -16,7 +16,7 @@
"""Package contents."""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
......
This diff is collapsed.
# Copyright (C) 2007-2015 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
"""Harness for testing Mailman's documentation.
Note that doctest extraction does not currently work for zip file
distributions. doctest discovery currently requires file system traversal.
"""
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
'setup',
'teardown'
]
from inspect import isfunction, ismethod
def stop():
"""Call into pdb.set_trace()"""
# Do the import here so that you get the wacky special hacked pdb instead
# of Python's normal pdb.
import pdb
pdb.set_trace()
def dump(results):
if results is None:
print(None)
return
for key in sorted(results):
if key == 'entries':
for i, entry in enumerate(results[key]):
# entry is a dictionary.
print('entry %d:' % i)
for entry_key in sorted(entry):
print(' {0}: {1}'.format(entry_key, entry[entry_key]))
else:
print('{0}: {1}'.format(key, results[key]))
def setup(testobj):
"""Test setup."""
# Make sure future statements in our doctests are the same as everywhere
# else.
testobj.globs['absolute_import'] = absolute_import
testobj.globs['print_function'] = print_function
testobj.globs['unicode_literals'] = unicode_literals
# In general, I don't like adding convenience functions, since I think
# doctests should do the imports themselves. It makes for better
# documentation that way. However, a few are really useful, or help to
# hide some icky test implementation details.
testobj.globs['stop'] = stop
testobj.globs['dump'] = dump
# Add this so that cleanups can be automatically added by the doctest.
testobj.globs['cleanups'] = []
def teardown(testobj):
for cleanup in testobj.globs['cleanups']:
if isfunction(cleanup) or ismethod(cleanup):
cleanup()
else:
cleanup[0](*cleanup[1:])
# Copyright (C) 2013-2015 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
"""nose2 test infrastructure."""
__all__ = [
'NosePlugin',
]
import os
import re
import vcr
import errno
import doctest
import mailmanclient
from contextlib2 import ExitStack
from mailmanclient.testing.documentation import setup, teardown
from nose2.events import Plugin
DOT = '.'
FLAGS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF
TOPDIR = os.path.dirname(mailmanclient.__file__)
class NosePlugin(Plugin):
configSection = 'mailman'
def __init__(self):
super(NosePlugin, self).__init__()
self.patterns = []
self.stderr = False
self.record = False
def set_stderr(ignore):
self.stderr = True
self.addArgument(self.patterns, 'P', 'pattern',
'Add a test matching pattern')
self.addFlag(set_stderr, 'E', 'stderr',
'Enable stderr logging to sub-runners')
def set_record(ignore):
self.record = True
self.addFlag(set_record, 'R', 'rerecord',
"""Force re-recording of test responses. Requires
Mailman to be running.""")
self._data_path = os.path.join(TOPDIR, 'tests', 'data', 'tape.yaml')
self._resources = ExitStack()
def startTestRun(self, event):
# Check to see if we're running the test suite in record mode. If so,
# delete any existing recording.
if self.record:
try:
os.remove(self._data_path)
except OSError as error:
if error.errno != errno.ENOENT:
raise
# This will automatically create the recording file.
self._resources.enter_context(vcr.use_cassette(self._data_path))
def stopTestRun(self, event):
# Stop all recording.
self._resources.close()
def getTestCaseNames(self, event):
if len(self.patterns) == 0:
# No filter patterns, so everything should be tested.
return
# Does the pattern match the fully qualified class name?
for pattern in self.patterns:
full_class_name = '{}.{}'.format(
event.testCase.__module__, event.testCase.__name__)
if re.search(pattern, full_class_name):
# Don't suppress this test class.
return
names = filter(event.isTestMethod, dir(event.testCase))
for name in names:
full_test_name = '{}.{}.{}'.format(
event.testCase.__module__,
event.testCase.__name__,
name)
for pattern in self.patterns:
if re.search(pattern, full_test_name):
break
else:
event.excludedNames.append(name)
def handleFile(self, event):
path = event.path[len(TOPDIR)+1:]
if len(self.patterns) > 0:
for pattern in self.patterns:
if re.search(pattern, path):
break
else:
# Skip this doctest.
return
base, ext = os.path.splitext(path)
if ext != '.rst':
return
test = doctest.DocFileTest(
path, package=mailmanclient,
optionflags=FLAGS,
setUp=setup,
tearDown=teardown)
# Suppress the extra "Doctest: ..." line.
test.shortDescription = lambda: None
event.extraTests.append(test)
## def startTest(self, event):
## import sys; print('vvvvv', event.test, file=sys.stderr)
## def stopTest(self, event):
## import sys; print('^^^^^', event.test, file=sys.stderr)
This diff is collapsed.
# Copyright (C) 2010-2014 by The Free Software Foundation, Inc.
#
# This file is part of mailman.client.
#
# mailman.client is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# mailman.client is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
"""Test harness for doctests."""
from __future__ import absolute_import, unicode_literals, print_function
__metaclass__ = type
__all__ = [
'additional_tests',
]
import os
import time
import atexit
import shutil
import doctest
import tempfile
import unittest
import subprocess
from textwrap import dedent
from mock import patch
# pylint: disable-msg=F0401
from pkg_resources import (
resource_filename, resource_exists, resource_listdir, cleanup_resources)
from mailman.config import config
from mailmanclient.tests.utils import FakeMailmanClient, inject_message
COMMASPACE = ', '
DOT = '.'
DOCTEST_FLAGS = (
doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_NDIFF)
def dump(results):
if results is None:
print(None)
return
for key in sorted(results):
if key == 'entries':
for i, entry in enumerate(results[key]):
# entry is a dictionary.
print('entry %d:' % i)
for entry_key in sorted(entry):
print(' {0}: {1}'.format(entry_key, entry[entry_key]))
else:
print('{0}: {1}'.format(key, results[key]))
def stop():
"""Call into pdb.set_trace()"""
# Do the import here so that you get the wacky special hacked pdb instead
# of Python's normal pdb.
import pdb
pdb.set_trace()
def setup(testobj):
testobj.globs['stop'] = stop
testobj.globs['dump'] = dump
testobj.globs['inject_message'] = inject_message
FakeMailmanClient.setUp()
# In unit tests, passwords aren't encrypted. Don't show this in the doctests
passlib_cfg = os.path.join(config.VAR_DIR, 'passlib.cfg')
with open(passlib_cfg, 'w') as fp:
print(dedent("""
[passlib]
schemes = sha512_crypt
"""), file=fp)
conf_hash_pw = dedent("""
[passwords]
configuration: {}
""".format(passlib_cfg))
config.push('conf_hash_pw', conf_hash_pw)
# Use the FakeMailmanClient
testobj.patcher = patch("mailmanclient.Client", FakeMailmanClient)
fmc = testobj.patcher.start()
def teardown(testobj):
"""Test teardown."""
testobj.patcher.stop()
config.pop('conf_hash_pw')
FakeMailmanClient.tearDown()
def additional_tests():
"Run the doc tests (README.txt and docs/*, if any exist)"
doctest_files = []
if resource_exists('mailmanclient', 'docs'):
for name in resource_listdir('mailmanclient', 'docs'):
if name.endswith('.txt'):
doctest_files.append(
os.path.abspath(
resource_filename('mailmanclient', 'docs/%s' % name)))
kwargs = dict(module_relative=False,
optionflags=DOCTEST_FLAGS,
setUp=setup, tearDown=teardown,
)
atexit.register(cleanup_resources)
return unittest.TestSuite((
doctest.DocFileSuite(*doctest_files, **kwargs)))
#!/usr/bin/env python
# Copyright (C) 2010-2013 by the Free Software Foundation, Inc.
# Copyright (C) 2015 by the Free Software Foundation, Inc.
#
# This file is part of mailman.client.
#
......@@ -15,21 +14,34 @@
# You should have received a copy of the GNU Lesser General Public License
# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
"""Test runner."""
"""Test domain corner cases."""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
'TestDomains',
]
import os
import sys
import unittest
from mailmanclient.tests.test_docs import additional_tests
if __name__ == '__main__':
suite = additional_tests()
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
from mailmanclient import Client
from six.moves.urllib_error import HTTPError
class TestDomains(unittest.TestCase):
def setUp(self):
self._client = Client(
'http://localhost:9001/3.0', 'restadmin', 'restpass')
def test_no_domain(self):
# Trying to get a non-existent domain returns a 404.
#
# We can't use `with self.assertRaises()` until we drop Python 2.6
try:
self._client.get_domain('example.org')
except HTTPError as error:
self.assertEqual(error.code, 404)
else:
raise AssertionError('Expected HTTPError 404')
# Copyright (C) 2010-2014 by The Free Software Foundation, Inc.
#
# This file is part of mailman.client.
#
# mailman.client is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# mailman.client is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
"""Test tools."""
from __future__ import absolute_import, unicode_literals, print_function
import os
from urllib2 import HTTPError
from urlparse import urljoin
from zope.component import getUtility
from webtest import TestApp
from mailman.config import config
from mailman.core.chains import process
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import specialized_message_from_string
from mailman.testing.layers import ConfigLayer
import mailmanclient
__metaclass__ = type
__all__ = [
"inject_message",
"FakeMailmanClient",