diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..f31f86e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +source = src + +[report] +exclude_lines = + pragma: no cover + if __name__ == '__main__': + raise NotImplementedError diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9b58178 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: python +sudo: false +python: + - 2.7 + - 3.4 + - 3.5 + - 3.6 + - pypy-5.4.1 +script: +# Coverage slows this old pypy down to several minutes + - if [[ $TRAVIS_PYTHON_VERSION == pypy* ]]; then zope-testrunner --test-path=src; fi + - if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then coverage run -m zope.testrunner --test-path=src; fi + +after_success: + - coveralls +notifications: + email: false + +install: + - pip install -U pip setuptools + - pip install -U coveralls coverage + - pip install -U -e ".[test]" + + +cache: pip + +before_cache: + - rm -f $HOME/.cache/pip/log/debug.log diff --git a/CHANGES.txt b/CHANGES.rst similarity index 96% rename from CHANGES.txt rename to CHANGES.rst index 00be58d..ffe2512 100644 --- a/CHANGES.txt +++ b/CHANGES.rst @@ -1,9 +1,13 @@ Release History *************** -1.4.0 (unreleased) +2.0.0 (unreleased) ================== +- Add support for Python 3.4, 3.5, 3.6 and PyPy. + +- Automated testing is enabled on Travis CI. + 1.3.6 (2014-04-14) ================== diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7081d2f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include *.rst +include *.txt +include buildout.cfg +include .travis.yml +include tox.ini +include .coveragerc +include test_all_pythons.cfg +recursive-include src *.rst +recursive-include src *.txt diff --git a/README.txt b/README.rst similarity index 100% rename from README.txt rename to README.rst diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2a9acf1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index 83b0413..7974928 100644 --- a/setup.py +++ b/setup.py @@ -16,35 +16,46 @@ from setuptools import setup, find_packages def read(*rnames): - return open(os.path.join(os.path.dirname(__file__), *rnames)).read() + with open(os.path.join(os.path.dirname(__file__), *rnames)) as f: + return f.read() -name = "zc.recipe.cmmi" +name="zc.recipe.cmmi" setup( - name = name, - version='1.4dev', - author = "Jim Fulton", - author_email = "jim@zope.com", - description = "ZC Buildout recipe for configure/make/make install", - license = "ZPL 2.1", - keywords = "zc.buildout buildout recipe cmmi configure make install", - classifiers = [ + name=name, + version='2.0.dev0', + author="Jim Fulton", + author_email="jim@zope.com", + description="ZC Buildout recipe for configure/make/make install", + license="ZPL 2.1", + keywords="zc.buildout buildout recipe cmmi configure make install", + classifiers=[ "Environment :: Plugins", "Framework :: Buildout", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Topic :: Software Development :: Build Tools", "Topic :: System :: Software Distribution", - ], - url='http://pypi.python.org/pypi/'+name, + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Natural Language :: English', + 'Operating System :: OS Independent', + ], + url='http://github.com/zopefoundation/' + name, long_description=( - read('README.txt') + read('README.rst') + '\n' + - read('CHANGES.txt') + read('CHANGES.rst') + '\n' + 'Detailed Documentation\n' '**********************\n' + '\n' + - read('src', 'zc', 'recipe', 'cmmi', 'README.txt') + read('src', 'zc', 'recipe', 'cmmi', 'README.rst') + '\n' + 'Download Cache\n' '**************\n' @@ -53,16 +64,29 @@ def read(*rnames): + '\n' + 'Download\n' '**********************\n' - ), - - package_dir = {'':'src'}, - packages = find_packages('src'), - include_package_data = True, - data_files = [('.', ['README.txt'])], - namespace_packages = ['zc', 'zc.recipe'], - install_requires = ['zc.buildout >=1.4', 'setuptools'], - extras_require = dict(test=['zope.testing']), - entry_points = {'zc.buildout': - ['default = %s:Recipe' % name]}, - zip_safe = True, - ) + ), + package_dir={'':'src'}, + packages=find_packages('src'), + include_package_data=True, + namespace_packages=['zc', 'zc.recipe'], + install_requires=[ + 'zc.buildout >= 1.4', + 'setuptools'], + extras_require={ + 'test': [ + # sadly zc.buildout doesn't have a test extra, so we + # need to duplicate its test dependencies, since we import its + # test package. + 'zc.buildout[test]', + 'manuel', + 'zope.testing', + 'zope.testrunner', + ], + }, + entry_points={ + 'zc.buildout': [ + 'default = %s:Recipe' % name + ], + }, + zip_safe=True, +) diff --git a/src/zc/__init__.py b/src/zc/__init__.py index de40ea7..2cdb0e4 100644 --- a/src/zc/__init__.py +++ b/src/zc/__init__.py @@ -1 +1 @@ -__import__('pkg_resources').declare_namespace(__name__) +__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover diff --git a/src/zc/recipe/__init__.py b/src/zc/recipe/__init__.py index de40ea7..2cdb0e4 100644 --- a/src/zc/recipe/__init__.py +++ b/src/zc/recipe/__init__.py @@ -1 +1 @@ -__import__('pkg_resources').declare_namespace(__name__) +__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover diff --git a/src/zc/recipe/cmmi/README.txt b/src/zc/recipe/cmmi/README.rst similarity index 86% rename from src/zc/recipe/cmmi/README.txt rename to src/zc/recipe/cmmi/README.rst index a49b737..26831f9 100644 --- a/src/zc/recipe/cmmi/README.txt +++ b/src/zc/recipe/cmmi/README.rst @@ -25,7 +25,7 @@ We used the url option to specify the location of the archive. If we run the buildout, the configure script in the archive is run. It creates a make file which is also run: - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Installing foo. foo: Downloading http://localhost/foo.tgz foo: Unpacking and configuring @@ -44,7 +44,7 @@ The recipe also creates the parts directory: If we run the buildout again, the update method will be called, which does nothing: - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Updating foo. You can supply extra configure options: @@ -60,7 +60,7 @@ You can supply extra configure options: ... extra_options = -a -b c ... """ % distros_url) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost/foo.tgz @@ -75,17 +75,14 @@ The recipe sets the location option, which can be read by other recipes, to the location where the part is installed: >>> cat('.installed.cfg') - ... # doctest: +ELLIPSIS [buildout] - installed_develop_eggs = + installed_develop_eggs = parts = foo [foo] - __buildout_installed__ = /sample_buildout/parts/foo - ... - extra_options = -a -b c + ... location = /sample_buildout/parts/foo - ... + ... It may be necessary to set some environment variables when running configure or make. This can be done by adding an environment statement: @@ -103,7 +100,7 @@ or make. This can be done by adding an environment statement: ... """ % distros_url) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost/foo.tgz @@ -128,8 +125,8 @@ First of all let's write a patchfile: ... @@ -1,13 +1,13 @@ ... #!%s ... import sys - ... -print "configuring foo", ' '.join(sys.argv[1:]) - ... +print "configuring foo patched", ' '.join(sys.argv[1:]) + ... -print("configuring foo " + ' '.join(sys.argv[1:])) + ... +print("configuring foo patched " + ' '.join(sys.argv[1:])) ... ... Makefile_template = ''' ... all: @@ -141,7 +138,8 @@ First of all let's write a patchfile: ... +\techo installing foo patched ... ''' ... - ... open('Makefile', 'w').write(Makefile_template) + ... with open('Makefile', 'w') as f: + ... _ = f.write(Makefile_template) ... ... """ % sys.executable) @@ -160,12 +158,13 @@ passed, -p0 is appended by default. ... patch_options = -p0 ... """ % distros_url) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost/foo.tgz foo: Unpacking and configuring patching file configure + ... configuring foo patched --prefix=/sample_buildout/parts/foo echo building foo patched building foo patched @@ -185,7 +184,7 @@ It is possible to autogenerate the configure files: ... autogen = autogen.sh ... """ % distros_url) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost//bar.tgz @@ -213,7 +212,7 @@ It is also possible to support configure commands other than "./configure": ... --bindir=bin ... """ % distros_url) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost//baz.tgz @@ -228,9 +227,9 @@ When downloading a source archive or a patch, we can optionally make sure of its authenticity by supplying an MD5 checksum that must be matched. If it matches, we'll not be bothered with the check by buildout's output: - >>> try: from hashlib import md5 - ... except ImportError: from md5 import new as md5 - >>> foo_md5sum = md5(open(join(distros, 'foo.tgz')).read()).hexdigest() + >>> from hashlib import md5 + >>> with open(join(distros, 'foo.tgz'), 'rb') as f: + ... foo_md5sum = md5(f.read()).hexdigest() >>> write('buildout.cfg', ... """ @@ -243,7 +242,7 @@ matches, we'll not be bothered with the check by buildout's output: ... md5sum = %s ... """ % (distros_url, foo_md5sum)) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost/foo.tgz @@ -268,7 +267,7 @@ But if the archive doesn't match the checksum, the recipe refuses to install: ... patch = ${buildout:directory}/patches/config.patch ... """ % (distros_url, foo_md5sum)) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Uninstalling foo. Installing foo. foo: Downloading http://localhost:20617/bar.tgz @@ -291,7 +290,7 @@ aborted: ... patch-md5sum = %s ... """ % (distros_url, foo_md5sum)) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) Installing foo. foo: Downloading http://localhost:21669/foo.tgz foo: Unpacking and configuring @@ -316,7 +315,7 @@ is logged to stdout, and left intact for debugging purposes. >>> write('patches/config.patch', "dgdgdfgdfg") >>> res = system('bin/buildout') - >>> print res + >>> print(res) Installing foo. foo: Downloading http://localhost/foo.tgz foo: Unpacking and configuring @@ -329,7 +328,7 @@ is logged to stdout, and left intact for debugging purposes. An internal error occurred due to a bug in either zc.buildout or in a recipe being used: ... - SystemError: ('Failed', 'patch -p0 < /.../patches/config.patch') + CalledProcessError: Command 'patch -p0 < ...' returned non-zero exit status ... >>> import re @@ -345,8 +344,9 @@ After a successful build, such temporary directories are removed. >>> import glob >>> import tempfile - >>> tempdir = tempfile.gettempdir() - >>> dirs = len(glob.glob(os.path.join(tempdir, '*buildout-foo'))) + >>> old_tempdir = tempfile.gettempdir() + >>> tempdir = tempfile.tempdir = tempfile.mkdtemp(suffix='.buildout.build') + >>> dirs = glob.glob(os.path.join(tempdir, '*buildout-foo')) >>> write('buildout.cfg', ... """ @@ -358,7 +358,7 @@ After a successful build, such temporary directories are removed. ... url = %sfoo.tgz ... """ % (distros_url,)) - >>> print system("bin/buildout") + >>> print(system("bin/buildout")) Installing foo. foo: Downloading http://localhost:21445/foo.tgz foo: Unpacking and configuring @@ -369,6 +369,7 @@ After a successful build, such temporary directories are removed. installing foo - >>> new_dirs = len(glob.glob(os.path.join(tempdir, '*buildout-foo'))) - >>> dirs == new_dirs + >>> new_dirs = glob.glob(os.path.join(tempdir, '*buildout-foo')) + >>> len(dirs) == len(new_dirs) == 0 True + >>> tempfile.tempdir = old_tempdir diff --git a/src/zc/recipe/cmmi/__init__.py b/src/zc/recipe/cmmi/__init__.py index b863cde..fd5b6d6 100644 --- a/src/zc/recipe/cmmi/__init__.py +++ b/src/zc/recipe/cmmi/__init__.py @@ -12,26 +12,26 @@ # ############################################################################## -try: - from hashlib import sha1 -except ImportError: # Python < 2.5 - from sha import new as sha1 + +from hashlib import sha1 import logging import os import os.path import re +import subprocess import setuptools.archive_util import shutil import tempfile import zc.buildout import zc.buildout.download -almost_environment_setting = re.compile('\w+=').match -not_starting_with_digit = re.compile('\D').match +almost_environment_setting = re.compile(r'\w+=').match +not_starting_with_digit = re.compile(r'\D').match def system(c): - if os.system(c): - raise SystemError("Failed", c) + subprocess.check_call(c, shell=True) + # if os.system(c): + # raise SystemError("Failed", c) class Recipe(object): @@ -102,19 +102,24 @@ def __init__(self, buildout, name, options): def _state_hash(self): # hash of our configuration state, so that e.g. different - # ./configure options will get a different build directory + # ./configure options will get a different build directory. + # Be sure to sort to keep a consistent order, since dictionary iteration order + # is never guaranteed. + # XXX: This doesn't actually fix it. env = ''.join(['%s%s' % (key, value) for key, value - in self.environ.items()]) + in sorted(self.environ.items())]) state = [self.url, self.extra_options, self.autogen, self.patch, self.patch_options, env] - return sha1(''.join(state)).hexdigest() + data = ''.join(state) + if not isinstance(data, bytes): + data = data.encode('utf-8') + return sha1(data).hexdigest() def install(self): self.build() if self.shared: return '' - else: - return self.options['location'] + return self.options['location'] def update(self): if not os.path.isdir(self.options['location']): @@ -134,7 +139,7 @@ def build(self): fname, is_temp = download(self.url, md5sum=self.options.get('md5sum')) # now unpack and work as normal - tmp = tempfile.mkdtemp('buildout-'+self.name) + tmp = tempfile.mkdtemp('buildout-' + self.name) logger.info('Unpacking and configuring') try: setuptools.archive_util.unpack_archive(fname, tmp) @@ -143,7 +148,7 @@ def build(self): os.remove(fname) for key, value in sorted(self.environ.items()): - logger.info('Updating environment: %s=%s' % (key, value)) + logger.info('Updating environment: %s=%s', key, value) os.environ.update(self.environ) # XXX This is probably more complicated than it needs to be. I @@ -163,7 +168,7 @@ def build(self): if not (os.path.exists(self.source_directory_contains) or (self.autogen and os.path.exists(self.autogen))): entries = os.listdir(tmp) - if len(entries) == 1: + if len(entries) == 1 and os.path.isdir(entries[0]): os.chdir(entries[0]) if self.patch != '': # patch may be a filesystem path or url @@ -189,7 +194,7 @@ def build(self): system("./%s" % self.autogen) if not os.path.exists(self.source_directory_contains): entries = os.listdir(tmp) - if len(entries) == 1: + if len(entries) == 1 and os.path.isdir(entries[0]): os.chdir(entries[0]) else: raise ValueError("Couldn't find configure") diff --git a/src/zc/recipe/cmmi/downloadcache.txt b/src/zc/recipe/cmmi/downloadcache.rst similarity index 96% rename from src/zc/recipe/cmmi/downloadcache.txt rename to src/zc/recipe/cmmi/downloadcache.rst index 0e311a7..880f134 100644 --- a/src/zc/recipe/cmmi/downloadcache.txt +++ b/src/zc/recipe/cmmi/downloadcache.rst @@ -42,7 +42,7 @@ We used the url option to specify the location of the archive. It creates a make file which is also run: - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) In... ... Installing foo. @@ -82,7 +82,7 @@ files are not downloaded afresh: ... remove("parts", f) >>> remove_parts() - >>> print system(buildout) + >>> print(system(buildout)) In... ... Uninstalling foo. @@ -107,7 +107,7 @@ are also removed, then it is downloaded afresh: >>> remove_parts() - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) In... ... Installing foo. @@ -139,7 +139,7 @@ the new cache is created and repopulated: ... url = %s/foo.tgz ... """ % (cache2, distros)) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) In... ... Installing foo. @@ -188,7 +188,7 @@ been removed, the files from the cache are used: >>> remove_parts() - >>> print system(buildout) + >>> print(system(buildout)) In... ... Uninstalling foo. @@ -211,7 +211,7 @@ the cache, an error is raised because the file is not in the cache: >>> remove_parts() - >>> print system(buildout) + >>> print(system(buildout)) In... ... Uninstalling foo. @@ -222,4 +222,3 @@ the cache, an error is raised because the file is not in the cache: Installing foo. Error: Couldn't download 'http://localhost//foo.tgz' in offline mode. - diff --git a/src/zc/recipe/cmmi/misc.txt b/src/zc/recipe/cmmi/misc.rst similarity index 93% rename from src/zc/recipe/cmmi/misc.txt rename to src/zc/recipe/cmmi/misc.rst index a646275..3be1412 100644 --- a/src/zc/recipe/cmmi/misc.txt +++ b/src/zc/recipe/cmmi/misc.rst @@ -9,7 +9,7 @@ Creating the location folder When the recipe is subclassed, the `location` folder might be created before `zc.recipe.cmmi` has a chance to create it, so we need to make sure it checks that the folder does not exists before it is created. - + In the test below, the `foo` folder is created before the recipe is launched:: @@ -27,7 +27,7 @@ is launched:: ... url = file://%s/foo.tgz ... """ % (distros)) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing... ... installing foo @@ -50,7 +50,7 @@ The part's directory is created when the part is installed: >>> remove('.installed.cfg') >>> rmdir('parts', 'foo') - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing... ... installing foo @@ -64,10 +64,10 @@ part now uses a shared build or because the part is gone altogether): >>> write('buildout.cfg', ... """ ... [buildout] - ... parts = + ... parts = ... """) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Uninstalling foo. >>> os.path.isdir(join(sample_buildout, "parts", "foo")) @@ -95,7 +95,7 @@ aren't of the form NAME=..... ... CFLAGS=-I/yyy -I/xxx --x=y 2=1+1 a=b ... """ % distros_url) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout')) Installing foo. foo: Downloading http://localhost/foo.tgz foo: Unpacking and configuring diff --git a/src/zc/recipe/cmmi/patching.txt b/src/zc/recipe/cmmi/patching.rst similarity index 88% rename from src/zc/recipe/cmmi/patching.txt rename to src/zc/recipe/cmmi/patching.rst index 9929007..1190c6b 100644 --- a/src/zc/recipe/cmmi/patching.txt +++ b/src/zc/recipe/cmmi/patching.rst @@ -3,12 +3,12 @@ Loading Patces from URLs Patch files can be loaded from URLs as well as files. -Any downloaded patch files can be cached in a download cache if -available, in exactly the same way as for tarballs. Similarly, -if the build is set to offline operation, then it will not download +Any downloaded patch files can be cached in a download cache if +available, in exactly the same way as for tarballs. Similarly, +if the build is set to offline operation, then it will not download from a remote location. -To see how this works, we'll set up a web server with a patch file, +To see how this works, we'll set up a web server with a patch file, and a cache with our tarball in: >>> import sys, os @@ -21,8 +21,8 @@ and a cache with our tarball in: ... @@ -1,13 +1,13 @@ ... #!%s ... import sys - ... -print "configuring foo", ' '.join(sys.argv[1:]) - ... +print "configuring foo patched", ' '.join(sys.argv[1:]) + ... -print("configuring foo " + ' '.join(sys.argv[1:])) + ... +print("configuring foo patched " + ' '.join(sys.argv[1:])) ... ... Makefile_template = ''' ... all: @@ -55,7 +55,7 @@ Now let's create a buildout.cfg file. ... patch = %(server_url)s/config.patch ... """ % dict(distros=distros,server_url=server_url,cache=cache)) - >>> print system('bin/buildout'), + >>> print(system('bin/buildout').strip()) In... Installing foo. foo: Searching cache at /cache/cmmi @@ -66,6 +66,7 @@ Now let's create a buildout.cfg file. foo: Cache miss; will cache http://localhost//config.patch as /cache/cmmi/... foo: Downloading http://localhost//config.patch patching file configure + ... configuring foo patched /sample-buildout/parts/foo echo building foo patched building foo patched @@ -83,4 +84,3 @@ We can see that the patch is now in the cache, as well as the tarball: >>> ls(cache_path) - ... - ... - diff --git a/src/zc/recipe/cmmi/shared.txt b/src/zc/recipe/cmmi/shared.rst similarity index 87% rename from src/zc/recipe/cmmi/shared.txt rename to src/zc/recipe/cmmi/shared.rst index f11e43f..3b9c87e 100644 --- a/src/zc/recipe/cmmi/shared.txt +++ b/src/zc/recipe/cmmi/shared.rst @@ -20,7 +20,7 @@ several buildouts. To do this, use the `shared` option: When run the first time, the build is executed as usual: - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. foo: Unpacking and configuring configuring foo /cache/cmmi/build/ @@ -34,7 +34,7 @@ But after that, the existing shared build directory is used instead of running the build again: >>> remove('.installed.cfg') - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. foo: using existing shared build @@ -53,8 +53,8 @@ For example, if the download url changes, the build is executed again: >>> import os >>> import shutil - >>> shutil.copy(os.path.join(distros, 'foo.tgz'), - ... os.path.join(distros, 'qux.tgz')) + >>> _ = shutil.copy(os.path.join(distros, 'foo.tgz'), + ... os.path.join(distros, 'qux.tgz')) >>> remove('.installed.cfg') >>> write('buildout.cfg', @@ -68,7 +68,7 @@ For example, if the download url changes, the build is executed again: ... url = file://%s/qux.tgz ... shared = True ... """ % (cache, distros)) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing qux. qux: Unpacking and configuring configuring foo /cache/cmmi/build/ @@ -107,7 +107,7 @@ directory directly (instead of a name computed from to the recipe options): ... """ % (distros, shared)) >>> remove('.installed.cfg') - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. foo: Unpacking and configuring configuring foo /cache/existing/cmmi @@ -130,13 +130,13 @@ If no download-cache is set, and `shared` is not a directory, an error is raised ... shared = True ... """ % distros) - >>> print system('bin/buildout') + >>> print(system('bin/buildout').strip()) While: Installing. Getting section foo. - Initializing part foo. + Initializing section foo. ... - ValueError: Set the 'shared' option of zc.recipe.cmmi to an existing + ValueError: Set the 'shared' option of zc.recipe.cmmi to an existing directory, or set ${buildout:download-cache} @@ -149,21 +149,21 @@ mistaking some half-baked build directory as a good cached shared build. Let's simulate a build error. First, we backup a working build. - >>> shutil.copy(os.path.join(distros, 'foo.tgz'), - ... os.path.join(distros, 'foo.tgz.bak')) + >>> _ = shutil.copy(os.path.join(distros, 'foo.tgz'), + ... os.path.join(distros, 'foo.tgz.bak')) Then we create a broken tarball: >>> import tarfile - >>> import StringIO + >>> from zc.recipe.cmmi.tests import BytesIO >>> import sys >>> tarpath = os.path.join(distros, 'foo.tgz') - >>> tar = tarfile.open(tarpath, 'w:gz') - >>> configure = 'invalid' - >>> info = tarfile.TarInfo('configure') - >>> info.size = len(configure) - >>> info.mode = 0755 - >>> tar.addfile(info, StringIO.StringIO(configure)) + >>> with tarfile.open(tarpath, 'w:gz') as tar: + ... configure = 'invalid' + ... info = tarfile.TarInfo('configure.off') + ... info.size = len(configure) + ... info.mode = 0o755 + ... tar.addfile(info, BytesIO(configure)) Now we reset the cache to force our broken tarball to be used: @@ -183,7 +183,7 @@ Now we reset the cache to force our broken tarball to be used: >>> remove('.installed.cfg') >>> res = system('bin/buildout') - >>> print res + >>> print(res) Installing foo. ... ValueError: Couldn't find configure @@ -198,8 +198,8 @@ When we now fix the error (by copying back the working version and resetting the cache), the build will be run again, and we don't use a half-baked shared directory: - >>> shutil.copy(os.path.join(distros, 'foo.tgz.bak'), - ... os.path.join(distros, 'foo.tgz')) + >>> _ = shutil.copy(os.path.join(distros, 'foo.tgz.bak'), + ... os.path.join(distros, 'foo.tgz')) >>> shutil.rmtree(cache) >>> cache = tmpdir('cache') >>> write('buildout.cfg', @@ -213,7 +213,7 @@ directory: ... url = file://%s/foo.tgz ... shared = True ... """ % (cache, distros)) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. foo: Unpacking and configuring configuring foo /cache/cmmi/build/ @@ -249,7 +249,7 @@ If someone deletes this shared build, updating the buildout part that needs it will cause it to be rebuilt: >>> rmdir(cache, 'cmmi', 'build') - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Updating foo. foo: Unpacking and configuring configuring foo /cache/cmmi/build/ @@ -274,7 +274,7 @@ If we stop using the shared build, it stays in the build cache: ... url = file://%s/foo.tgz ... """ % (cache, distros)) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Uninstalling foo. Installing foo. foo: Unpacking and configuring @@ -309,7 +309,7 @@ from the story so far: ... shared = True ... """ % (cache, distros)) - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. foo: Unpacking and configuring configuring foo /cache/cmmi/build/ @@ -323,12 +323,12 @@ part wouldn't keep track of the shared build and thus wasn't able to restore it if it got deleted from the cache. This is how it should work: >>> remove('.installed.cfg') - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. foo: using existing shared build >>> rmdir(cache, 'cmmi', 'build') - >>> print system('bin/buildout') + >>> print(system('bin/buildout').strip()) Updating foo. foo: Unpacking and configuring configuring foo /cache/cmmi/build/ @@ -363,7 +363,7 @@ We cause the download to fail by specifying a nonsensical MD5 sum: ... """ % (cache, distros)) >>> remove('.installed.cfg') - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. ... Error: MD5 checksum mismatch for local resource at '/distros/foo.tgz'. @@ -374,7 +374,7 @@ The build directory must not exist anymore: Another buildout run must fail the same way as the first attempt: - >>> print system('bin/buildout') + >>> print(system('bin/buildout')) Installing foo. ... Error: MD5 checksum mismatch for local resource at '/distros/foo.tgz'. diff --git a/src/zc/recipe/cmmi/tests.py b/src/zc/recipe/cmmi/tests.py index 13ad1aa..128d766 100644 --- a/src/zc/recipe/cmmi/tests.py +++ b/src/zc/recipe/cmmi/tests.py @@ -12,7 +12,11 @@ # ############################################################################## -import os, re, StringIO, sys, tarfile +import os +import re +from io import BytesIO as _BytesIO +import sys +import tarfile import zc.buildout.testing import unittest @@ -21,49 +25,56 @@ from zc.buildout.tests import easy_install_SetUp from zc.buildout.tests import normalize_bang +def _as_bytes(s): + return s.encode('utf-8') if not isinstance(s, bytes) else s + +def BytesIO(s): + return _BytesIO(_as_bytes(s)) + def setUp(test): zc.buildout.testing.buildoutSetUp(test) zc.buildout.testing.install_develop('zc.recipe.cmmi', test) distros = test.globs['distros'] = test.globs['tmpdir']('distros') tarpath = os.path.join(distros, 'foo.tgz') - tar = tarfile.open(tarpath, 'w:gz') - configure = configure_template % sys.executable - info = tarfile.TarInfo('configure') - info.size = len(configure) - info.mode = 0755 - tar.addfile(info, StringIO.StringIO(configure)) + with tarfile.open(tarpath, 'w:gz') as tar: + configure = configure_template % sys.executable + info = tarfile.TarInfo('configure') + info.size = len(configure) + info.mode = 0o755 + tar.addfile(info, BytesIO(configure)) tarpath = os.path.join(distros, 'bar.tgz') - tar = tarfile.open(tarpath, 'w:gz') - configure = configure_template % sys.executable - info = tarfile.TarInfo('configure.in') - info.size = len(configure) - info.mode = 0755 - tar.addfile(info, StringIO.StringIO(configure)) - autogen = autogen_template - info = tarfile.TarInfo('autogen.sh') - info.size = len(autogen) - info.mode = 0755 - tar.addfile(info, StringIO.StringIO(autogen)) + with tarfile.open(tarpath, 'w:gz') as tar: + configure = configure_template % sys.executable + info = tarfile.TarInfo('configure.in') + info.size = len(configure) + info.mode = 0o755 + tar.addfile(info, BytesIO(configure)) + + autogen = autogen_template + info = tarfile.TarInfo('autogen.sh') + info.size = len(autogen) + info.mode = 0o755 + tar.addfile(info, BytesIO(autogen)) tarpath = os.path.join(distros, 'baz.tgz') - tar = tarfile.open(tarpath, 'w:gz') - configure = configure_template % sys.executable - info = tarfile.TarInfo('configure.py') - info.size = len(configure) - info.mode = 0755 - tar.addfile(info, StringIO.StringIO(configure)) + with tarfile.open(tarpath, 'w:gz') as tar: + configure = configure_template % sys.executable + info = tarfile.TarInfo('configure.py') + info.size = len(configure) + info.mode = 0o755 + tar.addfile(info, BytesIO(configure)) def add(tar, name, src, mode=None): info.size = len(src) if mode is not None: info.mode = mode - tar.addfile(info, StringIO.StringIO(src)) + tar.addfile(info, BytesIO(src)) configure_template = """#!%s import sys -print "configuring foo", ' '.join(sys.argv[1:]) +print("configuring foo " + ' '.join(sys.argv[1:])) Makefile_template = ''' all: @@ -73,7 +84,7 @@ def add(tar, name, src, mode=None): \techo installing foo ''' -open('Makefile', 'w').write(Makefile_template) +with open('Makefile', 'w') as f: f.write(Makefile_template) """ @@ -85,23 +96,33 @@ def add(tar, name, src, mode=None): def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite( - 'README.txt', + 'README.rst', setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown, checker=renormalizing.RENormalizing([ - (re.compile('--prefix=\S+sample-buildout'), + (re.compile(r'--prefix=\S+sample-buildout'), '--prefix=/sample_buildout'), - (re.compile(' = \S+sample-buildout'), + (re.compile(r' = \S+sample-buildout'), ' = /sample_buildout'), (re.compile('http://localhost:[0-9]{4,5}/'), 'http://localhost/'), (re.compile('occured'), 'occurred'), + # Buildout or setuptools has a bug not closing .egg-link files, + # leading to issues being reported by PyPy, which naturally mess up + # doctests. + (re.compile('Exception IOError: IOError.*finalizer of closed file.*'), + ''), + # IGNORE_EXCEPTION_MODULE_IN_PYTHON2 fails because the output doesn't + # always look like a traceback. + (re.compile('subprocess.CalledProcessError'), 'CalledProcessError'), ]), - optionflags = doctest.ELLIPSIS - ), + optionflags=(doctest.ELLIPSIS + | doctest.NORMALIZE_WHITESPACE + | renormalizing.IGNORE_EXCEPTION_MODULE_IN_PYTHON2) + ), doctest.DocFileSuite( - 'downloadcache.txt', - 'patching.txt', - 'shared.txt', + 'downloadcache.rst', + 'patching.rst', + 'shared.rst', setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown, @@ -115,19 +136,19 @@ def test_suite(): (re.compile('extdemo[.]pyd'), 'extdemo.so'), (re.compile('[0-9a-f]{40}'), ''), ]), - optionflags = doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE - ), + optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE + ), doctest.DocFileSuite( - 'misc.txt', + 'misc.rst', setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown, checker=renormalizing.RENormalizing([ - (re.compile('--prefix=\S+sample-buildout'), + (re.compile(r'--prefix=\S+sample-buildout'), '--prefix=/sample_buildout'), (re.compile('http://localhost:[0-9]{4,5}/'), 'http://localhost/'), ]), - optionflags = doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE - ), - )) + optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE + ), + )) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..3377c39 --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py27,py34,py35,py36,pypy + +[testenv] +commands = + zope-testrunner --test-path=src [] +deps = + .[test]