summaryrefslogtreecommitdiff
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/__init__.py40
-rwxr-xr-xsetuptools/command/easy_install.py163
-rwxr-xr-xsetuptools/command/egg_info.py39
-rwxr-xr-xsetuptools/command/sdist.py164
-rwxr-xr-xsetuptools/command/upload.py13
-rw-r--r--setuptools/compat.py8
-rw-r--r--setuptools/dist.py168
-rwxr-xr-xsetuptools/package_index.py6
-rw-r--r--setuptools/py26compat.py19
-rw-r--r--setuptools/script template (dev).py8
-rw-r--r--setuptools/ssl_support.py92
-rw-r--r--setuptools/svn_utils.py534
-rw-r--r--setuptools/tests/environment.py158
-rw-r--r--setuptools/tests/script-with-bom.py3
-rw-r--r--setuptools/tests/svn_data/dummy.zipbin0 -> 1771 bytes
-rw-r--r--setuptools/tests/svn_data/dummy13.zipbin0 -> 9243 bytes
-rw-r--r--setuptools/tests/svn_data/dummy14.zipbin0 -> 7496 bytes
-rw-r--r--setuptools/tests/svn_data/dummy15.zipbin0 -> 7506 bytes
-rw-r--r--setuptools/tests/svn_data/dummy16.zipbin0 -> 7155 bytes
-rw-r--r--setuptools/tests/svn_data/dummy17.zipbin0 -> 7512 bytes
-rw-r--r--setuptools/tests/svn_data/dummy18.zipbin0 -> 7639 bytes
-rw-r--r--setuptools/tests/svn_data/svn13_example.zipbin0 -> 48818 bytes
-rw-r--r--setuptools/tests/svn_data/svn13_ext_list.txt3
-rw-r--r--setuptools/tests/svn_data/svn13_ext_list.xml0
-rw-r--r--setuptools/tests/svn_data/svn13_info.xml121
-rw-r--r--setuptools/tests/svn_data/svn14_example.zipbin0 -> 31077 bytes
-rw-r--r--setuptools/tests/svn_data/svn14_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn14_ext_list.xml0
-rw-r--r--setuptools/tests/svn_data/svn14_info.xml119
-rw-r--r--setuptools/tests/svn_data/svn15_example.zipbin0 -> 31143 bytes
-rw-r--r--setuptools/tests/svn_data/svn15_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn15_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn15_info.xml125
-rw-r--r--setuptools/tests/svn_data/svn16_example.zipbin0 -> 29418 bytes
-rw-r--r--setuptools/tests/svn_data/svn16_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn16_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn16_info.xml125
-rw-r--r--setuptools/tests/svn_data/svn17_example.zipbin0 -> 46954 bytes
-rw-r--r--setuptools/tests/svn_data/svn17_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn17_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn17_info.xml130
-rw-r--r--setuptools/tests/svn_data/svn18_example.zipbin0 -> 47477 bytes
-rw-r--r--setuptools/tests/svn_data/svn18_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn18_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn18_info.xml136
-rw-r--r--setuptools/tests/test_egg_info.py128
-rw-r--r--setuptools/tests/test_sandbox.py13
-rw-r--r--setuptools/tests/test_sdist.py138
-rw-r--r--setuptools/tests/test_svn.py223
-rw-r--r--setuptools/version.py2
50 files changed, 2308 insertions, 466 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index 45b95f90..fc9b7b93 100644
--- a/setuptools/__init__.py
+++ b/setuptools/__init__.py
@@ -1,19 +1,24 @@
"""Extensions to the 'distutils' for large or complex distributions"""
-from setuptools.extension import Extension, Library
-from setuptools.dist import Distribution, Feature, _get_unpatched
-import distutils.core, setuptools.command
-from setuptools.depends import Require
-from distutils.core import Command as _Command
-from distutils.util import convert_path
+
import os
import sys
-from setuptools.version import __version__
+import distutils.core
+import distutils.filelist
+from distutils.core import Command as _Command
+from distutils.util import convert_path
+
+import setuptools.version
+from setuptools.extension import Extension
+from setuptools.dist import Distribution, Feature, _get_unpatched
+from setuptools.depends import Require
__all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
'find_packages'
]
+__version__ = setuptools.version.__version__
+
bootstrap_install_from = None
# If we run 2to3 on .py files, should we also convert docstrings?
@@ -37,10 +42,14 @@ def find_packages(where='.', exclude=()):
where,prefix = stack.pop(0)
for name in os.listdir(where):
fn = os.path.join(where,name)
- if ('.' not in name and os.path.isdir(fn) and
- os.path.isfile(os.path.join(fn,'__init__.py'))
- ):
- out.append(prefix+name); stack.append((fn,prefix+name+'.'))
+ looks_like_package = (
+ '.' not in name
+ and os.path.isdir(fn)
+ and os.path.isfile(os.path.join(fn, '__init__.py'))
+ )
+ if looks_like_package:
+ out.append(prefix+name)
+ stack.append((fn, prefix+name+'.'))
for pat in list(exclude)+['ez_setup']:
from fnmatch import fnmatchcase
out = [item for item in out if not fnmatchcase(item,pat)]
@@ -67,7 +76,6 @@ class Command(_Command):
setattr(cmd,k,v) # update command with keywords
return cmd
-import distutils.core
distutils.core.Command = Command # we can't patch distutils.cmd, alas
def findall(dir = os.curdir):
@@ -83,12 +91,8 @@ def findall(dir = os.curdir):
all_files.extend(filter(os.path.isfile, files))
return all_files
-import distutils.filelist
distutils.filelist.findall = findall # fix findall bug in distutils.
# sys.dont_write_bytecode was introduced in Python 2.6.
-if ((hasattr(sys, "dont_write_bytecode") and sys.dont_write_bytecode) or
- (not hasattr(sys, "dont_write_bytecode") and os.environ.get("PYTHONDONTWRITEBYTECODE"))):
- _dont_write_bytecode = True
-else:
- _dont_write_bytecode = False
+_dont_write_bytecode = getattr(sys, 'dont_write_bytecode',
+ bool(os.environ.get("PYTHONDONTWRITEBYTECODE")))
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 6a45e596..6ce19fa4 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -23,6 +23,9 @@ import stat
import random
import platform
import textwrap
+import warnings
+import site
+import struct
from glob import glob
from distutils import log, dir_util
@@ -72,22 +75,19 @@ __all__ = [
'main', 'get_exe_prefixes',
]
-import site
HAS_USER_SITE = not sys.version < "2.6" and site.ENABLE_USER_SITE
-import struct
def is_64bit():
return struct.calcsize("P") == 8
def samefile(p1, p2):
- if hasattr(os.path,'samefile') and (
- os.path.exists(p1) and os.path.exists(p2)
- ):
- return os.path.samefile(p1,p2)
- return (
- os.path.normpath(os.path.normcase(p1)) ==
- os.path.normpath(os.path.normcase(p2))
- )
+ both_exist = os.path.exists(p1) and os.path.exists(p2)
+ use_samefile = hasattr(os.path, 'samefile') and both_exist
+ if use_samefile:
+ return os.path.samefile(p1, p2)
+ norm_p1 = os.path.normpath(os.path.normcase(p1))
+ norm_p2 = os.path.normpath(os.path.normcase(p2))
+ return norm_p1 == norm_p2
if sys.version_info <= (3,):
def _to_ascii(s):
@@ -124,9 +124,6 @@ class easy_install(Command):
("always-copy", "a", "Copy all needed packages to install dir"),
("index-url=", "i", "base URL of Python Package Index"),
("find-links=", "f", "additional URL(s) to search for packages"),
- ("delete-conflicting", "D", "no longer needed; don't use this"),
- ("ignore-conflicts-at-my-risk", None,
- "no longer needed; don't use this"),
("build-directory=", "b",
"download/extract/build in DIR; keep the results"),
('optimize=', 'O',
@@ -147,7 +144,7 @@ class easy_install(Command):
]
boolean_options = [
'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
- 'delete-conflicting', 'ignore-conflicts-at-my-risk', 'editable',
+ 'editable',
'no-deps', 'local-snapshots-ok', 'version'
]
@@ -196,8 +193,6 @@ class easy_install(Command):
# Options not specifiable via command line
self.package_index = None
self.pth_file = self.always_copy_from = None
- self.delete_conflicting = None
- self.ignore_conflicts_at_my_risk = None
self.site_dirs = None
self.installed_projects = {}
self.sitepy_installed = False
@@ -341,11 +336,6 @@ class easy_install(Command):
except ValueError:
raise DistutilsOptionError("--optimize must be 0, 1, or 2")
- if self.delete_conflicting and self.ignore_conflicts_at_my_risk:
- raise DistutilsOptionError(
- "Can't use both --delete-conflicting and "
- "--ignore-conflicts-at-my-risk at the same time"
- )
if self.editable and not self.build_directory:
raise DistutilsArgError(
"Must specify a build directory (-b) when using --editable"
@@ -410,12 +400,7 @@ class easy_install(Command):
return os.path.join(self.install_dir, "test-easy-install-%s" % pid)
def warn_deprecated_options(self):
- if self.delete_conflicting or self.ignore_conflicts_at_my_risk:
- log.warn(
- "Note: The -D, --delete-conflicting and"
- " --ignore-conflicts-at-my-risk no longer have any purpose"
- " and should not be used."
- )
+ pass
def check_site_dir(self):
"""Verify that self.install_dir is .pth-capable dir, if needed"""
@@ -516,7 +501,8 @@ Please make the appropriate changes for your system and try again.
else:
try:
f.write("import os; f = open(%r, 'w'); f.write('OK'); f.close()\n" % (ok_file,))
- f.close(); f=None
+ f.close()
+ f=None
executable = sys.executable
if os.name=='nt':
dirname,basename = os.path.split(executable)
@@ -535,9 +521,12 @@ Please make the appropriate changes for your system and try again.
)
return True
finally:
- if f: f.close()
- if os.path.exists(ok_file): os.unlink(ok_file)
- if os.path.exists(pth_file): os.unlink(pth_file)
+ if f:
+ f.close()
+ if os.path.exists(ok_file):
+ os.unlink(ok_file)
+ if os.path.exists(pth_file):
+ os.unlink(pth_file)
if not self.multi_version:
log.warn("TEST FAILED: %s does NOT support .pth files", instdir)
return False
@@ -651,7 +640,7 @@ Please make the appropriate changes for your system and try again.
for dist in dists:
self.process_distribution(spec, dist, deps)
else:
- dists = [self.check_conflicts(self.egg_distribution(download))]
+ dists = [self.egg_distribution(download)]
self.process_distribution(spec, dists[0], deps, "Using")
if spec is not None:
@@ -849,7 +838,6 @@ Please make the appropriate changes for your system and try again.
ensure_directory(destination)
dist = self.egg_distribution(egg_path)
- self.check_conflicts(dist)
if not samefile(egg_path, destination):
if os.path.isdir(destination) and not os.path.islink(destination):
dir_util.remove_tree(destination, dry_run=self.dry_run)
@@ -893,10 +881,10 @@ Please make the appropriate changes for your system and try again.
# Convert the .exe to an unpacked egg
egg_path = dist.location = os.path.join(tmpdir, dist.egg_name()+'.egg')
egg_tmp = egg_path + '.tmp'
- egg_info = os.path.join(egg_tmp, 'EGG-INFO')
- pkg_inf = os.path.join(egg_info, 'PKG-INFO')
+ _egg_info = os.path.join(egg_tmp, 'EGG-INFO')
+ pkg_inf = os.path.join(_egg_info, 'PKG-INFO')
ensure_directory(pkg_inf) # make sure EGG-INFO dir exists
- dist._provider = PathMetadata(egg_tmp, egg_info) # XXX
+ dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX
self.exe_to_egg(dist_filename, egg_tmp)
# Write EGG-INFO/PKG-INFO
@@ -907,7 +895,7 @@ Please make the appropriate changes for your system and try again.
if k != 'target_version':
f.write('%s: %s\n' % (k.replace('_','-').title(), v))
f.close()
- script_dir = os.path.join(egg_info,'scripts')
+ script_dir = os.path.join(_egg_info,'scripts')
self.delete_blockers( # delete entry-point scripts to avoid duping
[os.path.join(script_dir,args[0]) for args in get_script_args(dist)]
)
@@ -968,82 +956,6 @@ Please make the appropriate changes for your system and try again.
f.write('\n'.join(locals()[name])+'\n')
f.close()
- def check_conflicts(self, dist):
- """Verify that there are no conflicting "old-style" packages"""
-
- return dist # XXX temporarily disable until new strategy is stable
- from imp import find_module, get_suffixes
- from glob import glob
-
- blockers = []
- names = dict.fromkeys(dist._get_metadata('top_level.txt')) # XXX private attr
-
- exts = {'.pyc':1, '.pyo':1} # get_suffixes() might leave one out
- for ext,mode,typ in get_suffixes():
- exts[ext] = 1
-
- for path,files in expand_paths([self.install_dir]+self.all_site_dirs):
- for filename in files:
- base,ext = os.path.splitext(filename)
- if base in names:
- if not ext:
- # no extension, check for package
- try:
- f, filename, descr = find_module(base, [path])
- except ImportError:
- continue
- else:
- if f: f.close()
- if filename not in blockers:
- blockers.append(filename)
- elif ext in exts and base!='site': # XXX ugh
- blockers.append(os.path.join(path,filename))
- if blockers:
- self.found_conflicts(dist, blockers)
-
- return dist
-
- def found_conflicts(self, dist, blockers):
- if self.delete_conflicting:
- log.warn("Attempting to delete conflicting packages:")
- return self.delete_blockers(blockers)
-
- msg = """\
--------------------------------------------------------------------------
-CONFLICT WARNING:
-
-The following modules or packages have the same names as modules or
-packages being installed, and will be *before* the installed packages in
-Python's search path. You MUST remove all of the relevant files and
-directories before you will be able to use the package(s) you are
-installing:
-
- %s
-
-""" % '\n '.join(blockers)
-
- if self.ignore_conflicts_at_my_risk:
- msg += """\
-(Note: you can run EasyInstall on '%s' with the
---delete-conflicting option to attempt deletion of the above files
-and/or directories.)
-""" % dist.project_name
- else:
- msg += """\
-Note: you can attempt this installation again with EasyInstall, and use
-either the --delete-conflicting (-D) option or the
---ignore-conflicts-at-my-risk option, to either delete the above files
-and directories, or to ignore the conflicts, respectively. Note that if
-you ignore the conflicts, the installed package(s) may not work.
-"""
- msg += """\
--------------------------------------------------------------------------
-"""
- sys.stderr.write(msg)
- sys.stderr.flush()
- if not self.ignore_conflicts_at_my_risk:
- raise DistutilsError("Installation aborted due to conflicts")
-
def installation_report(self, req, dist, what="Installed"):
"""Helpful installation message for display to package users"""
msg = "\n%(what)s %(eggloc)s%(extras)s"
@@ -1261,7 +1173,7 @@ Here are some of your options for correcting the problem:
https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations
Please make the appropriate changes for your system and try again."""
- return template % self.install_dir, os.environ.get('PYTHONPATH','')
+ return template % (self.install_dir, os.environ.get('PYTHONPATH',''))
def install_site_py(self):
"""Make sure there's a site.py in the target dir, if needed"""
@@ -1833,19 +1745,22 @@ class WindowsScriptWriter(ScriptWriter):
"""
Get a script writer suitable for Windows
"""
- # for compatibility, return the writer that creates exe launchers
- # unless the SETUPTOOLS_USE_PYLAUNCHER is set, indicating
- # future behavior.
- use_legacy = 'SETUPTOOLS_USE_PYLAUNCHER' not in os.environ
- if use_legacy:
- return WindowsLauncherScriptWriter
- return cls
+ writer_lookup = dict(
+ executable=WindowsExecutableLauncherWriter,
+ natural=cls,
+ )
+ # for compatibility, use the executable launcher by default
+ launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')
+ return writer_lookup[launcher]
@classmethod
def _get_script_args(cls, type_, name, header, script_text):
"For Windows, add a .py extension"
- ext = dict(console='.py', gui='.pyw')[type_]
- old = ['.py', '.pyc', '.pyo', '.pyw', '.exe']
+ ext = dict(console='.pya', gui='.pyw')[type_]
+ if ext not in os.environ['PATHEXT'].lower().split(';'):
+ warnings.warn("%s not listed in PATHEXT; scripts will not be "
+ "recognized as executables." % ext, UserWarning)
+ old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
old.remove(ext)
header = cls._adjust_header(type_, header)
blockers = [name+x for x in old]
@@ -1870,7 +1785,7 @@ class WindowsScriptWriter(ScriptWriter):
return new_header
-class WindowsLauncherScriptWriter(WindowsScriptWriter):
+class WindowsExecutableLauncherWriter(WindowsScriptWriter):
@classmethod
def _get_script_args(cls, type_, name, header, script_text):
"""
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 642687b2..a0ba5305 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -9,12 +9,14 @@ from distutils.errors import *
from distutils import log
from setuptools.command.sdist import sdist
from setuptools.compat import basestring
+from setuptools import svn_utils
from distutils.util import convert_path
from distutils.filelist import FileList as _FileList
from pkg_resources import parse_requirements, safe_name, parse_version, \
safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename
from setuptools.command.sdist import walk_revctrl
+
class egg_info(Command):
description = "create a distribution's .egg-info directory"
@@ -213,42 +215,9 @@ class egg_info(Command):
@staticmethod
def get_svn_revision():
- revision = 0
- urlre = re.compile('url="([^"]+)"')
- revre = re.compile('committed-rev="(\d+)"')
-
- for base,dirs,files in os.walk(os.curdir):
- if '.svn' not in dirs:
- dirs[:] = []
- continue # no sense walking uncontrolled subdirs
- dirs.remove('.svn')
- f = open(os.path.join(base,'.svn','entries'))
- data = f.read()
- f.close()
+ return str(svn_utils.SvnInfo.load(os.curdir).get_revision())
+
- if data.startswith('<?xml'):
- dirurl = urlre.search(data).group(1) # get repository URL
- localrev = max([int(m.group(1)) for m in revre.finditer(data)]+[0])
- else:
- try: svnver = int(data.splitlines()[0])
- except: svnver=-1
- if svnver<8:
- log.warn("unrecognized .svn/entries format; skipping %s", base)
- dirs[:] = []
- continue
-
- data = list(map(str.splitlines,data.split('\n\x0c\n')))
- del data[0][0] # get rid of the '8' or '9' or '10'
- dirurl = data[0][3]
- localrev = max([int(d[9]) for d in data if len(d)>9 and d[9]]+[0])
- if base==os.curdir:
- base_url = dirurl+'/' # save the root url
- elif not dirurl.startswith(base_url):
- dirs[:] = []
- continue # not part of the same svn tree, skip it
- revision = max(revision, localrev)
-
- return str(revision or get_pkg_info_revision())
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index 39cd6043..6249e75c 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -1,46 +1,16 @@
+import os
+import re
+import sys
+from glob import glob
+
+import pkg_resources
from distutils.command.sdist import sdist as _sdist
from distutils.util import convert_path
from distutils import log
-from glob import glob
-import os, re, sys, pkg_resources
-from glob import glob
+from setuptools import svn_utils
READMES = ('README', 'README.rst', 'README.txt')
-entities = [
- ("&lt;","<"), ("&gt;", ">"), ("&quot;", '"'), ("&apos;", "'"),
- ("&amp;", "&")
-]
-
-def unescape(data):
- for old,new in entities:
- data = data.replace(old,new)
- return data
-
-def re_finder(pattern, postproc=None):
- def find(dirname, filename):
- f = open(filename,'rU')
- data = f.read()
- f.close()
- for match in pattern.finditer(data):
- path = match.group(1)
- if postproc:
- path = postproc(path)
- yield joinpath(dirname,path)
- return find
-
-def joinpath(prefix,suffix):
- if not prefix:
- return suffix
- return os.path.join(prefix,suffix)
-
-
-
-
-
-
-
-
def walk_revctrl(dirname=''):
"""Find all files under revision control"""
@@ -48,80 +18,51 @@ def walk_revctrl(dirname=''):
for item in ep.load()(dirname):
yield item
-def _default_revctrl(dirname=''):
- for path, finder in finders:
- path = joinpath(dirname,path)
+
+#TODO will need test case
+class re_finder(object):
+
+ def __init__(self, path, pattern, postproc=None):
+ self.pattern = pattern
+ self.postproc = postproc
+ self.path = convert_path(path)
+
+ def _finder(self, dirname, filename):
+ f = open(filename,'rU')
+ try:
+ data = f.read()
+ finally:
+ f.close()
+ for match in self.pattern.finditer(data):
+ path = match.group(1)
+ if postproc:
+ #postproc used to be used when the svn finder
+ #was an re_finder for calling unescape
+ path = postproc(path)
+ yield svn_utils.joinpath(dirname,path)
+ def __call__(self, dirname=''):
+ path = svn_utils.joinpath(dirname, self.path)
+
if os.path.isfile(path):
- for path in finder(dirname,path):
+ for path in self._finder(dirname,path):
if os.path.isfile(path):
yield path
elif os.path.isdir(path):
- for item in _default_revctrl(path):
+ for item in self.find(path):
yield item
-def externals_finder(dirname, filename):
- """Find any 'svn:externals' directories"""
- found = False
- f = open(filename,'rt')
- for line in iter(f.readline, ''): # can't use direct iter!
- parts = line.split()
- if len(parts)==2:
- kind,length = parts
- data = f.read(int(length))
- if kind=='K' and data=='svn:externals':
- found = True
- elif kind=='V' and found:
- f.close()
- break
- else:
- f.close()
- return
-
- for line in data.splitlines():
- parts = line.split()
- if parts:
- yield joinpath(dirname, parts[0])
-
-
-entries_pattern = re.compile(r'name="([^"]+)"(?![^>]+deleted="true")', re.I)
-
-def entries_finder(dirname, filename):
- f = open(filename,'rU')
- data = f.read()
- f.close()
- if data.startswith('<?xml'):
- for match in entries_pattern.finditer(data):
- yield joinpath(dirname,unescape(match.group(1)))
- else:
- svnver=-1
- try: svnver = int(data.splitlines()[0])
- except: pass
- if svnver<8:
- log.warn("unrecognized .svn/entries format in %s", os.path.abspath(dirname))
- return
- for record in map(str.splitlines, data.split('\n\x0c\n')[1:]):
- # subversion 1.6/1.5/1.4
- if not record or len(record)>=6 and record[5]=="delete":
- continue # skip deleted
- yield joinpath(dirname, record[0])
-
-
-finders = [
- (convert_path('CVS/Entries'),
- re_finder(re.compile(r"^\w?/([^/]+)/", re.M))),
- (convert_path('.svn/entries'), entries_finder),
- (convert_path('.svn/dir-props'), externals_finder),
- (convert_path('.svn/dir-prop-base'), externals_finder), # svn 1.4
-]
-
-
-
-
-
-
+def _default_revctrl(dirname=''):
+ 'Primary svn_cvs entry point'
+ for finder in finders:
+ for item in finder(dirname):
+ yield item
+finders = [
+ re_finder('CVS/Entries', re.compile(r"^\w?/([^/]+)/", re.M)),
+ svn_utils.svn_finder,
+]
@@ -179,11 +120,12 @@ class sdist(_sdist):
# Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle
# has been fixed, so only override the method if we're using an earlier
# Python.
- if (
- sys.version_info < (2,7,2)
- or (3,0) <= sys.version_info < (3,1,4)
- or (3,2) <= sys.version_info < (3,2,1)
- ):
+ has_leaky_handle = (
+ sys.version_info < (2,7,2)
+ or (3,0) <= sys.version_info < (3,1,4)
+ or (3,2) <= sys.version_info < (3,2,1)
+ )
+ if has_leaky_handle:
read_template = __read_template_hack
def add_defaults(self):
@@ -248,7 +190,6 @@ class sdist(_sdist):
"standard file not found: should have one of " +', '.join(READMES)
)
-
def make_release_tree(self, base_dir, files):
_sdist.make_release_tree(self, base_dir, files)
@@ -295,10 +236,3 @@ class sdist(_sdist):
continue
self.filelist.append(line)
manifest.close()
-
-
-
-
-
-
-#
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
index 02d955ed..a6eff385 100755
--- a/setuptools/command/upload.py
+++ b/setuptools/command/upload.py
@@ -2,10 +2,10 @@
Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
-from distutils.errors import *
+from distutils import errors
+from distutils import log
from distutils.core import Command
from distutils.spawn import spawn
-from distutils import log
try:
from hashlib import md5
except ImportError:
@@ -45,7 +45,7 @@ class upload(Command):
def finalize_options(self):
if self.identity and not self.sign:
- raise DistutilsOptionError(
+ raise errors.DistutilsOptionError(
"Must use --sign for --identity to have meaning"
)
if 'HOME' in os.environ:
@@ -68,7 +68,7 @@ class upload(Command):
def run(self):
if not self.distribution.dist_files:
- raise DistutilsOptionError("No dist file created in earlier command")
+ raise errors.DistutilsOptionError("No dist file created in earlier command")
for command, pyversion, filename in self.distribution.dist_files:
self.upload_file(command, pyversion, filename)
@@ -119,10 +119,10 @@ class upload(Command):
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = '\n--' + boundary
end_boundary = sep_boundary + '--'
- body = StringIO.StringIO()
+ body = StringIO()
for key, value in data.items():
# handle multiple entries for the same name
- if type(value) != type([]):
+ if not isinstance(value, list):
value = [value]
for value in value:
if type(value) is tuple:
@@ -158,7 +158,6 @@ class upload(Command):
raise AssertionError("unsupported schema " + schema)
data = ''
- loglevel = log.INFO
try:
http.connect()
http.putrequest("POST", url)
diff --git a/setuptools/compat.py b/setuptools/compat.py
index 3a961c0e..bbc98d66 100644
--- a/setuptools/compat.py
+++ b/setuptools/compat.py
@@ -26,7 +26,8 @@ if sys.version_info[0] < 3:
reduce = reduce
unichr = unichr
unicode = unicode
- from urllib import url2pathname
+ bytes = str
+ from urllib import url2pathname, splittag
import urllib2
from urllib2 import urlopen, HTTPError, URLError, unquote, splituser
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
@@ -69,12 +70,13 @@ else:
from functools import reduce
unichr = chr
unicode = str
+ bytes = bytes
from urllib.error import HTTPError, URLError
import urllib.request as urllib2
from urllib.request import urlopen, url2pathname
from urllib.parse import (
urlparse, urlunparse, unquote, splituser, urljoin, urlsplit,
- urlunsplit,
+ urlunsplit, splittag,
)
xrange = range
filterfalse = itertools.filterfalse
@@ -84,7 +86,7 @@ else:
globs = globals()
if locs is None:
locs = globs
- f = open(fn)
+ f = open(fn, 'rb')
try:
source = f.read()
finally:
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 01889215..c5b02f99 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -1,17 +1,19 @@
__all__ = ['Distribution']
import re
+import os
import sys
+import warnings
+import distutils.log
+import distutils.core
+import distutils.cmd
from distutils.core import Distribution as _Distribution
+from distutils.errors import (DistutilsOptionError, DistutilsPlatformError,
+ DistutilsSetupError)
+
from setuptools.depends import Require
-from setuptools.command.install import install
-from setuptools.command.sdist import sdist
-from setuptools.command.install_lib import install_lib
from setuptools.compat import numeric_types, basestring
-from distutils.errors import DistutilsOptionError, DistutilsPlatformError
-from distutils.errors import DistutilsSetupError
-import setuptools, pkg_resources, distutils.core, distutils.dist, distutils.cmd
-import os, distutils.log
+import pkg_resources
def _get_unpatched(cls):
"""Protect against re-patching the distutils if reloaded
@@ -132,38 +134,6 @@ def check_packages(dist, attr, value):
"WARNING: %r not a valid package name; please use only"
".-separated package names in setup.py", pkgname
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
class Distribution(_Distribution):
@@ -194,7 +164,8 @@ class Distribution(_Distribution):
EasyInstall and requests one of your extras, the corresponding
additional requirements will be installed if needed.
- 'features' -- a dictionary mapping option names to 'setuptools.Feature'
+ 'features' **deprecated** -- a dictionary mapping option names to
+ 'setuptools.Feature'
objects. Features are a portion of the distribution that can be
included or excluded based on user options, inter-feature dependencies,
and availability on the current system. Excluded features are omitted
@@ -248,10 +219,13 @@ class Distribution(_Distribution):
dist._version = pkg_resources.safe_version(str(attrs['version']))
self._patched_dist = dist
- def __init__ (self, attrs=None):
+ def __init__(self, attrs=None):
have_package_data = hasattr(self, "package_data")
if not have_package_data:
self.package_data = {}
+ _attrs_dict = attrs or {}
+ if 'features' in _attrs_dict or 'require_features' in _attrs_dict:
+ Feature.warn_deprecated()
self.require_features = []
self.features = {}
self.dist_files = []
@@ -362,23 +336,6 @@ class Distribution(_Distribution):
self.global_options = self.feature_options = go + self.global_options
self.negative_opt = self.feature_negopt = no
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
def _finalize_features(self):
"""Add/remove features and resolve dependencies between them"""
@@ -396,7 +353,6 @@ class Distribution(_Distribution):
feature.exclude_from(self)
self._set_feature(name,0)
-
def get_command_class(self, command):
"""Pluggable version of get_command_class()"""
if command in self.cmdclass:
@@ -416,10 +372,6 @@ class Distribution(_Distribution):
self.cmdclass[ep.name] = cmdclass
return _Distribution.print_commands(self)
-
-
-
-
def _set_feature(self,name,status):
"""Set feature's inclusion status"""
setattr(self,self._feature_attrname(name),status)
@@ -434,8 +386,8 @@ class Distribution(_Distribution):
if self.feature_is_included(name)==0:
descr = self.features[name].description
raise DistutilsOptionError(
- descr + " is required, but was excluded or is not available"
- )
+ descr + " is required, but was excluded or is not available"
+ )
self.features[name].include_in(self)
self._set_feature(name,1)
@@ -483,7 +435,6 @@ class Distribution(_Distribution):
if p.name != package and not p.name.startswith(pfx)
]
-
def has_contents_for(self,package):
"""Return true if 'exclude_package(package)' would do something"""
@@ -493,15 +444,6 @@ class Distribution(_Distribution):
if p==package or p.startswith(pfx):
return True
-
-
-
-
-
-
-
-
-
def _exclude_misc(self,name,value):
"""Handle 'exclude()' for list/tuple attrs without a special handler"""
if not isinstance(value,sequence):
@@ -573,17 +515,6 @@ class Distribution(_Distribution):
)
list(map(self.exclude_package, packages))
-
-
-
-
-
-
-
-
-
-
-
def _parse_command_opts(self, parser, args):
# Remove --with-X/--without-X options when processing command args
self.global_options = self.__class__.global_options
@@ -610,21 +541,6 @@ class Distribution(_Distribution):
return nargs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
def get_cmdline_options(self):
"""Return a '{cmd: {opt:val}}' map of all command-line options
@@ -665,7 +581,6 @@ class Distribution(_Distribution):
return d
-
def iter_distribution_names(self):
"""Yield all packages, modules, and extension names in distribution"""
@@ -684,7 +599,6 @@ class Distribution(_Distribution):
name = name[:-6]
yield name
-
def handle_display_options(self, option_order):
"""If there were any non-global "display-only" options
(--help-commands or the metadata display options) on the command
@@ -726,26 +640,14 @@ for module in distutils.dist, distutils.core, distutils.cmd:
module.Distribution = Distribution
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
class Feature:
- """A subset of the distribution that can be excluded if unneeded/wanted
+ """
+ **deprecated** -- The `Feature` facility was never completely implemented
+ or supported, `has reported issues
+ <https://bitbucket.org/pypa/setuptools/issue/58>`_ and will be removed in
+ a future version.
+
+ A subset of the distribution that can be excluded if unneeded/wanted
Features are created using these keyword arguments:
@@ -794,9 +696,19 @@ class Feature:
Aside from the methods, the only feature attributes that distributions look
at are 'description' and 'optional'.
"""
+
+ @staticmethod
+ def warn_deprecated():
+ warnings.warn(
+ "Features are deprecated and will be removed in a future "
+ "version. See http://bitbucket.org/pypa/setuptools/65.",
+ DeprecationWarning,
+ stacklevel=3,
+ )
+
def __init__(self, description, standard=False, available=True,
- optional=True, require_features=(), remove=(), **extras
- ):
+ optional=True, require_features=(), remove=(), **extras):
+ self.warn_deprecated()
self.description = description
self.standard = standard
@@ -847,8 +759,6 @@ class Feature:
for f in self.require_features:
dist.include_feature(f)
-
-
def exclude_from(self,dist):
"""Ensure feature is excluded from distribution
@@ -865,8 +775,6 @@ class Feature:
for item in self.remove:
dist.exclude_package(item)
-
-
def validate(self,dist):
"""Verify that feature makes sense in context of distribution
@@ -886,7 +794,3 @@ class Feature:
" doesn't contain any packages or modules under %s"
% (self.description, item, item)
)
-
-
-
-
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 231311ec..26d1d4ad 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -23,6 +23,7 @@ from setuptools.compat import filterfalse
from fnmatch import translate
from setuptools.py24compat import hashlib
from setuptools.py24compat import wraps
+from setuptools.py26compat import strip_fragment
from setuptools.py27compat import get_all_headers
EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$')
@@ -344,7 +345,8 @@ class PackageIndex(Environment):
s = URL_SCHEME(url)
if (s and s.group(1).lower()=='file') or self.allows(urlparse(url)[1]):
return True
- msg = "\nLink to % s ***BLOCKED*** by --allow-hosts\n"
+ msg = ("\nNote: Bypassing %s (disallowed host; see "
+ "http://bit.ly/1dg9ijs for details).\n")
if fatal:
raise DistutilsError(msg % url)
else:
@@ -660,7 +662,7 @@ class PackageIndex(Environment):
fp, tfp, info = None, None, None
try:
checker = HashChecker.from_url(url)
- fp = self.open_url(url)
+ fp = self.open_url(strip_fragment(url))
if isinstance(fp, HTTPError):
raise DistutilsError(
"Can't download %s: %s %s" % (url, fp.code,fp.msg)
diff --git a/setuptools/py26compat.py b/setuptools/py26compat.py
new file mode 100644
index 00000000..738b0cc4
--- /dev/null
+++ b/setuptools/py26compat.py
@@ -0,0 +1,19 @@
+"""
+Compatibility Support for Python 2.6 and earlier
+"""
+
+import sys
+
+from setuptools.compat import splittag
+
+def strip_fragment(url):
+ """
+ In `Python 8280 <http://bugs.python.org/issue8280>`_, Python 2.7 and
+ later was patched to disregard the fragment when making URL requests.
+ Do the same for Python 2.6 and earlier.
+ """
+ url, fragment = splittag(url)
+ return url
+
+if sys.version_info >= (2,7):
+ strip_fragment = lambda x: x
diff --git a/setuptools/script template (dev).py b/setuptools/script template (dev).py
index 901790e7..b3fe209e 100644
--- a/setuptools/script template (dev).py
+++ b/setuptools/script template (dev).py
@@ -1,9 +1,11 @@
# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r
__requires__ = """%(spec)r"""
-from pkg_resources import require; require("""%(spec)r""")
+import sys
+from pkg_resources import require
+require("""%(spec)r""")
del require
__file__ = """%(dev_path)r"""
-try:
+if sys.version_info < (3, 0):
execfile(__file__)
-except NameError:
+else:
exec(compile(open(__file__).read(), __file__, 'exec'))
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
index f8a780a9..346188f2 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -44,6 +44,7 @@ is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection)
try:
from socket import create_connection
except ImportError:
+ from socket import error
_GLOBAL_DEFAULT_TIMEOUT = getattr(socket, '_GLOBAL_DEFAULT_TIMEOUT', object())
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
@@ -85,33 +86,74 @@ except ImportError:
try:
from ssl import CertificateError, match_hostname
except ImportError:
+ try:
+ from backports.ssl_match_hostname import CertificateError
+ from backports.ssl_match_hostname import match_hostname
+ except ImportError:
+ CertificateError = None
+ match_hostname = None
+
+if not CertificateError:
class CertificateError(ValueError):
pass
- def _dnsname_to_pat(dn, max_wildcards=1):
+if not match_hostname:
+ def _dnsname_match(dn, hostname, max_wildcards=1):
+ """Matching according to RFC 6125, section 6.4.3
+
+ http://tools.ietf.org/html/rfc6125#section-6.4.3
+ """
pats = []
- for frag in dn.split(r'.'):
- if frag.count('*') > max_wildcards:
- # Issue #17980: avoid denials of service by refusing more
- # than one wildcard per fragment. A survery of established
- # policy among SSL implementations showed it to be a
- # reasonable choice.
- raise CertificateError(
- "too many wildcards in certificate DNS name: " + repr(dn))
- if frag == '*':
- # When '*' is a fragment by itself, it matches a non-empty dotless
- # fragment.
- pats.append('[^.]+')
- else:
- # Otherwise, '*' matches any dotless fragment.
- frag = re.escape(frag)
- pats.append(frag.replace(r'\*', '[^.]*'))
- return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
+ if not dn:
+ return False
+
+ # Ported from python3-syntax:
+ # leftmost, *remainder = dn.split(r'.')
+ parts = dn.split(r'.')
+ leftmost = parts[0]
+ remainder = parts[1:]
+
+ wildcards = leftmost.count('*')
+ if wildcards > max_wildcards:
+ # Issue #17980: avoid denials of service by refusing more
+ # than one wildcard per fragment. A survey of established
+ # policy among SSL implementations showed it to be a
+ # reasonable choice.
+ raise CertificateError(
+ "too many wildcards in certificate DNS name: " + repr(dn))
+
+ # speed up common case w/o wildcards
+ if not wildcards:
+ return dn.lower() == hostname.lower()
+
+ # RFC 6125, section 6.4.3, subitem 1.
+ # The client SHOULD NOT attempt to match a presented identifier in which
+ # the wildcard character comprises a label other than the left-most label.
+ if leftmost == '*':
+ # When '*' is a fragment by itself, it matches a non-empty dotless
+ # fragment.
+ pats.append('[^.]+')
+ elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
+ # RFC 6125, section 6.4.3, subitem 3.
+ # The client SHOULD NOT attempt to match a presented identifier
+ # where the wildcard character is embedded within an A-label or
+ # U-label of an internationalized domain name.
+ pats.append(re.escape(leftmost))
+ else:
+ # Otherwise, '*' matches any dotless string, e.g. www*
+ pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
+
+ # add the remaining fragments, ignore any wildcards
+ for frag in remainder:
+ pats.append(re.escape(frag))
+
+ pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
+ return pat.match(hostname)
def match_hostname(cert, hostname):
"""Verify that *cert* (in decoded format as returned by
- SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
- are mostly followed, but IP addresses are not accepted for *hostname*.
+ SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
+ rules are followed, but IP addresses are not accepted for *hostname*.
CertificateError is raised on failure. On success, the function
returns nothing.
@@ -122,7 +164,7 @@ except ImportError:
san = cert.get('subjectAltName', ())
for key, value in san:
if key == 'DNS':
- if _dnsname_to_pat(value).match(hostname):
+ if _dnsname_match(value, hostname):
return
dnsnames.append(value)
if not dnsnames:
@@ -133,7 +175,7 @@ except ImportError:
# XXX according to RFC 2818, the most specific Common Name
# must be used.
if key == 'commonName':
- if _dnsname_to_pat(value).match(hostname):
+ if _dnsname_match(value, hostname):
return
dnsnames.append(value)
if len(dnsnames) > 1:
@@ -194,6 +236,12 @@ class VerifyingHTTPSConn(HTTPSConnection):
sock = create_connection(
(self.host, self.port), getattr(self,'source_address',None)
)
+
+ # Handle the socket if a (proxy) tunnel is present
+ if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None):
+ self.sock = sock
+ self._tunnel()
+
self.sock = ssl.wrap_socket(
sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
)
diff --git a/setuptools/svn_utils.py b/setuptools/svn_utils.py
new file mode 100644
index 00000000..224d11ea
--- /dev/null
+++ b/setuptools/svn_utils.py
@@ -0,0 +1,534 @@
+import os
+import re
+import sys
+from distutils import log
+import xml.dom.pulldom
+import shlex
+import locale
+import unicodedata
+import warnings
+from setuptools.compat import unicode, bytes
+from xml.sax.saxutils import unescape
+
+try:
+ import urlparse
+except ImportError:
+ import urllib.parse as urlparse
+
+from subprocess import Popen as _Popen, PIPE as _PIPE
+
+#NOTE: Use of the command line options require SVN 1.3 or newer (December 2005)
+# and SVN 1.3 hasn't been supported by the developers since mid 2008.
+
+#subprocess is called several times with shell=(sys.platform=='win32')
+#see the follow for more information:
+# http://bugs.python.org/issue8557
+# http://stackoverflow.com/questions/5658622/
+# python-subprocess-popen-environment-path
+
+
+def _run_command(args, stdout=_PIPE, stderr=_PIPE, encoding=None, stream=0):
+ #regarding the shell argument, see: http://bugs.python.org/issue8557
+ try:
+ proc = _Popen(args, stdout=stdout, stderr=stderr,
+ shell=(sys.platform == 'win32'))
+
+ data = proc.communicate()[stream]
+ except OSError:
+ return 1, ''
+
+ #doubled checked and
+ data = decode_as_string(data, encoding)
+
+ #communciate calls wait()
+ return proc.returncode, data
+
+
+def _get_entry_schedule(entry):
+ schedule = entry.getElementsByTagName('schedule')[0]
+ return "".join([t.nodeValue
+ for t in schedule.childNodes
+ if t.nodeType == t.TEXT_NODE])
+
+
+def _get_target_property(target):
+ property_text = target.getElementsByTagName('property')[0]
+ return "".join([t.nodeValue
+ for t in property_text.childNodes
+ if t.nodeType == t.TEXT_NODE])
+
+
+def _get_xml_data(decoded_str):
+ if sys.version_info < (3, 0):
+ #old versions want an encoded string
+ data = decoded_str.encode('utf-8')
+ else:
+ data = decoded_str
+ return data
+
+
+def joinpath(prefix, *suffix):
+ if not prefix or prefix == '.':
+ return os.path.join(*suffix)
+ return os.path.join(prefix, *suffix)
+
+
+def decode_as_string(text, encoding=None):
+ """
+ Decode the console or file output explicitly using getpreferredencoding.
+ The text paraemeter should be a encoded string, if not no decode occurs
+ If no encoding is given, getpreferredencoding is used. If encoding is
+ specified, that is used instead. This would be needed for SVN --xml
+ output. Unicode is explicitly put in composed NFC form.
+
+ --xml should be UTF-8 (SVN Issue 2938) the discussion on the Subversion
+ DEV List from 2007 seems to indicate the same.
+ """
+ #text should be a byte string
+
+ if encoding is None:
+ encoding = locale.getpreferredencoding()
+
+ if not isinstance(text, unicode):
+ text = text.decode(encoding)
+
+ text = unicodedata.normalize('NFC', text)
+
+ return text
+
+
+def parse_dir_entries(decoded_str):
+ '''Parse the entries from a recursive info xml'''
+ doc = xml.dom.pulldom.parseString(_get_xml_data(decoded_str))
+ entries = list()
+
+ for event, node in doc:
+ if event == 'START_ELEMENT' and node.nodeName == 'entry':
+ doc.expandNode(node)
+ if not _get_entry_schedule(node).startswith('delete'):
+ entries.append((node.getAttribute('path'),
+ node.getAttribute('kind')))
+
+ return entries[1:] # do not want the root directory
+
+
+def parse_externals_xml(decoded_str, prefix=''):
+ '''Parse a propget svn:externals xml'''
+ prefix = os.path.normpath(prefix)
+ prefix = os.path.normcase(prefix)
+
+ doc = xml.dom.pulldom.parseString(_get_xml_data(decoded_str))
+ externals = list()
+
+ for event, node in doc:
+ if event == 'START_ELEMENT' and node.nodeName == 'target':
+ doc.expandNode(node)
+ path = os.path.normpath(node.getAttribute('path'))
+
+ if os.path.normcase(path).startswith(prefix):
+ path = path[len(prefix)+1:]
+
+ data = _get_target_property(node)
+ #data should be decoded already
+ for external in parse_external_prop(data):
+ externals.append(joinpath(path, external))
+
+ return externals # do not want the root directory
+
+
+def parse_external_prop(lines):
+ """
+ Parse the value of a retrieved svn:externals entry.
+
+ possible token setups (with quotng and backscaping in laters versions)
+ URL[@#] EXT_FOLDERNAME
+ [-r#] URL EXT_FOLDERNAME
+ EXT_FOLDERNAME [-r#] URL
+ """
+ externals = []
+ for line in lines.splitlines():
+ line = line.lstrip() # there might be a "\ "
+ if not line:
+ continue
+
+ if sys.version_info < (3, 0):
+ #shlex handles NULLs just fine and shlex in 2.7 tries to encode
+ #as ascii automatiically
+ line = line.encode('utf-8')
+ line = shlex.split(line)
+ if sys.version_info < (3, 0):
+ line = [x.decode('utf-8') for x in line]
+
+ #EXT_FOLDERNAME is either the first or last depending on where
+ #the URL falls
+ if urlparse.urlsplit(line[-1])[0]:
+ external = line[0]
+ else:
+ external = line[-1]
+
+ external = decode_as_string(external, encoding="utf-8")
+ externals.append(os.path.normpath(external))
+
+ return externals
+
+
+def parse_prop_file(filename, key):
+ found = False
+ f = open(filename, 'rt')
+ data = ''
+ try:
+ for line in iter(f.readline, ''): # can't use direct iter!
+ parts = line.split()
+ if len(parts) == 2:
+ kind, length = parts
+ data = f.read(int(length))
+ if kind == 'K' and data == key:
+ found = True
+ elif kind == 'V' and found:
+ break
+ finally:
+ f.close()
+
+ return data
+
+
+class SvnInfo(object):
+ '''
+ Generic svn_info object. No has little knowledge of how to extract
+ information. Use cls.load to instatiate according svn version.
+
+ Paths are not filesystem encoded.
+ '''
+
+ @staticmethod
+ def get_svn_version():
+ code, data = _run_command(['svn', '--version', '--quiet'])
+ if code == 0 and data:
+ return data.strip()
+ else:
+ return ''
+
+ #svnversion return values (previous implementations return max revision)
+ # 4123:4168 mixed revision working copy
+ # 4168M modified working copy
+ # 4123S switched working copy
+ # 4123:4168MS mixed revision, modified, switched working copy
+ revision_re = re.compile(r'(?:([\-0-9]+):)?(\d+)([a-z]*)\s*$', re.I)
+
+ @classmethod
+ def load(cls, dirname=''):
+ normdir = os.path.normpath(dirname)
+ code, data = _run_command(['svn', 'info', normdir])
+ # Must check for some contents, as some use empty directories
+ # in testcases
+ svn_dir = os.path.join(normdir, '.svn')
+ has_svn = (os.path.isfile(os.path.join(svn_dir, 'entries')) or
+ os.path.isfile(os.path.join(svn_dir, 'dir-props')) or
+ os.path.isfile(os.path.join(svn_dir, 'dir-prop-base')))
+
+ svn_version = tuple(cls.get_svn_version().split('.'))
+
+ try:
+ base_svn_version = tuple(int(x) for x in svn_version[:2])
+ except ValueError:
+ base_svn_version = tuple()
+
+ if has_svn and (code or not base_svn_version
+ or base_svn_version < (1, 3)):
+ warnings.warn(("No SVN 1.3+ command found: falling back "
+ "on pre 1.7 .svn parsing"), DeprecationWarning)
+ return SvnFileInfo(dirname)
+ elif not has_svn:
+ return SvnInfo(dirname)
+ elif base_svn_version < (1, 5):
+ return Svn13Info(dirname)
+ else:
+ return Svn15Info(dirname)
+
+ def __init__(self, path=''):
+ self.path = path
+ self._entries = None
+ self._externals = None
+
+ def get_revision(self):
+ 'Retrieve the directory revision informatino using svnversion'
+ code, data = _run_command(['svnversion', '-c', self.path])
+ if code:
+ log.warn("svnversion failed")
+ return 0
+
+ parsed = self.revision_re.match(data)
+ if parsed:
+ return int(parsed.group(2))
+ else:
+ return 0
+
+ @property
+ def entries(self):
+ if self._entries is None:
+ self._entries = self.get_entries()
+ return self._entries
+
+ @property
+ def externals(self):
+ if self._externals is None:
+ self._externals = self.get_externals()
+ return self._externals
+
+ def iter_externals(self):
+ '''
+ Iterate over the svn:external references in the repository path.
+ '''
+ for item in self.externals:
+ yield item
+
+ def iter_files(self):
+ '''
+ Iterate over the non-deleted file entries in the repository path
+ '''
+ for item, kind in self.entries:
+ if kind.lower() == 'file':
+ yield item
+
+ def iter_dirs(self, include_root=True):
+ '''
+ Iterate over the non-deleted file entries in the repository path
+ '''
+ if include_root:
+ yield self.path
+ for item, kind in self.entries:
+ if kind.lower() == 'dir':
+ yield item
+
+ def get_entries(self):
+ return []
+
+ def get_externals(self):
+ return []
+
+
+class Svn13Info(SvnInfo):
+ def get_entries(self):
+ code, data = _run_command(['svn', 'info', '-R', '--xml', self.path],
+ encoding="utf-8")
+
+ if code:
+ log.debug("svn info failed")
+ return []
+
+ return parse_dir_entries(data)
+
+ def get_externals(self):
+ #Previous to 1.5 --xml was not supported for svn propget and the -R
+ #output format breaks the shlex compatible semantics.
+ cmd = ['svn', 'propget', 'svn:externals']
+ result = []
+ for folder in self.iter_dirs():
+ code, lines = _run_command(cmd + [folder], encoding="utf-8")
+ if code != 0:
+ log.warn("svn propget failed")
+ return []
+ #lines should a str
+ for external in parse_external_prop(lines):
+ if folder:
+ external = os.path.join(folder, external)
+ result.append(os.path.normpath(external))
+
+ return result
+
+
+class Svn15Info(Svn13Info):
+ def get_externals(self):
+ cmd = ['svn', 'propget', 'svn:externals', self.path, '-R', '--xml']
+ code, lines = _run_command(cmd, encoding="utf-8")
+ if code:
+ log.debug("svn propget failed")
+ return []
+ return parse_externals_xml(lines, prefix=os.path.abspath(self.path))
+
+
+class SvnFileInfo(SvnInfo):
+
+ def __init__(self, path=''):
+ super(SvnFileInfo, self).__init__(path)
+ self._directories = None
+ self._revision = None
+
+ def _walk_svn(self, base):
+ entry_file = joinpath(base, '.svn', 'entries')
+ if os.path.isfile(entry_file):
+ entries = SVNEntriesFile.load(base)
+ yield (base, False, entries.parse_revision())
+ for path in entries.get_undeleted_records():
+ path = decode_as_string(path)
+ path = joinpath(base, path)
+ if os.path.isfile(path):
+ yield (path, True, None)
+ elif os.path.isdir(path):
+ for item in self._walk_svn(path):
+ yield item
+
+ def _build_entries(self):
+ entries = list()
+
+ rev = 0
+ for path, isfile, dir_rev in self._walk_svn(self.path):
+ if isfile:
+ entries.append((path, 'file'))
+ else:
+ entries.append((path, 'dir'))
+ rev = max(rev, dir_rev)
+
+ self._entries = entries
+ self._revision = rev
+
+ def get_entries(self):
+ if self._entries is None:
+ self._build_entries()
+ return self._entries
+
+ def get_revision(self):
+ if self._revision is None:
+ self._build_entries()
+ return self._revision
+
+ def get_externals(self):
+ prop_files = [['.svn', 'dir-prop-base'],
+ ['.svn', 'dir-props']]
+ externals = []
+
+ for dirname in self.iter_dirs():
+ prop_file = None
+ for rel_parts in prop_files:
+ filename = joinpath(dirname, *rel_parts)
+ if os.path.isfile(filename):
+ prop_file = filename
+
+ if prop_file is not None:
+ ext_prop = parse_prop_file(prop_file, 'svn:externals')
+ #ext_prop should be utf-8 coming from svn:externals
+ ext_prop = decode_as_string(ext_prop, encoding="utf-8")
+ externals.extend(parse_external_prop(ext_prop))
+
+ return externals
+
+
+def svn_finder(dirname=''):
+ #combined externals due to common interface
+ #combined externals and entries due to lack of dir_props in 1.7
+ info = SvnInfo.load(dirname)
+ for path in info.iter_files():
+ yield path
+
+ for path in info.iter_externals():
+ sub_info = SvnInfo.load(path)
+ for sub_path in sub_info.iter_files():
+ yield sub_path
+
+
+class SVNEntriesFile(object):
+ def __init__(self, data):
+ self.data = data
+
+ @classmethod
+ def load(class_, base):
+ filename = os.path.join(base, '.svn', 'entries')
+ f = open(filename)
+ try:
+ result = SVNEntriesFile.read(f)
+ finally:
+ f.close()
+ return result
+
+ @classmethod
+ def read(class_, fileobj):
+ data = fileobj.read()
+ is_xml = data.startswith('<?xml')
+ class_ = [SVNEntriesFileText, SVNEntriesFileXML][is_xml]
+ return class_(data)
+
+ def parse_revision(self):
+ all_revs = self.parse_revision_numbers() + [0]
+ return max(all_revs)
+
+
+class SVNEntriesFileText(SVNEntriesFile):
+ known_svn_versions = {
+ '1.4.x': 8,
+ '1.5.x': 9,
+ '1.6.x': 10,
+ }
+
+ def __get_cached_sections(self):
+ return self.sections
+
+ def get_sections(self):
+ SECTION_DIVIDER = '\f\n'
+ sections = self.data.split(SECTION_DIVIDER)
+ sections = [x for x in map(str.splitlines, sections)]
+ try:
+ # remove the SVN version number from the first line
+ svn_version = int(sections[0].pop(0))
+ if not svn_version in self.known_svn_versions.values():
+ log.warn("Unknown subversion verson %d", svn_version)
+ except ValueError:
+ return
+ self.sections = sections
+ self.get_sections = self.__get_cached_sections
+ return self.sections
+
+ def is_valid(self):
+ return bool(self.get_sections())
+
+ def get_url(self):
+ return self.get_sections()[0][4]
+
+ def parse_revision_numbers(self):
+ revision_line_number = 9
+ rev_numbers = [
+ int(section[revision_line_number])
+ for section in self.get_sections()
+ if (len(section) > revision_line_number
+ and section[revision_line_number])
+ ]
+ return rev_numbers
+
+ def get_undeleted_records(self):
+ undeleted = lambda s: s and s[0] and (len(s) < 6 or s[5] != 'delete')
+ result = [
+ section[0]
+ for section in self.get_sections()
+ if undeleted(section)
+ ]
+ return result
+
+
+class SVNEntriesFileXML(SVNEntriesFile):
+ def is_valid(self):
+ return True
+
+ def get_url(self):
+ "Get repository URL"
+ urlre = re.compile('url="([^"]+)"')
+ return urlre.search(self.data).group(1)
+
+ def parse_revision_numbers(self):
+ revre = re.compile(r'committed-rev="(\d+)"')
+ return [
+ int(m.group(1))
+ for m in revre.finditer(self.data)
+ ]
+
+ def get_undeleted_records(self):
+ entries_pattern = \
+ re.compile(r'name="([^"]+)"(?![^>]+deleted="true")', re.I)
+ results = [
+ unescape(match.group(1))
+ for match in entries_pattern.finditer(self.data)
+ ]
+ return results
+
+
+if __name__ == '__main__':
+ for name in svn_finder(sys.argv[1]):
+ print(name)
diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py
new file mode 100644
index 00000000..bd2c53d2
--- /dev/null
+++ b/setuptools/tests/environment.py
@@ -0,0 +1,158 @@
+import os
+import zipfile
+import sys
+import tempfile
+import unittest
+import shutil
+import stat
+import unicodedata
+
+from subprocess import Popen as _Popen, PIPE as _PIPE
+
+
+def _extract(self, member, path=None, pwd=None):
+ """for zipfile py2.5 borrowed from cpython"""
+ if not isinstance(member, zipfile.ZipInfo):
+ member = self.getinfo(member)
+
+ if path is None:
+ path = os.getcwd()
+
+ return _extract_member(self, member, path, pwd)
+
+
+def _extract_from_zip(self, name, dest_path):
+ dest_file = open(dest_path, 'wb')
+ try:
+ dest_file.write(self.read(name))
+ finally:
+ dest_file.close()
+
+
+def _extract_member(self, member, targetpath, pwd):
+ """for zipfile py2.5 borrowed from cpython"""
+ # build the destination pathname, replacing
+ # forward slashes to platform specific separators.
+ # Strip trailing path separator, unless it represents the root.
+ if (targetpath[-1:] in (os.path.sep, os.path.altsep)
+ and len(os.path.splitdrive(targetpath)[1]) > 1):
+ targetpath = targetpath[:-1]
+
+ # don't include leading "/" from file name if present
+ if member.filename[0] == '/':
+ targetpath = os.path.join(targetpath, member.filename[1:])
+ else:
+ targetpath = os.path.join(targetpath, member.filename)
+
+ targetpath = os.path.normpath(targetpath)
+
+ # Create all upper directories if necessary.
+ upperdirs = os.path.dirname(targetpath)
+ if upperdirs and not os.path.exists(upperdirs):
+ os.makedirs(upperdirs)
+
+ if member.filename[-1] == '/':
+ if not os.path.isdir(targetpath):
+ os.mkdir(targetpath)
+ return targetpath
+
+ _extract_from_zip(self, member.filename, targetpath)
+
+ return targetpath
+
+
+def _remove_dir(target):
+
+ #on windows this seems to a problem
+ for dir_path, dirs, files in os.walk(target):
+ os.chmod(dir_path, stat.S_IWRITE)
+ for filename in files:
+ os.chmod(os.path.join(dir_path, filename), stat.S_IWRITE)
+ shutil.rmtree(target)
+
+
+class ZippedEnvironment(unittest.TestCase):
+
+ datafile = None
+ dataname = None
+ old_cwd = None
+
+ def setUp(self):
+ if not os.path.isfile(self.datafile):
+ self.old_cwd = None
+ return
+
+ self.old_cwd = os.getcwd()
+
+ self.temp_dir = tempfile.mkdtemp()
+ zip_file, source, target = [None, None, None]
+ try:
+ zip_file = zipfile.ZipFile(self.datafile)
+ for files in zip_file.namelist():
+ _extract(zip_file, files, self.temp_dir)
+ finally:
+ if zip_file:
+ zip_file.close()
+ del zip_file
+
+ os.chdir(os.path.join(self.temp_dir, self.dataname))
+
+ def tearDown(self):
+ try:
+ if self.old_cwd:
+ os.chdir(self.old_cwd)
+ _remove_dir(self.temp_dir)
+ except OSError:
+ #sigh?
+ pass
+
+
+def _which_dirs(cmd):
+ result = set()
+ for path in os.environ.get('PATH', '').split(os.pathsep):
+ filename = os.path.join(path, cmd)
+ if os.access(filename, os.X_OK):
+ result.add(path)
+ return result
+
+
+def run_setup_py(cmd, pypath=None, path=None,
+ data_stream=0, env=None):
+ """
+ Execution command for tests, separate from those used by the
+ code directly to prevent accidental behavior issues
+ """
+ if env is None:
+ env = dict()
+ for envname in os.environ:
+ env[envname] = os.environ[envname]
+
+ #override the python path if needed
+ if pypath is not None:
+ env["PYTHONPATH"] = pypath
+
+ #overide the execution path if needed
+ if path is not None:
+ env["PATH"] = path
+ if not env.get("PATH", ""):
+ env["PATH"] = _which_dirs("tar").union(_which_dirs("gzip"))
+ env["PATH"] = os.pathsep.join(env["PATH"])
+
+ cmd = [sys.executable, "setup.py"] + list(cmd)
+
+ #regarding the shell argument, see: http://bugs.python.org/issue8557
+ try:
+ proc = _Popen(cmd, stdout=_PIPE, stderr=_PIPE,
+ shell=(sys.platform == 'win32'), env=env)
+
+ data = proc.communicate()[data_stream]
+ except OSError:
+ return 1, ''
+
+ #decode the console string if needed
+ if hasattr(data, "decode"):
+ data = data.decode() # should use the preffered encoding
+ data = unicodedata.normalize('NFC', data)
+
+ #communciate calls wait()
+ return proc.returncode, data
diff --git a/setuptools/tests/script-with-bom.py b/setuptools/tests/script-with-bom.py
new file mode 100644
index 00000000..22dee0d2
--- /dev/null
+++ b/setuptools/tests/script-with-bom.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+result = 'passed'
diff --git a/setuptools/tests/svn_data/dummy.zip b/setuptools/tests/svn_data/dummy.zip
new file mode 100644
index 00000000..1347be53
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy13.zip b/setuptools/tests/svn_data/dummy13.zip
new file mode 100644
index 00000000..47764342
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy13.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy14.zip b/setuptools/tests/svn_data/dummy14.zip
new file mode 100644
index 00000000..02ed8cf0
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy14.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy15.zip b/setuptools/tests/svn_data/dummy15.zip
new file mode 100644
index 00000000..ed8daeeb
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy15.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy16.zip b/setuptools/tests/svn_data/dummy16.zip
new file mode 100644
index 00000000..b6e98d6c
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy16.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy17.zip b/setuptools/tests/svn_data/dummy17.zip
new file mode 100644
index 00000000..d96e1513
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy17.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy18.zip b/setuptools/tests/svn_data/dummy18.zip
new file mode 100644
index 00000000..a7267838
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy18.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn13_example.zip b/setuptools/tests/svn_data/svn13_example.zip
new file mode 100644
index 00000000..d85fb84f
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn13_ext_list.txt b/setuptools/tests/svn_data/svn13_ext_list.txt
new file mode 100644
index 00000000..0bb0f438
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_ext_list.txt
@@ -0,0 +1,3 @@
+third_party3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party2 -r3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party -r1 file:///C:/development/svn_example/repos/svn13/extra1
diff --git a/setuptools/tests/svn_data/svn13_ext_list.xml b/setuptools/tests/svn_data/svn13_ext_list.xml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_ext_list.xml
diff --git a/setuptools/tests/svn_data/svn13_info.xml b/setuptools/tests/svn_data/svn13_info.xml
new file mode 100644
index 00000000..5c96520a
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_info.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<info>
+<entry
+ kind="dir"
+ path="svn13_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<prop-updated>2013-07-13T15:33:23.187500Z</prop-updated>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:33:28.359375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:21.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:33:21.312500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<text-updated>2013-07-13T15:33:28.140625Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:33:28.359375Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn13_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<prop-updated>2013-07-13T15:33:26.187500Z</prop-updated>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:33:26.312500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:20.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:33:20.312500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:19.375000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:33:19.609375Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn14_example.zip b/setuptools/tests/svn_data/svn14_example.zip
new file mode 100644
index 00000000..57093c0b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn14_ext_list.txt b/setuptools/tests/svn_data/svn14_ext_list.txt
new file mode 100644
index 00000000..800a0965
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_ext_list.txt
@@ -0,0 +1,4 @@
+third_party3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party2 -r3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party -r1 file:///C:/development/svn_example/repos/svn13/extra1
+
diff --git a/setuptools/tests/svn_data/svn14_ext_list.xml b/setuptools/tests/svn_data/svn14_ext_list.xml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_ext_list.xml
diff --git a/setuptools/tests/svn_data/svn14_info.xml b/setuptools/tests/svn_data/svn14_info.xml
new file mode 100644
index 00000000..a896a77f
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_info.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<info>
+<entry
+ kind="dir"
+ path="svn14_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:14.406250Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:08.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:34:08.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<text-updated>2013-07-13T15:34:14.125000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:14.406250Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn14_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:34:12.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:07.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:34:07.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:06.250000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:34:06.531250Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn15_example.zip b/setuptools/tests/svn_data/svn15_example.zip
new file mode 100644
index 00000000..52a1d45b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn15_ext_list.txt b/setuptools/tests/svn_data/svn15_ext_list.txt
new file mode 100644
index 00000000..75fde4e6
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_ext_list.txt
@@ -0,0 +1,4 @@
+third_party3 file:///C:/development/svn_example/repos/svn15/extra1
+-r3 file:///C:/development/svn_example/repos/svn15/extra1 third_party2
+file:///C:/development/svn_example/repos/svn15/extra1@r1 third_party
+
diff --git a/setuptools/tests/svn_data/svn15_ext_list.xml b/setuptools/tests/svn_data/svn15_ext_list.xml
new file mode 100644
index 00000000..6950b3c5
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn15_example/folder">
+<property
+ name="svn:externals">third_party3 file:///C:/development/svn_example/repos/svn15/extra2
+-r3 file:///C:/development/svn_example/repos/svn15/extra2 third_party2
+file:///C:/development/svn_example/repos/svn15/extra2@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn15_example">
+<property
+ name="svn:externals">third_party3 file:///C:/development/svn_example/repos/svn15/extra1
+-r3 file:///C:/development/svn_example/repos/svn15/extra1 third_party2
+file:///C:/development/svn_example/repos/svn15/extra1@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn15_info.xml b/setuptools/tests/svn_data/svn15_info.xml
new file mode 100644
index 00000000..0b3550af
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_info.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<info>
+<entry
+ kind="dir"
+ path="svn15_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:49.562500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:43.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:34:43.484375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:49.125000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:49.562500Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn15_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:34:47.515625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:42.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:34:42.484375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:41.375000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:34:41.734375Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn16_example.zip b/setuptools/tests/svn_data/svn16_example.zip
new file mode 100644
index 00000000..e886b2af
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn16_ext_list.txt b/setuptools/tests/svn_data/svn16_ext_list.txt
new file mode 100644
index 00000000..3ca54893
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_ext_list.txt
@@ -0,0 +1,4 @@
+"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+'third party3b' file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party
diff --git a/setuptools/tests/svn_data/svn16_ext_list.xml b/setuptools/tests/svn_data/svn16_ext_list.xml
new file mode 100644
index 00000000..8ddaed0a
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn16_example/folder">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra2
+-r3 file:///C:/development/svn_example/repos/svn16/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra2@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn16_example">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn16_info.xml b/setuptools/tests/svn_data/svn16_info.xml
new file mode 100644
index 00000000..745469c9
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_info.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<info>
+<entry
+ kind="dir"
+ path="svn16_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:17.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:14.578125Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:14.906250Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:17.046875Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:17.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn16_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:16.406250Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:14.078125Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:14.421875Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:12.171875Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:13.906250Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn17_example.zip b/setuptools/tests/svn_data/svn17_example.zip
new file mode 100644
index 00000000..ba0e8823
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn17_ext_list.txt b/setuptools/tests/svn_data/svn17_ext_list.txt
new file mode 100644
index 00000000..a8b832a8
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_ext_list.txt
@@ -0,0 +1,4 @@
+"third party3" file:///C:/development/svn_example/repos/svn17/extra1
+'third party3b' file:///C:/development/svn_example/repos/svn17/extra1
+-r3 file:///C:/development/svn_example/repos/svn17/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn17/extra1@r1 third_party
diff --git a/setuptools/tests/svn_data/svn17_ext_list.xml b/setuptools/tests/svn_data/svn17_ext_list.xml
new file mode 100644
index 00000000..2879bb65
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn17_example">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn17_example/folder">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn17/extra2
+-r3 file:///C:/development/svn_example/repos/svn17/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn17/extra2@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn17_info.xml b/setuptools/tests/svn_data/svn17_info.xml
new file mode 100644
index 00000000..6cffeffd
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_info.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<info>
+<entry
+ kind="dir"
+ path="svn17_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:36.171875Z</date>
+</commit>
+</entry>
+<entry
+ path="svn17_example\folder"
+ revision="6"
+ kind="dir">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:34.859375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn17_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:32.812500Z</text-updated>
+<checksum>bc80eba9e7a10c0a571a4678c520bc9683f3bac2</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:33.109375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn17_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:32.343750Z</text-updated>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:32.687500Z</date>
+</commit>
+</entry>
+<entry
+ path="svn17_example\a file"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn17/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:33.187500Z</text-updated>
+<checksum>43785ab4b1816b49f242990883292813cd4f486c</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:33.515625Z</date>
+</commit>
+</entry>
+<entry
+ path="svn17_example\to_delete"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn17/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:36.171875Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn18_example.zip b/setuptools/tests/svn_data/svn18_example.zip
new file mode 100644
index 00000000..4362f8e9
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn18_ext_list.txt b/setuptools/tests/svn_data/svn18_ext_list.txt
new file mode 100644
index 00000000..c90a5f11
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_ext_list.txt
@@ -0,0 +1,4 @@
+"third party3" file:///C:/development/svn_example/repos/svn18/extra1
+'third party3b' file:///C:/development/svn_example/repos/svn18/extra1
+-r3 file:///C:/development/svn_example/repos/svn18/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn18/extra1@r1 third_party
diff --git a/setuptools/tests/svn_data/svn18_ext_list.xml b/setuptools/tests/svn_data/svn18_ext_list.xml
new file mode 100644
index 00000000..9b5e9e96
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn18_example">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn18_example/folder">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn18/extra2
+-r3 file:///C:/development/svn_example/repos/svn18/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn18/extra2@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn18_info.xml b/setuptools/tests/svn_data/svn18_info.xml
new file mode 100644
index 00000000..7ca55995
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_info.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<info>
+<entry
+ path="svn18_example"
+ revision="6"
+ kind="dir">
+<url>file:///C:/development/svn_example/repos/svn18/main</url>
+<relative-url>^/</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:57.796875Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn18_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/a%20file</url>
+<relative-url>^/a%20file</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.906250Z</text-updated>
+<checksum>43785ab4b1816b49f242990883292813cd4f486c</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:55.265625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn18_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/to_delete</url>
+<relative-url>^/to_delete</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:57.796875Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn18_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder</url>
+<relative-url>^/folder</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:56.750000Z</date>
+</commit>
+</entry>
+<entry
+ path="svn18_example\folder\quest.txt"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder/quest.txt</url>
+<relative-url>^/folder/quest.txt</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.484375Z</text-updated>
+<checksum>bc80eba9e7a10c0a571a4678c520bc9683f3bac2</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:54.843750Z</date>
+</commit>
+</entry>
+<entry
+ path="svn18_example\folder\lalala.txt"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder/lalala.txt</url>
+<relative-url>^/folder/lalala.txt</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.015625Z</text-updated>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:54.375000Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index f26a1f51..28682dfd 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -1,14 +1,20 @@
+
import os
+import sys
import tempfile
import shutil
import unittest
import pkg_resources
+import warnings
from setuptools.command import egg_info
+from setuptools.tests import environment
+from setuptools import svn_utils
ENTRIES_V10 = pkg_resources.resource_string(__name__, 'entries-v10')
"An entries file generated with svn 1.6.17 against the legacy Setuptools repo"
+
class TestEggInfo(unittest.TestCase):
def setUp(self):
@@ -31,10 +37,132 @@ class TestEggInfo(unittest.TestCase):
def test_version_10_format(self):
"""
"""
+ #keeping this set for 1.6 is a good check on the get_svn_revision
+ #to ensure I return using svnversion what would had been returned
+ version_str = svn_utils.SvnInfo.get_svn_version()
+ version = [int(x) for x in version_str.split('.')[:2]]
+ if version != [1, 6]:
+ if hasattr(self, 'skipTest'):
+ self.skipTest('')
+ else:
+ sys.stderr.write('\n Skipping due to SVN Version\n')
+ return
+
self._write_entries(ENTRIES_V10)
rev = egg_info.egg_info.get_svn_revision()
self.assertEqual(rev, '89000')
+ def test_version_10_format_legacy_parser(self):
+ """
+ """
+ path_variable = None
+ for env in os.environ:
+ if env.lower() == 'path':
+ path_variable = env
+
+ if path_variable is None:
+ self.skipTest('Cannot figure out how to modify path')
+
+ old_path = os.environ[path_variable]
+ os.environ[path_variable] = ''
+ #catch_warnings not available until py26
+ warning_filters = warnings.filters
+ warnings.filters = warning_filters[:]
+ try:
+ warnings.simplefilter("ignore", DeprecationWarning)
+ self._write_entries(ENTRIES_V10)
+ rev = egg_info.egg_info.get_svn_revision()
+ finally:
+ #restore the warning filters
+ warnings.filters = warning_filters
+ #restore the os path
+ os.environ[path_variable] = old_path
+
+ self.assertEqual(rev, '89000')
+
+DUMMY_SOURCE_TXT = """CHANGES.txt
+CONTRIBUTORS.txt
+HISTORY.txt
+LICENSE
+MANIFEST.in
+README.txt
+setup.py
+dummy/__init__.py
+dummy/test.txt
+dummy.egg-info/PKG-INFO
+dummy.egg-info/SOURCES.txt
+dummy.egg-info/dependency_links.txt
+dummy.egg-info/top_level.txt"""
+
+
+class TestSvnDummy(environment.ZippedEnvironment):
+
+ def setUp(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.base_version = tuple([int(x) for x in version.split('.')][:2])
+
+ if not self.base_version:
+ raise ValueError('No SVN tools installed')
+ elif self.base_version < (1, 3):
+ raise ValueError('Insufficient SVN Version %s' % version)
+ elif self.base_version >= (1, 9):
+ #trying the latest version
+ self.base_version = (1, 8)
+
+ self.dataname = "dummy%i%i" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvnDummy, self).setUp()
+
+ def test_sources(self):
+ code, data = environment.run_setup_py(["sdist"],
+ pypath=self.old_cwd,
+ data_stream=1)
+ if code:
+ raise AssertionError(data)
+
+ sources = os.path.join('dummy.egg-info', 'SOURCES.txt')
+ infile = open(sources, 'r')
+ try:
+ read_contents = infile.read()
+ finally:
+ infile.close()
+ del infile
+
+ self.assertEqual(DUMMY_SOURCE_TXT, read_contents)
+
+ return data
+
+
+class TestSvnDummyLegacy(environment.ZippedEnvironment):
+
+ def setUp(self):
+ self.base_version = (1, 6)
+ self.dataname = "dummy%i%i" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvnDummyLegacy, self).setUp()
+
+ def test_sources(self):
+ code, data = environment.run_setup_py(["sdist"],
+ pypath=self.old_cwd,
+ path="",
+ data_stream=1)
+ if code:
+ raise AssertionError(data)
+
+ sources = os.path.join('dummy.egg-info', 'SOURCES.txt')
+ infile = open(sources, 'r')
+ try:
+ read_contents = infile.read()
+ finally:
+ infile.close()
+ del infile
+
+ self.assertEqual(DUMMY_SOURCE_TXT, read_contents)
+
+ return data
+
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index 1609ee86..3dad1376 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -5,7 +5,10 @@ import os
import shutil
import unittest
import tempfile
+import types
+import pkg_resources
+import setuptools.sandbox
from setuptools.sandbox import DirectorySandbox, SandboxViolation
def has_win32com():
@@ -62,5 +65,15 @@ class TestSandbox(unittest.TestCase):
finally:
if os.path.exists(target): os.remove(target)
+ def test_setup_py_with_BOM(self):
+ """
+ It should be possible to execute a setup.py with a Byte Order Mark
+ """
+ target = pkg_resources.resource_filename(__name__,
+ 'script-with-bom.py')
+ namespace = types.ModuleType('namespace')
+ setuptools.sandbox.execfile(target, vars(namespace))
+ assert namespace.result == 'passed'
+
if __name__ == '__main__':
unittest.main()
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index 438f7ced..b42dcc57 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -1,19 +1,22 @@
# -*- coding: utf-8 -*-
"""sdist tests"""
-
+import locale
import os
import shutil
import sys
import tempfile
import unittest
import unicodedata
+import re
+from setuptools.tests import environment
from setuptools.compat import StringIO, unicode
-from setuptools.command.sdist import sdist
+from setuptools.tests.py26compat import skipIf
+from setuptools.command.sdist import sdist, walk_revctrl
from setuptools.command.egg_info import manifest_maker
from setuptools.dist import Distribution
-
+from setuptools import svn_utils
SETUP_ATTRS = {
'name': 'sdist_test',
@@ -318,6 +321,8 @@ class TestSdistTest(unittest.TestCase):
filename = filename.decode('latin-1')
self.assertFalse(filename in cmd.filelist.files)
+ @skipIf(sys.version_info >= (3,) and locale.getpreferredencoding() != 'UTF-8',
+ 'Unittest fails if locale is not utf-8 but the manifests is recorded correctly')
def test_sdist_with_utf8_encoded_filename(self):
# Test for #303.
dist = Distribution(SETUP_ATTRS)
@@ -395,6 +400,131 @@ class TestSdistTest(unittest.TestCase):
self.assertTrue(filename in cmd.filelist.files)
+class TestDummyOutput(environment.ZippedEnvironment):
+
+ def setUp(self):
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', "dummy.zip")
+ self.dataname = "dummy"
+ super(TestDummyOutput, self).setUp()
+
+ def _run(self):
+ code, data = environment.run_setup_py(["sdist"],
+ pypath=self.old_cwd,
+ data_stream=0)
+ if code:
+ info = "DIR: " + os.path.abspath('.')
+ info += "\n SDIST RETURNED: %i\n\n" % code
+ info += data
+ raise AssertionError(info)
+
+ datalines = data.splitlines()
+
+ possible = (
+ "running sdist",
+ "running egg_info",
+ "creating dummy\.egg-info",
+ "writing dummy\.egg-info",
+ "writing top-level names to dummy\.egg-info",
+ "writing dependency_links to dummy\.egg-info",
+ "writing manifest file 'dummy\.egg-info",
+ "reading manifest file 'dummy\.egg-info",
+ "reading manifest template 'MANIFEST\.in'",
+ "writing manifest file 'dummy\.egg-info",
+ "creating dummy-0.1.1",
+ "making hard links in dummy-0\.1\.1",
+ "copying files to dummy-0\.1\.1",
+ "copying \S+ -> dummy-0\.1\.1",
+ "copying dummy",
+ "copying dummy\.egg-info",
+ "hard linking \S+ -> dummy-0\.1\.1",
+ "hard linking dummy",
+ "hard linking dummy\.egg-info",
+ "Writing dummy-0\.1\.1",
+ "creating dist",
+ "creating 'dist",
+ "Creating tar archive",
+ "running check",
+ "adding 'dummy-0\.1\.1",
+ "tar .+ dist/dummy-0\.1\.1\.tar dummy-0\.1\.1",
+ "gzip .+ dist/dummy-0\.1\.1\.tar",
+ "removing 'dummy-0\.1\.1' \\(and everything under it\\)",
+ )
+
+ print(" DIR: " + os.path.abspath('.'))
+ for line in datalines:
+ found = False
+ for pattern in possible:
+ if re.match(pattern, line):
+ print(" READ: " + line)
+ found = True
+ break
+ if not found:
+ raise AssertionError("Unexpexected: %s\n-in-\n%s"
+ % (line, data))
+
+ return data
+
+ def test_sources(self):
+ self._run()
+
+
+class TestSvn(environment.ZippedEnvironment):
+
+ def setUp(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.base_version = tuple([int(x) for x in version.split('.')][:2])
+
+ if not self.base_version:
+ raise ValueError('No SVN tools installed')
+ elif self.base_version < (1, 3):
+ raise ValueError('Insufficient SVN Version %s' % version)
+ elif self.base_version >= (1, 9):
+ #trying the latest version
+ self.base_version = (1, 8)
+
+ self.dataname = "svn%i%i_example" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvn, self).setUp()
+
+ def test_walksvn(self):
+ if self.base_version >= (1, 6):
+ folder2 = 'third party2'
+ folder3 = 'third party3'
+ else:
+ folder2 = 'third_party2'
+ folder3 = 'third_party3'
+
+ #TODO is this right
+ expected = set([
+ os.path.join('a file'),
+ os.path.join(folder2, 'Changes.txt'),
+ os.path.join(folder2, 'MD5SUMS'),
+ os.path.join(folder2, 'README.txt'),
+ os.path.join(folder3, 'Changes.txt'),
+ os.path.join(folder3, 'MD5SUMS'),
+ os.path.join(folder3, 'README.txt'),
+ os.path.join(folder3, 'TODO.txt'),
+ os.path.join(folder3, 'fin'),
+ os.path.join('third_party', 'README.txt'),
+ os.path.join('folder', folder2, 'Changes.txt'),
+ os.path.join('folder', folder2, 'MD5SUMS'),
+ os.path.join('folder', folder2, 'WatashiNiYomimasu.txt'),
+ os.path.join('folder', folder3, 'Changes.txt'),
+ os.path.join('folder', folder3, 'fin'),
+ os.path.join('folder', folder3, 'MD5SUMS'),
+ os.path.join('folder', folder3, 'oops'),
+ os.path.join('folder', folder3, 'WatashiNiYomimasu.txt'),
+ os.path.join('folder', folder3, 'ZuMachen.txt'),
+ os.path.join('folder', 'third_party', 'WatashiNiYomimasu.txt'),
+ os.path.join('folder', 'lalala.txt'),
+ os.path.join('folder', 'quest.txt'),
+ # The example will have a deleted file
+ # (or should) but shouldn't return it
+ ])
+ self.assertEqual(set(x for x in walk_revctrl()), expected)
+
+
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
-
diff --git a/setuptools/tests/test_svn.py b/setuptools/tests/test_svn.py
new file mode 100644
index 00000000..5418b9a4
--- /dev/null
+++ b/setuptools/tests/test_svn.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+"""svn tests"""
+
+
+import os
+import unittest
+import codecs
+from setuptools.tests import environment
+from setuptools.compat import unicode, unichr
+
+from setuptools import svn_utils
+
+
+class TestSvnVersion(unittest.TestCase):
+
+ def test_no_svn_found(self):
+ path_variable = None
+ for env in os.environ:
+ if env.lower() == 'path':
+ path_variable = env
+
+ if path_variable is None:
+ self.skipTest('Cannot figure out how to modify path')
+
+ old_path = os.environ[path_variable]
+ os.environ[path_variable] = ''
+ try:
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.assertEqual(version, '')
+ finally:
+ os.environ[path_variable] = old_path
+
+ def test_svn_should_exist(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.assertNotEqual(version, '')
+
+def _read_utf8_file(path):
+ fileobj = None
+ try:
+ fileobj = codecs.open(path, 'r', 'utf-8')
+ data = fileobj.read()
+ return data
+ finally:
+ if fileobj:
+ fileobj.close()
+
+
+class ParserInfoXML(unittest.TestCase):
+
+ def parse_tester(self, svn_name, ext_spaces):
+ path = os.path.join('setuptools', 'tests',
+ 'svn_data', svn_name + '_info.xml')
+ #Remember these are pre-generated to test XML parsing
+ # so these paths might not valid on your system
+ example_base = "%s_example" % svn_name
+
+ data = _read_utf8_file(path)
+
+ expected = set([
+ ("\\".join((example_base, 'a file')), 'file'),
+ ("\\".join((example_base, 'folder')), 'dir'),
+ ("\\".join((example_base, 'folder', 'lalala.txt')), 'file'),
+ ("\\".join((example_base, 'folder', 'quest.txt')), 'file'),
+ ])
+ self.assertEqual(set(x for x in svn_utils.parse_dir_entries(data)),
+ expected)
+
+ def test_svn13(self):
+ self.parse_tester('svn13', False)
+
+ def test_svn14(self):
+ self.parse_tester('svn14', False)
+
+ def test_svn15(self):
+ self.parse_tester('svn15', False)
+
+ def test_svn16(self):
+ self.parse_tester('svn16', True)
+
+ def test_svn17(self):
+ self.parse_tester('svn17', True)
+
+ def test_svn18(self):
+ self.parse_tester('svn18', True)
+
+class ParserExternalXML(unittest.TestCase):
+
+ def parse_tester(self, svn_name, ext_spaces):
+ path = os.path.join('setuptools', 'tests',
+ 'svn_data', svn_name + '_ext_list.xml')
+ example_base = svn_name + '_example'
+ data = _read_utf8_file(path)
+
+ if ext_spaces:
+ folder2 = 'third party2'
+ folder3 = 'third party3'
+ else:
+ folder2 = 'third_party2'
+ folder3 = 'third_party3'
+
+ expected = set([
+ os.sep.join((example_base, folder2)),
+ os.sep.join((example_base, folder3)),
+ # folder is third_party大介
+ os.sep.join((example_base,
+ unicode('third_party') +
+ unichr(0x5927) + unichr(0x4ecb))),
+ os.sep.join((example_base, 'folder', folder2)),
+ os.sep.join((example_base, 'folder', folder3)),
+ os.sep.join((example_base, 'folder',
+ unicode('third_party') +
+ unichr(0x5927) + unichr(0x4ecb))),
+ ])
+
+ expected = set(os.path.normpath(x) for x in expected)
+ dir_base = os.sep.join(('C:', 'development', 'svn_example'))
+ self.assertEqual(set(x for x
+ in svn_utils.parse_externals_xml(data, dir_base)), expected)
+
+ def test_svn15(self):
+ self.parse_tester('svn15', False)
+
+ def test_svn16(self):
+ self.parse_tester('svn16', True)
+
+ def test_svn17(self):
+ self.parse_tester('svn17', True)
+
+ def test_svn18(self):
+ self.parse_tester('svn18', True)
+
+
+class ParseExternal(unittest.TestCase):
+
+ def parse_tester(self, svn_name, ext_spaces):
+ path = os.path.join('setuptools', 'tests',
+ 'svn_data', svn_name + '_ext_list.txt')
+ data = _read_utf8_file(path)
+
+ if ext_spaces:
+ expected = set(['third party2', 'third party3',
+ 'third party3b', 'third_party'])
+ else:
+ expected = set(['third_party2', 'third_party3', 'third_party'])
+
+ self.assertEqual(set(x for x in svn_utils.parse_external_prop(data)),
+ expected)
+
+ def test_svn13(self):
+ self.parse_tester('svn13', False)
+
+ def test_svn14(self):
+ self.parse_tester('svn14', False)
+
+ def test_svn15(self):
+ self.parse_tester('svn15', False)
+
+ def test_svn16(self):
+ self.parse_tester('svn16', True)
+
+ def test_svn17(self):
+ self.parse_tester('svn17', True)
+
+ def test_svn18(self):
+ self.parse_tester('svn18', True)
+
+
+class TestSvn(environment.ZippedEnvironment):
+
+ def setUp(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.base_version = tuple([int(x) for x in version.split('.')[:2]])
+
+ if not self.base_version:
+ raise ValueError('No SVN tools installed')
+ elif self.base_version < (1,3):
+ raise ValueError('Insufficient SVN Version %s' % version)
+ elif self.base_version >= (1,9):
+ #trying the latest version
+ self.base_version = (1,8)
+
+ self.dataname = "svn%i%i_example" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvn, self).setUp()
+
+ def test_revision(self):
+ rev = svn_utils.SvnInfo.load('.').get_revision()
+ self.assertEqual(rev, 6)
+
+ def test_entries(self):
+ expected = set([
+ (os.path.join('a file'), 'file'),
+ (os.path.join('folder'), 'dir'),
+ (os.path.join('folder', 'lalala.txt'), 'file'),
+ (os.path.join('folder', 'quest.txt'), 'file'),
+ #The example will have a deleted file (or should)
+ #but shouldn't return it
+ ])
+ info = svn_utils.SvnInfo.load('.')
+ self.assertEqual(set(x for x in info.entries), expected)
+
+ def test_externals(self):
+ if self.base_version >= (1,6):
+ folder2 = 'third party2'
+ folder3 = 'third party3'
+ else:
+ folder2 = 'third_party2'
+ folder3 = 'third_party3'
+
+ expected = set([
+ os.path.join(folder2),
+ os.path.join(folder3),
+ os.path.join('third_party'),
+ os.path.join('folder', folder2),
+ os.path.join('folder', folder3),
+ os.path.join('folder', 'third_party'),
+ ])
+ info = svn_utils.SvnInfo.load('.')
+ self.assertEqual(set([x for x in info.externals]), expected)
+
+def test_suite():
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/setuptools/version.py b/setuptools/version.py
index 7e49527e..07f744ca 100644
--- a/setuptools/version.py
+++ b/setuptools/version.py
@@ -1 +1 @@
-__version__ = '1.0'
+__version__ = '1.3.3'