2008-10-16 00:02:57 +02:00
|
|
|
#!/usr/bin/env python
|
2010-04-28 22:55:10 +02:00
|
|
|
import os, sys, tempfile, shutil, glob, atexit, textwrap
|
2010-04-28 17:41:55 +02:00
|
|
|
from path import *
|
2008-10-16 00:02:57 +02:00
|
|
|
|
|
|
|
pyversion = sys.version[:3]
|
2010-04-26 08:52:46 +02:00
|
|
|
|
|
|
|
# the directory containing all the tests
|
2008-10-16 00:02:57 +02:00
|
|
|
here = os.path.dirname(os.path.abspath(__file__))
|
2010-04-26 08:52:46 +02:00
|
|
|
|
|
|
|
# the root of this pip source distribution
|
|
|
|
src = os.path.dirname(here)
|
2010-04-27 17:35:48 +02:00
|
|
|
download_cache = os.path.join(tempfile.mkdtemp(), 'pip-test-cache')
|
2010-04-26 08:52:46 +02:00
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
def demand_dirs(path):
|
|
|
|
if not os.path.exists(path):
|
|
|
|
os.makedirs(path)
|
|
|
|
|
|
|
|
demand_dirs(download_cache)
|
2008-10-16 00:02:57 +02:00
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
# Tweak the path so we can find up-to-date pip sources
|
|
|
|
# (http://bitbucket.org/ianb/pip/issue/98) and scripttest (because my
|
|
|
|
# split_cmd patch hasn't been accepted/released yet).
|
2010-04-26 08:52:46 +02:00
|
|
|
sys.path = [src, os.path.join(src, 'scripttest')] + sys.path
|
2008-10-16 00:02:57 +02:00
|
|
|
from scripttest import TestFileEnvironment
|
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
def create_virtualenv(where):
|
|
|
|
save_argv = sys.argv
|
|
|
|
|
|
|
|
try:
|
|
|
|
import virtualenv
|
2010-05-20 08:45:24 +02:00
|
|
|
sys.argv = ['virtualenv', '--quiet', '--no-site-packages', '--unzip-setuptools', where]
|
2010-04-27 17:35:48 +02:00
|
|
|
virtualenv.main()
|
|
|
|
finally:
|
|
|
|
sys.argv = save_argv
|
|
|
|
|
|
|
|
return virtualenv.path_locations(where)
|
|
|
|
|
2010-05-02 20:11:45 +02:00
|
|
|
def relpath(root, other):
|
|
|
|
"""a poor man's os.path.relpath, since we may not have Python 2.6"""
|
|
|
|
prefix = root+Path.sep
|
|
|
|
assert other.startswith(prefix)
|
|
|
|
return Path(other[len(prefix):])
|
|
|
|
|
2008-10-16 00:02:57 +02:00
|
|
|
if 'PYTHONPATH' in os.environ:
|
|
|
|
del os.environ['PYTHONPATH']
|
|
|
|
|
2009-10-07 23:25:17 +02:00
|
|
|
try:
|
|
|
|
any
|
|
|
|
except NameError:
|
|
|
|
def any(seq):
|
|
|
|
for item in seq:
|
|
|
|
if item:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2009-10-24 15:24:08 +02:00
|
|
|
def clear_environ(environ):
|
|
|
|
return dict(((k, v) for k, v in environ.iteritems()
|
|
|
|
if not k.lower().startswith('pip_')))
|
|
|
|
|
2010-04-10 22:41:27 +02:00
|
|
|
def install_setuptools(env):
|
2010-05-02 20:11:45 +02:00
|
|
|
easy_install = os.path.join(env.bin_path, 'easy_install')
|
2010-04-10 22:41:27 +02:00
|
|
|
version = 'setuptools==0.6c11'
|
|
|
|
if sys.platform != 'win32':
|
|
|
|
return env.run(easy_install, version)
|
|
|
|
|
|
|
|
tempdir = tempfile.mkdtemp()
|
|
|
|
try:
|
2010-04-27 17:35:48 +02:00
|
|
|
for f in glob.glob(easy_install+'*'):
|
|
|
|
shutil.copy2(f, tempdir)
|
2010-04-10 22:41:27 +02:00
|
|
|
return env.run(os.path.join(tempdir, 'easy_install'), version)
|
|
|
|
finally:
|
|
|
|
shutil.rmtree(tempdir)
|
2010-04-26 10:18:52 +02:00
|
|
|
|
|
|
|
def reset_env(environ = None):
|
|
|
|
global env
|
|
|
|
env = TestPipEnvironment(environ)
|
2010-04-24 09:13:54 +02:00
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
return env
|
|
|
|
|
|
|
|
env = None
|
2010-04-27 17:35:48 +02:00
|
|
|
|
2010-04-28 22:55:10 +02:00
|
|
|
class TestFailure(AssertionError):
|
|
|
|
"""
|
|
|
|
|
|
|
|
An "assertion" failed during testing.
|
|
|
|
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
#
|
|
|
|
# This cleanup routine prevents the __del__ method that cleans up the
|
|
|
|
# tree of the last TestPipEnvironment from firing after shutil has
|
|
|
|
# already been unloaded.
|
|
|
|
#
|
|
|
|
def _cleanup():
|
|
|
|
global env
|
|
|
|
del env
|
|
|
|
shutil.rmtree(download_cache, ignore_errors=True)
|
|
|
|
|
|
|
|
atexit.register(_cleanup)
|
|
|
|
|
2010-04-28 22:55:10 +02:00
|
|
|
class TestPipResult(object):
|
|
|
|
|
2010-05-19 12:13:07 +02:00
|
|
|
def __init__(self, impl, verbose=False):
|
2010-04-28 22:55:10 +02:00
|
|
|
self._impl = impl
|
2010-05-19 12:13:07 +02:00
|
|
|
|
|
|
|
if verbose:
|
|
|
|
print self.stdout
|
|
|
|
if self.stderr:
|
|
|
|
print '======= stderr ========'
|
|
|
|
print self.stderr
|
|
|
|
print '======================='
|
2010-04-28 22:55:10 +02:00
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
return getattr(self._impl,attr)
|
2010-04-29 16:04:49 +02:00
|
|
|
|
2010-05-01 23:34:06 +02:00
|
|
|
if sys.platform == 'win32':
|
|
|
|
@property
|
|
|
|
def stdout(self):
|
|
|
|
return self._impl.stdout.replace('\r\n', '\n')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def stderr(self):
|
|
|
|
return self._impl.stderr.replace('\r\n', '\n')
|
|
|
|
|
2010-05-02 00:09:45 +02:00
|
|
|
def __str__(self):
|
|
|
|
return str(self._impl).replace('\r\n','\n')
|
|
|
|
else:
|
|
|
|
# Python doesn't automatically forward __str__ through __getattr__
|
|
|
|
def __str__(self):
|
|
|
|
return str(self._impl)
|
|
|
|
|
2010-04-28 22:55:10 +02:00
|
|
|
def assert_installed(self, pkg_name, with_files=[], without_files=[], without_egg_link=False):
|
|
|
|
e = self.test_env
|
|
|
|
|
2010-05-02 20:11:45 +02:00
|
|
|
pkg_dir = e.venv/ 'src'/ pkg_name.lower()
|
2010-04-28 22:55:10 +02:00
|
|
|
|
|
|
|
egg_link_path = e.site_packages / pkg_name + '.egg-link'
|
|
|
|
if without_egg_link:
|
|
|
|
if egg_link_path in self.files_created:
|
2010-05-20 08:35:04 +02:00
|
|
|
raise TestFailure, 'unexpected egg link file created: %r\n%s' % (egg_link_path, self)
|
2010-04-28 22:55:10 +02:00
|
|
|
else:
|
2010-05-20 08:35:04 +02:00
|
|
|
if not egg_link_path in self.files_created:
|
|
|
|
raise TestFailure, 'expected egg link file missing: %r\n%s' % (egg_link_path, self)
|
|
|
|
|
2010-04-28 22:55:10 +02:00
|
|
|
egg_link_file = self.files_created[egg_link_path]
|
|
|
|
|
|
|
|
if not (# FIXME: I don't understand why there's a trailing . here
|
|
|
|
egg_link_file.bytes.endswith('.')
|
|
|
|
and egg_link_file.bytes[:-1].strip().endswith(pkg_dir)):
|
|
|
|
raise TestFailure, textwrap.dedent(u'''\
|
|
|
|
Incorrect egg_link file %r
|
|
|
|
Expected ending: %r
|
|
|
|
------- Actual contents -------
|
|
|
|
%s
|
|
|
|
-------------------------------''' % (
|
|
|
|
egg_link_file,
|
|
|
|
pkg_dir + u'\n.',
|
|
|
|
egg_link_file.bytes))
|
|
|
|
|
|
|
|
pth_file = Path.string(e.site_packages / 'easy-install.pth')
|
|
|
|
|
|
|
|
if (pth_file in self.files_updated) == without_egg_link:
|
|
|
|
raise TestFailure, '%r unexpectedly %supdated by install' % (
|
|
|
|
pth_file, ('' if without_egg_link else 'not '))
|
|
|
|
|
|
|
|
if (pkg_dir in self.files_created) == (curdir in without_files):
|
|
|
|
raise TestFailure, textwrap.dedent('''\
|
|
|
|
expected package directory %r %sto be created
|
|
|
|
actually created:
|
|
|
|
%s
|
|
|
|
''') % (
|
|
|
|
Path.string(pkg_dir),
|
|
|
|
('not ' if curdir in without_files else ''),
|
|
|
|
sorted(self.files_created.keys()))
|
|
|
|
|
|
|
|
for f in with_files:
|
|
|
|
if not (pkg_dir/f).normpath in self.files_created:
|
|
|
|
raise TestFailure, 'Package directory %r missing expected content %f' % (pkg_dir,f)
|
|
|
|
|
|
|
|
for f in without_files:
|
|
|
|
if (pkg_dir/f).normpath in self.files_created:
|
|
|
|
raise TestFailure, 'Package directory %r has unexpected content %f' % (pkg_dir,f)
|
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
class TestPipEnvironment(TestFileEnvironment):
|
2010-05-02 20:11:45 +02:00
|
|
|
"""A specialized TestFileEnvironment for testing pip"""
|
|
|
|
|
|
|
|
#
|
|
|
|
# Attribute naming convention
|
|
|
|
# ---------------------------
|
|
|
|
#
|
|
|
|
# Instances of this class have many attributes representing paths
|
|
|
|
# in the filesystem. To keep things straight, absolute paths have
|
|
|
|
# a name of the form xxxx_path and relative paths have a name that
|
|
|
|
# does not end in '_path'.
|
|
|
|
|
|
|
|
# The following paths are relative to the root_path, and should be
|
|
|
|
# treated by clients as instance attributes. The fact that they
|
|
|
|
# are defined in the class is an implementation detail
|
|
|
|
|
|
|
|
# where we'll create the virtual Python installation for testing
|
|
|
|
#
|
|
|
|
# Named with a leading dot to reduce the chance of spurious
|
|
|
|
# results due to being mistaken for the virtualenv package.
|
|
|
|
venv = Path('.virtualenv')
|
|
|
|
|
|
|
|
# The root of a directory tree to be used arbitrarily by tests
|
|
|
|
scratch = Path('scratch')
|
|
|
|
|
|
|
|
exe = '.exe' if sys.platform == 'win32' else ''
|
|
|
|
|
2010-05-19 12:13:07 +02:00
|
|
|
verbose = False
|
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
def __init__(self, environ=None):
|
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
self.root_path = Path(tempfile.mkdtemp('-piptest'))
|
|
|
|
|
|
|
|
# We will set up a virtual environment at root_path.
|
2010-05-02 20:11:45 +02:00
|
|
|
self.scratch_path = self.root_path / self.scratch
|
2010-04-27 17:35:48 +02:00
|
|
|
|
2010-05-02 20:11:45 +02:00
|
|
|
self.venv_path = self.root_path / self.venv
|
2010-04-27 17:35:48 +02:00
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
if not environ:
|
|
|
|
environ = os.environ.copy()
|
|
|
|
environ = clear_environ(environ)
|
2010-04-27 17:35:48 +02:00
|
|
|
environ['PIP_DOWNLOAD_CACHE'] = str(download_cache)
|
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
environ['PIP_NO_INPUT'] = '1'
|
2010-04-27 17:35:48 +02:00
|
|
|
environ['PIP_LOG_FILE'] = str(self.root_path/'pip-log.txt')
|
2010-04-26 10:18:52 +02:00
|
|
|
|
|
|
|
super(TestPipEnvironment,self).__init__(
|
2010-04-27 17:35:48 +02:00
|
|
|
self.root_path, ignore_hidden=False,
|
|
|
|
environ=environ, split_cmd=False, start_clear=False,
|
|
|
|
cwd=self.scratch_path, capture_temp=True, assert_no_temp=True
|
|
|
|
)
|
|
|
|
|
2010-05-02 20:11:45 +02:00
|
|
|
demand_dirs(self.venv_path)
|
2010-04-27 17:35:48 +02:00
|
|
|
demand_dirs(self.scratch_path)
|
2010-04-26 05:40:27 +02:00
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
# Create a virtualenv and remember where it's putting things.
|
2010-05-02 20:11:45 +02:00
|
|
|
virtualenv_paths = create_virtualenv(self.venv_path)
|
|
|
|
assert self.venv_path == virtualenv_paths[0] # sanity check
|
|
|
|
|
|
|
|
for id,path in zip(('venv', 'lib', 'include', 'bin'), virtualenv_paths):
|
|
|
|
setattr(self, id+'_path', Path(path))
|
|
|
|
setattr(self, id, relpath(self.root_path,path))
|
|
|
|
|
|
|
|
assert self.venv == TestPipEnvironment.venv # sanity check
|
2010-04-26 05:40:27 +02:00
|
|
|
|
2010-05-02 20:11:45 +02:00
|
|
|
self.site_packages = self.lib/'site-packages'
|
2010-04-27 17:35:48 +02:00
|
|
|
|
|
|
|
# put the test-scratch virtualenv's bin dir first on the PATH
|
2010-05-02 20:11:45 +02:00
|
|
|
self.environ['PATH'] = Path.pathsep.join( (self.bin_path, self.environ['PATH']) )
|
2010-04-26 05:40:27 +02:00
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
# test that test-scratch virtualenv creation produced sensible venv python
|
2010-04-27 17:35:48 +02:00
|
|
|
result = self.run('python', '-c', 'import sys; print sys.executable')
|
2010-04-26 10:18:52 +02:00
|
|
|
pythonbin = result.stdout.strip()
|
2010-04-28 17:41:55 +02:00
|
|
|
|
2010-05-02 20:11:45 +02:00
|
|
|
if Path(pythonbin).noext != self.bin_path/'python':
|
2010-04-28 17:41:55 +02:00
|
|
|
raise RuntimeError(
|
|
|
|
"Oops! 'python' in our test environment runs %r"
|
2010-05-02 20:11:45 +02:00
|
|
|
" rather than expected %r" % (pythonbin, self.bin_path/'python'))
|
2010-04-17 23:49:29 +02:00
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
# make sure we have current setuptools to avoid svn incompatibilities
|
2010-04-27 17:35:48 +02:00
|
|
|
install_setuptools(self)
|
2010-04-13 02:48:37 +02:00
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
# Uninstall whatever version of pip came with the virtualenv.
|
|
|
|
# Earlier versions of pip were incapable of
|
|
|
|
# self-uninstallation on Windows, so we use the one we're testing.
|
|
|
|
self.run('python', '-c',
|
|
|
|
'import sys;sys.path.insert(0, %r);import pip;sys.exit(pip.main());' % os.path.dirname(here),
|
2010-05-19 12:13:07 +02:00
|
|
|
'uninstall', '-vvv', '-y', 'pip')
|
2010-04-13 02:48:37 +02:00
|
|
|
|
2010-04-26 10:18:52 +02:00
|
|
|
# Install this version instead
|
2010-04-27 17:35:48 +02:00
|
|
|
self.run('python', 'setup.py', 'install', cwd=src)
|
|
|
|
|
2010-04-28 22:55:10 +02:00
|
|
|
def run(self, *args, **kw):
|
2010-05-19 12:13:07 +02:00
|
|
|
if self.verbose:
|
|
|
|
print '>> running', args, kw
|
2010-04-28 22:55:10 +02:00
|
|
|
cwd = kw.pop('cwd', None)
|
|
|
|
run_from = kw.pop('run_from',None)
|
|
|
|
assert not cwd or not run_from, "Don't use run_from; it's going away"
|
|
|
|
cwd = Path.string(cwd or run_from or self.cwd)
|
|
|
|
assert not isinstance(cwd,Path)
|
2010-05-19 12:13:07 +02:00
|
|
|
return TestPipResult( super(TestPipEnvironment,self).run(cwd=cwd,*args,**kw), verbose=self.verbose )
|
2010-04-28 22:55:10 +02:00
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
def __del__(self):
|
|
|
|
shutil.rmtree(self.root_path, ignore_errors=True)
|
2008-10-16 00:02:57 +02:00
|
|
|
|
2008-10-16 00:24:00 +02:00
|
|
|
def run_pip(*args, **kw):
|
2010-04-28 22:55:10 +02:00
|
|
|
return env.run('pip', *args, **kw)
|
2008-10-16 00:02:57 +02:00
|
|
|
|
2010-04-27 17:35:48 +02:00
|
|
|
def write_file(filename, text, dest=None):
|
|
|
|
"""Write a file in the dest (default=env.scratch_path)
|
|
|
|
|
|
|
|
"""
|
|
|
|
env = get_env()
|
|
|
|
if dest:
|
|
|
|
complete_path = dest/ filename
|
|
|
|
else:
|
|
|
|
complete_path = env.scratch_path/ filename
|
|
|
|
f = open(complete_path, 'w')
|
2008-10-16 00:02:57 +02:00
|
|
|
f.write(text)
|
|
|
|
f.close()
|
|
|
|
|
2010-04-15 13:01:36 +02:00
|
|
|
def mkdir(dirname):
|
2010-04-27 17:35:48 +02:00
|
|
|
os.mkdir(os.path.join(get_env().scratch_path, dirname))
|
2010-04-15 13:01:36 +02:00
|
|
|
|
2008-10-16 00:02:57 +02:00
|
|
|
def get_env():
|
2010-04-27 17:35:48 +02:00
|
|
|
if env is None:
|
|
|
|
reset_env()
|
2008-10-16 00:02:57 +02:00
|
|
|
return env
|
|
|
|
|
2009-04-01 00:17:08 +02:00
|
|
|
# FIXME ScriptTest does something similar, but only within a single
|
|
|
|
# ProcResult; this generalizes it so states can be compared across
|
|
|
|
# multiple commands. Maybe should be rolled into ScriptTest?
|
2009-04-06 19:59:20 +02:00
|
|
|
def diff_states(start, end, ignore=None):
|
2009-04-01 00:17:08 +02:00
|
|
|
"""
|
|
|
|
Differences two "filesystem states" as represented by dictionaries
|
|
|
|
of FoundFile and FoundDir objects.
|
|
|
|
|
|
|
|
Returns a dictionary with following keys:
|
|
|
|
|
|
|
|
``deleted``
|
|
|
|
Dictionary of files/directories found only in the start state.
|
|
|
|
|
|
|
|
``created``
|
|
|
|
Dictionary of files/directories found only in the end state.
|
|
|
|
|
|
|
|
``updated``
|
|
|
|
Dictionary of files whose size has changed (FIXME not entirely
|
|
|
|
reliable, but comparing contents is not possible because
|
|
|
|
FoundFile.bytes is lazy, and comparing mtime doesn't help if
|
|
|
|
we want to know if a file has been returned to its earlier
|
|
|
|
state).
|
|
|
|
|
|
|
|
Ignores mtime and other file attributes; only presence/absence and
|
|
|
|
size are considered.
|
2010-04-22 08:37:50 +02:00
|
|
|
|
2009-04-01 00:17:08 +02:00
|
|
|
"""
|
2009-04-06 19:59:20 +02:00
|
|
|
ignore = ignore or []
|
2010-05-03 04:29:38 +02:00
|
|
|
# FIXME: this code ignores too much, e.g. foo/bar when only foo/b is specified
|
2009-04-06 19:59:20 +02:00
|
|
|
start_keys = set([k for k in start.keys()
|
2009-09-06 19:16:02 +02:00
|
|
|
if not any([k.startswith(i) for i in ignore])])
|
2009-04-06 19:59:20 +02:00
|
|
|
end_keys = set([k for k in end.keys()
|
2009-09-06 19:16:02 +02:00
|
|
|
if not any([k.startswith(i) for i in ignore])])
|
2009-04-06 19:59:20 +02:00
|
|
|
deleted = dict([(k, start[k]) for k in start_keys.difference(end_keys)])
|
|
|
|
created = dict([(k, end[k]) for k in end_keys.difference(start_keys)])
|
2009-04-01 00:17:08 +02:00
|
|
|
updated = {}
|
2009-04-06 19:59:20 +02:00
|
|
|
for k in start_keys.intersection(end_keys):
|
2009-04-01 00:17:08 +02:00
|
|
|
if (start[k].size != end[k].size):
|
|
|
|
updated[k] = end[k]
|
|
|
|
return dict(deleted=deleted, created=created, updated=updated)
|
|
|
|
|
2010-05-03 06:39:04 +02:00
|
|
|
def assert_all_changes( start_state, end_state, expected_changes ):
|
2010-05-03 04:08:03 +02:00
|
|
|
"""
|
2010-05-03 06:39:04 +02:00
|
|
|
Fails if anything changed that isn't listed in the
|
|
|
|
expected_changes.
|
|
|
|
|
|
|
|
start_state is either a dict mapping paths to
|
|
|
|
scripttest.[FoundFile|FoundDir] objects or a TestPipResult whose
|
|
|
|
files_before we'll test. end_state is either a similar dict or a
|
|
|
|
TestPipResult whose files_after we'll test.
|
|
|
|
|
|
|
|
Note: listing a directory means anything below
|
|
|
|
that directory can be expected to have changed.
|
2010-05-03 04:08:03 +02:00
|
|
|
"""
|
2010-05-03 06:39:04 +02:00
|
|
|
start_files = start_state
|
|
|
|
end_files = end_state
|
|
|
|
if isinstance(start_state, TestPipResult):
|
|
|
|
start_files = start_state.files_before
|
|
|
|
if isinstance(end_state, TestPipResult):
|
|
|
|
end_files = end_state.files_after
|
|
|
|
|
|
|
|
diff = diff_states( start_files, end_files, ignore=expected_changes )
|
2010-05-03 04:08:03 +02:00
|
|
|
if diff.values() != [{},{},{}]:
|
|
|
|
import pprint
|
|
|
|
raise TestFailure, 'Unexpected changes:\n' + '\n'.join(
|
|
|
|
[k + ': ' + ', '.join(v.keys()) for k,v in diff.items()])
|
|
|
|
|
|
|
|
# Don't throw away this potentially useful information
|
|
|
|
return diff
|
|
|
|
|
2008-10-16 00:02:57 +02:00
|
|
|
if __name__ == '__main__':
|
2010-04-13 02:48:37 +02:00
|
|
|
sys.stderr.write("Run pip's tests using nosetests. Requires virtualenv, ScriptTest, and nose.\n")
|
2010-03-08 16:12:47 +01:00
|
|
|
sys.exit(1)
|