duplicity-team team mailing list archive
-
duplicity-team team
-
Mailing list archive
-
Message #02092
[Merge] lp:~mterry/duplicity/modern-testing into lp:duplicity
Michael Terry has proposed merging lp:~mterry/duplicity/modern-testing into lp:duplicity with lp:~mterry/duplicity/consolidate-tests as a prerequisite.
Requested reviews:
duplicity-team (duplicity-team)
For more details, see:
https://code.launchpad.net/~mterry/duplicity/modern-testing/+merge/215994
Enable/use more modern testing tools like nosetests and tox as well as more common setup.py hooks like test and sdist.
Specifically:
* move setup.py to toplevel where most tools and users expect it
* Move and adjust test files to work when running "nosetests" in toplevel directory. Specifically, do a lot more of the test setup in tests/__init__.py rather than the run-tests scripts
* Add small tox.ini file for using tox, which is a wrapper for using virtualenv. Only enable for py26 and py27 right now, since modern setuptools dropped support for <2.6 (and tox 1.7 also recently dropped <2.6)
* Add setup.py hooks for test and sdist which are both standard targets (sdist just outsources to dist/makedist right now)
--
https://code.launchpad.net/~mterry/duplicity/modern-testing/+merge/215994
Your team duplicity-team is requested to review the proposed merge of lp:~mterry/duplicity/modern-testing into lp:duplicity.
=== modified file 'bin/duplicity'
--- bin/duplicity 2014-03-10 14:17:08 +0000
+++ bin/duplicity 2014-04-16 02:52:51 +0000
@@ -33,10 +33,6 @@
from datetime import datetime
from lockfile import FileLock
-pwd = os.path.abspath(os.path.dirname(sys.argv[0]))
-if os.path.exists(os.path.join(pwd, "../duplicity")):
- sys.path.insert(0, os.path.abspath(os.path.join(pwd, "../.")))
-
from duplicity import log
log.setup()
=== modified file 'dist/makedist'
--- dist/makedist 2011-11-25 17:47:57 +0000
+++ dist/makedist 2014-04-16 02:52:51 +0000
@@ -23,7 +23,6 @@
import os, re, shutil, time, sys, subprocess
SourceDir = "duplicity"
-DistDir = "dist"
# Various details about the files must also be specified by the rpm
# spec template.
@@ -89,7 +88,7 @@
os.path.join(tardir, "bin", "duplicity.1"))
VersionedCopy(os.path.join("bin", "rdiffdir.1"),
os.path.join(tardir, "bin", "rdiffdir.1"))
- VersionedCopy(os.path.join(DistDir, "setup.py"),
+ VersionedCopy("setup.py",
os.path.join(tardir, "setup.py"))
# make sure executables are
=== renamed file 'dist/setup.py' => 'setup.py'
--- dist/setup.py 2011-11-25 17:47:57 +0000
+++ setup.py 2014-04-16 02:52:51 +0000
@@ -21,7 +21,10 @@
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import sys, os
-from distutils.core import setup, Extension
+from setuptools import setup, Extension
+from setuptools.command.test import test
+from setuptools.command.install import install
+from setuptools.command.sdist import sdist
version_string = "$version"
@@ -55,8 +58,9 @@
'CHANGELOG']),
]
-assert os.path.exists("po"), "Missing 'po' directory."
-for root, dirs, files in os.walk("po"):
+top_dir = os.path.dirname(os.path.abspath(__file__))
+assert os.path.exists(os.path.join(top_dir, "po")), "Missing 'po' directory."
+for root, dirs, files in os.walk(os.path.join(top_dir, "po")):
for file in files:
path = os.path.join(root, file)
if path.endswith("duplicity.mo"):
@@ -65,6 +69,60 @@
('share/locale/%s/LC_MESSAGES' % lang,
["po/%s/duplicity.mo" % lang]))
+
+class TestCommand(test):
+ def run(self):
+ # Make sure all modules are ready
+ build_cmd = self.get_finalized_command("build_py")
+ build_cmd.run()
+ # And make sure our scripts are ready
+ build_scripts_cmd = self.get_finalized_command("build_scripts")
+ build_scripts_cmd.run()
+
+ # make symlinks for test data
+ if build_cmd.build_lib != top_dir:
+ for path in ['testfiles.tar.gz', 'testtar.tar', 'gnupg']:
+ src = os.path.join(top_dir, 'testing', path)
+ target = os.path.join(build_cmd.build_lib, 'testing', path)
+ try:
+ os.symlink(src, target)
+ except Exception:
+ pass
+
+ os.environ['PATH'] = "%s:%s" % (
+ os.path.abspath(build_scripts_cmd.build_dir),
+ os.environ.get('PATH'))
+
+ test.run(self)
+
+
+class InstallCommand(install):
+ def run(self):
+ # Normally, install will call build(). But we want to delete the
+ # testing dir between building and installing. So we manually build
+ # and mark ourselves to skip building when we run() for real.
+ self.run_command('build')
+ self.skip_build = True
+
+ # This should always be true, but just to make sure!
+ if self.build_lib != top_dir:
+ testing_dir = os.path.join(self.build_lib, 'testing')
+ os.system("rm -rf %s" % testing_dir)
+
+ install.run(self)
+
+
+# TODO: move logic from dist/makedist inline
+class SDistCommand(sdist):
+ def run(self):
+ version = version_string
+ if version[0] == '$':
+ version = "0"
+ os.system(os.path.join(top_dir, "dist", "makedist") + " " + version)
+ os.system("rm duplicity.spec")
+ os.system("mv duplicity-" + version + ".tar.gz " + self.dist_dir)
+
+
setup(name="duplicity",
version=version_string,
description="Encrypted backup using rsync algorithm",
@@ -74,7 +132,10 @@
maintainer_email="kenneth@xxxxxxxxxxx",
url="http://duplicity.nongnu.org/index.html",
packages = ['duplicity',
- 'duplicity.backends',],
+ 'duplicity.backends',
+ 'testing',
+ 'testing.helpers',
+ 'testing.tests'],
package_dir = {"duplicity" : "duplicity",
"duplicity.backends" : "duplicity/backends",},
ext_modules = [Extension("duplicity._librsync",
@@ -84,4 +145,9 @@
libraries=["rsync"])],
scripts = ['bin/rdiffdir', 'bin/duplicity'],
data_files = data_files,
+ tests_require = ['lockfile', 'nose'],
+ test_suite = 'nose.collector',
+ cmdclass={'test': TestCommand,
+ 'install': InstallCommand,
+ 'sdist': SDistCommand},
)
=== added file 'testing/__init__.py'
--- testing/__init__.py 1970-01-01 00:00:00 +0000
+++ testing/__init__.py 2014-04-16 02:52:51 +0000
@@ -0,0 +1,3 @@
+import sys
+if sys.version_info < (2, 5,):
+ import tests
=== modified file 'testing/helpers/helper.py'
--- testing/helpers/helper.py 2014-04-16 02:52:51 +0000
+++ testing/helpers/helper.py 2014-04-16 02:52:51 +0000
@@ -33,14 +33,22 @@
encrypt_key1 = 'B5FA894F'
encrypt_key2 = '9B736B2A'
-# TODO: remove this method
def setup():
""" setup for unit tests """
+ # TODO: remove these lines
log.setup()
log.setverbosity(log.WARNING)
globals.print_statistics = 0
backend.import_backends()
+ # Have all file references in tests relative to our testing dir
+ _testing_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ os.chdir(_testing_dir)
+ # Make sure that PYTHONPATH is set right for subprocesses (setuptools
+ # may futz with it)
+ _top_dir = os.path.dirname(_testing_dir)
+ os.environ['PYTHONPATH'] = _top_dir
+
class CmdError(Exception):
"""Indicates an error running an external command"""
@@ -57,7 +65,7 @@
cls.sign_key = sign_key
cls.sign_passphrase = sign_passphrase
cls.encrypt_key1 = encrypt_key1
- cls.encrypt_key2 = encrypt_key2
+ cls.encrypt_key2 = encrypt_key2
setup()
def setUp(self):
=== modified file 'testing/run-tests'
--- testing/run-tests 2014-02-05 02:57:01 +0000
+++ testing/run-tests 2014-04-16 02:52:51 +0000
@@ -24,11 +24,6 @@
cd $(dirname $0)
THISDIR=$(pwd)
-export TZ=US/Central
-export LANG=en_US.UTF-8
-# up for 'duplicity' module and here for 'helper.py'
-export PYTHONPATH="$THISDIR/overrides:$(dirname $THISDIR):$THISDIR/helpers"
-export GNUPGHOME="$THISDIR/gnupg"
export PATH="$(dirname $THISDIR)/bin:$PATH"
TOP_TESTS=$*
=== modified file 'testing/run-tests-ve'
--- testing/run-tests-ve 2011-11-25 19:11:39 +0000
+++ testing/run-tests-ve 2014-04-16 02:52:51 +0000
@@ -24,11 +24,6 @@
cd $(dirname $0)
THISDIR=$(pwd)
-export TZ=US/Central
-export LANG=
-# up for 'duplicity' module and here for 'helper.py'
-export PYTHONPATH="$(dirname $THISDIR):$THISDIR/helpers"
-export GNUPGHOME="$THISDIR/gnupg"
export PATH="$(dirname $THISDIR)/bin:$PATH"
TOP_TESTS=$*
=== added file 'testing/tests/__init__.py'
--- testing/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ testing/tests/__init__.py 2014-04-16 02:52:51 +0000
@@ -0,0 +1,52 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright 2012 Canonical Ltd
+#
+# This file is part of duplicity.
+#
+# Duplicity 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 2 of the License, or (at your
+# option) any later version.
+#
+# Duplicity 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 duplicity; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+import sys
+import time
+
+_this_dir = os.path.dirname(os.path.abspath(__file__))
+_testing_dir = os.path.dirname(_this_dir)
+_top_dir = os.path.dirname(_testing_dir)
+_helpers_dir = os.path.join(_testing_dir, 'helpers')
+_overrides_dir = os.path.join(_testing_dir, 'overrides')
+
+# Adjust python path for duplicity and helper modules
+sys.path = [_overrides_dir, _top_dir, _helpers_dir] + sys.path
+
+# Also set PYTHONPATH for any subprocesses
+os.environ['PYTHONPATH'] = _overrides_dir + ":" + _top_dir
+
+# Now set some variables that help standardize test behavior
+os.environ['LANG'] = ''
+os.environ['GNUPGHOME'] = os.path.join(_testing_dir, 'gnupg')
+
+# Standardize time
+os.environ['TZ'] = 'US/Central'
+time.tzset()
+
+# Automatically add all submodules into this namespace. Helps python2.4
+# unittest work.
+if sys.version_info < (2, 5,):
+ for module in os.listdir(_this_dir):
+ if module == '__init__.py' or module[-3:] != '.py':
+ continue
+ __import__(module[:-3], locals(), globals())
+ del module
=== renamed file 'testing/tests/GnuPGInterfacetest.py' => 'testing/tests/test_GnuPGInterface.py'
=== renamed file 'testing/tests/badupload.py' => 'testing/tests/test_badupload.py'
=== renamed file 'testing/tests/cleanuptest.py' => 'testing/tests/test_cleanup.py'
=== renamed file 'testing/tests/collectionstest.py' => 'testing/tests/test_collections.py'
--- testing/tests/collectionstest.py 2011-11-04 04:07:59 +0000
+++ testing/tests/test_collections.py 2014-04-16 02:52:51 +0000
@@ -70,20 +70,6 @@
"duplicity-inc.2000-08-17T16:17:01-07:00.to.2000-08-18T00:04:30-07:00.vol1.difftar.gpg",
"Extra stuff to be ignored"]
-assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")
-
-col_test_dir = path.Path("testfiles/collectionstest")
-archive_dir = col_test_dir.append("archive_dir")
-globals.archive_dir = archive_dir
-archive_dir_backend = backend.get_backend("file://testfiles/collectionstest"
- "/archive_dir")
-
-dummy_backend = None
-real_backend = backend.get_backend("file://%s/%s" %
- (col_test_dir.name, "remote_dir"))
-output_dir = path.Path("testfiles/output") # used as a temp directory
-output_dir_backend = backend.get_backend("file://testfiles/output")
-
class CollectionTest(unittest.TestCase):
"""Test collections"""
@@ -91,13 +77,24 @@
assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")
assert not os.system("mkdir testfiles/output")
+ col_test_dir = path.Path("testfiles/collectionstest")
+ archive_dir = col_test_dir.append("archive_dir")
+ globals.archive_dir = archive_dir
+ self.archive_dir_backend = backend.get_backend("file://testfiles/collectionstest"
+ "/archive_dir")
+
+ self.real_backend = backend.get_backend("file://%s/%s" %
+ (col_test_dir.name, "remote_dir"))
+ self.output_dir = path.Path("testfiles/output") # used as a temp directory
+ self.output_dir_backend = backend.get_backend("file://testfiles/output")
+
def tearDown(self):
assert not os.system("rm -rf testfiles tempdir temp2.tar")
def del_tmp(self):
"""Reset the testfiles/output directory"""
- output_dir.deltree()
- output_dir.mkdir()
+ self.output_dir.deltree()
+ self.output_dir.mkdir()
def set_gpg_profile(self):
"""Set gpg profile to standard "foobar" sym"""
@@ -106,7 +103,7 @@
def test_backup_chains(self):
"""Test basic backup chain construction"""
random.shuffle(filename_list1)
- cs = collections.CollectionsStatus(dummy_backend, archive_dir)
+ cs = collections.CollectionsStatus(None, globals.archive_dir)
chains, orphaned, incomplete = cs.get_backup_chains(filename_list1) #@UnusedVariable
if len(chains) != 1 or len(orphaned) != 0:
print chains
@@ -127,26 +124,26 @@
assert cs.matched_chain_pair[0].end_time == 1029826800L
assert len(cs.all_backup_chains) == 1, cs.all_backup_chains
- cs = collections.CollectionsStatus(real_backend, archive_dir).set_values()
+ cs = collections.CollectionsStatus(self.real_backend, globals.archive_dir).set_values()
check_cs(cs)
assert cs.matched_chain_pair[0].islocal()
def test_sig_chain(self):
"""Test a single signature chain"""
- chain = collections.SignatureChain(1, archive_dir)
+ chain = collections.SignatureChain(1, globals.archive_dir)
for filename in local_sigchain_filename_list:
assert chain.add_filename(filename)
assert not chain.add_filename("duplicity-new-signatures.2002-08-18T00:04:30-07:00.to.2002-08-20T00:00:00-07:00.sigtar.gpg")
def test_sig_chains(self):
"""Test making signature chains from filename list"""
- cs = collections.CollectionsStatus(dummy_backend, archive_dir)
+ cs = collections.CollectionsStatus(None, globals.archive_dir)
chains, orphaned_paths = cs.get_signature_chains(local = 1)
self.sig_chains_helper(chains, orphaned_paths)
def test_sig_chains2(self):
"""Test making signature chains from filename list on backend"""
- cs = collections.CollectionsStatus(archive_dir_backend, archive_dir)
+ cs = collections.CollectionsStatus(self.archive_dir_backend, globals.archive_dir)
chains, orphaned_paths = cs.get_signature_chains(local = None)
self.sig_chains_helper(chains, orphaned_paths)
@@ -161,16 +158,16 @@
def sigchain_fileobj_get(self, local):
"""Return chain, local if local is true with filenames added"""
if local:
- chain = collections.SignatureChain(1, archive_dir)
+ chain = collections.SignatureChain(1, globals.archive_dir)
for filename in local_sigchain_filename_list:
assert chain.add_filename(filename)
else:
- chain = collections.SignatureChain(None, real_backend)
+ chain = collections.SignatureChain(None, self.real_backend)
for filename in remote_sigchain_filename_list:
assert chain.add_filename(filename)
return chain
- def sigchain_fileobj_testlist(self, chain):
+ def sigchain_fileobj_check_list(self, chain):
"""Make sure the list of file objects in chain has right contents
The contents of the testfiles/collectiontest/remote_dir have
@@ -190,18 +187,18 @@
def test_sigchain_fileobj(self):
"""Test getting signature chain fileobjs from archive_dir"""
self.set_gpg_profile()
- self.sigchain_fileobj_testlist(self.sigchain_fileobj_get(1))
- self.sigchain_fileobj_testlist(self.sigchain_fileobj_get(None))
+ self.sigchain_fileobj_check_list(self.sigchain_fileobj_get(1))
+ self.sigchain_fileobj_check_list(self.sigchain_fileobj_get(None))
def get_filelist2_cs(self):
"""Return set CollectionsStatus object from filelist 2"""
# Set up testfiles/output with files from filename_list2
self.del_tmp()
for filename in filename_list2:
- p = output_dir.append(filename)
+ p = self.output_dir.append(filename)
p.touch()
- cs = collections.CollectionsStatus(output_dir_backend, archive_dir)
+ cs = collections.CollectionsStatus(self.output_dir_backend, globals.archive_dir)
cs.set_values()
return cs
=== renamed file 'testing/tests/diffdirtest.py' => 'testing/tests/test_diffdir.py'
=== renamed file 'testing/tests/file_namingtest.py' => 'testing/tests/test_filenaming.py'
=== renamed file 'testing/tests/finaltest.py' => 'testing/tests/test_final.py'
=== renamed file 'testing/tests/gpgtest.py' => 'testing/tests/test_gpg.py'
--- testing/tests/gpgtest.py 2013-01-06 18:57:01 +0000
+++ testing/tests/test_gpg.py 2014-04-16 02:52:51 +0000
@@ -27,12 +27,11 @@
helper.setup()
-default_profile = gpg.GPGProfile(passphrase = "foobar")
-
class GPGTest(unittest.TestCase):
"""Test GPGFile"""
def setUp(self):
assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")
+ self.default_profile = gpg.GPGProfile(passphrase = "foobar")
def tearDown(self):
assert not os.system("rm -rf testfiles tempdir temp2.tar")
@@ -47,7 +46,7 @@
self.deltmp()
epath = path.Path("testfiles/output/encrypted_file")
if not profile:
- profile = default_profile
+ profile = self.default_profile
encrypted_file = gpg.GPGFile(1, epath, profile)
encrypted_file.write(s)
encrypted_file.close()
=== renamed file 'testing/tests/lazytest.py' => 'testing/tests/test_lazy.py'
=== renamed file 'testing/tests/logtest.py' => 'testing/tests/test_log.py'
=== renamed file 'testing/tests/manifesttest.py' => 'testing/tests/test_manifest.py'
=== renamed file 'testing/tests/misctest.py' => 'testing/tests/test_misc.py'
=== renamed file 'testing/tests/parsedurltest.py' => 'testing/tests/test_parsedurl.py'
=== renamed file 'testing/tests/patchdirtest.py' => 'testing/tests/test_patchdir.py'
=== renamed file 'testing/tests/pathtest.py' => 'testing/tests/test_path.py'
=== renamed file 'testing/tests/rdiffdirtest.py' => 'testing/tests/test_rdiffdir.py'
=== renamed file 'testing/tests/restarttest.py' => 'testing/tests/test_restart.py'
=== renamed file 'testing/tests/selectiontest.py' => 'testing/tests/test_selection.py'
=== renamed file 'testing/tests/statictest.py' => 'testing/tests/test_static.py'
=== renamed file 'testing/tests/statisticstest.py' => 'testing/tests/test_statistics.py'
=== renamed file 'testing/tests/dup_temptest.py' => 'testing/tests/test_temp.py'
=== renamed file 'testing/tests/tempdirtest.py' => 'testing/tests/test_tempdir.py'
=== renamed file 'testing/tests/dup_timetest.py' => 'testing/tests/test_time.py'
=== renamed file 'testing/tests/unicode.py' => 'testing/tests/test_unicode.py'
=== added file 'tox.ini'
--- tox.ini 1970-01-01 00:00:00 +0000
+++ tox.ini 2014-04-16 02:52:51 +0000
@@ -0,0 +1,7 @@
+[tox]
+envlist=py26,py27
+
+[testenv]
+deps=lockfile
+ nose
+commands=nosetests
Follow ups