Commit 337b7586 authored by Gary Poster's avatar Gary Poster

tests now pass in Windows (and still in Linux)

parent dac3bbfd
...@@ -110,8 +110,10 @@ try: ...@@ -110,8 +110,10 @@ try:
import setuptools # A flag. Sometimes pkg_resources is installed alone. import setuptools # A flag. Sometimes pkg_resources is installed alone.
import pkg_resources import pkg_resources
except ImportError: except ImportError:
ez_code = urllib2.urlopen(
configuration['--ez_setup-source']).read().replace('\r\n', '\n')
ez = {} ez = {}
exec urllib2.urlopen(configuration['--ez_setup-source']).read() in ez exec ez_code in ez
setuptools_args = dict(to_dir=configuration['--eggs'], download_delay=0) setuptools_args = dict(to_dir=configuration['--eggs'], download_delay=0)
if configuration['--download-base']: if configuration['--download-base']:
setuptools_args['download_base'] = configuration['--download-base'] setuptools_args['download_base'] = configuration['--download-base']
......
...@@ -1300,13 +1300,16 @@ def _get_system_paths(executable): ...@@ -1300,13 +1300,16 @@ def _get_system_paths(executable):
cmd.extend([ cmd.extend([
"-c", "import sys, os;" "-c", "import sys, os;"
"print repr([os.path.normpath(p) for p in sys.path if p])"]) "print repr([os.path.normpath(p) for p in sys.path if p])"])
# Windows needs some (as yet to be determined) part of the real env.
env = os.environ.copy()
env.update(kwargs)
_proc = subprocess.Popen( _proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=kwargs) cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
stdout, stderr = _proc.communicate(); stdout, stderr = _proc.communicate();
if _proc.returncode: if _proc.returncode:
raise RuntimeError( raise RuntimeError(
'error trying to get system packages:\n%s' % (stderr,)) 'error trying to get system packages:\n%s' % (stderr,))
res = eval(stdout) res = eval(stdout.strip())
try: try:
res.remove('.') res.remove('.')
except ValueError: except ValueError:
...@@ -1506,10 +1509,52 @@ def _generate_interpreter(name, dest, executable, site_py_dest, ...@@ -1506,10 +1509,52 @@ def _generate_interpreter(name, dest, executable, site_py_dest,
full_name = os.path.join(dest, name) full_name = os.path.join(dest, name)
site_py_dest_string, rpsetup = _relative_path_and_setup( site_py_dest_string, rpsetup = _relative_path_and_setup(
full_name, [site_py_dest], relative_paths) full_name, [site_py_dest], relative_paths)
if sys.platform == 'win32':
windows_import = '\nimport subprocess'
# os.exec* is a mess on Windows, particularly if the path
# to the executable has spaces and the Python is using MSVCRT.
# The standard fix is to surround the executable's path with quotes,
# but that has been unreliable in testing.
#
# Here's a demonstration of the problem. Given a Python
# compiled with a MSVCRT-based compiler, such as the free Visual
# C++ 2008 Express Edition, and an executable path with spaces
# in it such as the below, we see the following.
#
# >>> import os
# >>> p0 = 'C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe'
# >>> os.path.exists(p0)
# True
# >>> os.execv(p0, [])
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# OSError: [Errno 22] Invalid argument
#
# That seems like a standard problem. The standard solution is
# to quote the path (see, for instance
# http://bugs.python.org/issue436259). However, this solution,
# and other variations, fail:
#
# >>> p1 = '"C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe"'
# >>> os.execv(p1, [])
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# OSError: [Errno 22] Invalid argument
#
# We simply use subprocess instead, since it handles everything
# nicely, and the transparency of exec* (that is, not running,
# perhaps unexpectedly, in a subprocess) is arguably not a
# necessity, at least for many use cases.
execute = 'subprocess.call(argv, env=environ)'
else:
windows_import = ''
execute = 'os.execve(sys.executable, argv, environ)'
contents = interpreter_template % dict( contents = interpreter_template % dict(
python = _safe_arg(executable), python=_safe_arg(executable),
site_dest = site_py_dest_string, site_dest=site_py_dest_string,
relative_paths_setup = rpsetup, relative_paths_setup=rpsetup,
windows_import=windows_import,
execute=execute,
) )
return _write_script(full_name, contents, 'interpreter') return _write_script(full_name, contents, 'interpreter')
...@@ -1517,7 +1562,7 @@ interpreter_template = script_header + '''\ ...@@ -1517,7 +1562,7 @@ interpreter_template = script_header + '''\
%(relative_paths_setup)s %(relative_paths_setup)s
import os import os
import sys import sys%(windows_import)s
argv = [sys.executable] + sys.argv[1:] argv = [sys.executable] + sys.argv[1:]
environ = os.environ.copy() environ = os.environ.copy()
...@@ -1525,7 +1570,7 @@ path = %(site_dest)s ...@@ -1525,7 +1570,7 @@ path = %(site_dest)s
if environ.get('PYTHONPATH'): if environ.get('PYTHONPATH'):
path = os.pathsep.join([path, environ['PYTHONPATH']]) path = os.pathsep.join([path, environ['PYTHONPATH']])
environ['PYTHONPATH'] = path environ['PYTHONPATH'] = path
os.execve(sys.executable, argv, environ) %(execute)s
''' '''
# End of script generation code. # End of script generation code.
......
...@@ -1094,12 +1094,11 @@ in both site packages and eggs. ...@@ -1094,12 +1094,11 @@ in both site packages and eggs.
Here are some examples of the interpreter in use. Here are some examples of the interpreter in use.
>>> print system(interpreter_path + ' -c "print 16+26"') >>> print call_py(interpreter_path, "print 16+26")
42 42
<BLANKLINE> <BLANKLINE>
>>> res = system(interpreter_path + >>> res = call_py(interpreter_path, "import sys; print sys.path")
... ' -c "import sys, pprint; pprint.pprint(sys.path)"') >>> print res # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
>>> print res # doctest: +ELLIPSIS
['', ['',
'/interpreter/eggs/demo-0.3-pyN.N.egg', '/interpreter/eggs/demo-0.3-pyN.N.egg',
'/interpreter/eggs/demoneeded-1.1-pyN.N.egg', '/interpreter/eggs/demoneeded-1.1-pyN.N.egg',
...@@ -1129,8 +1128,7 @@ If you provide initialization, it goes in sitecustomize.py. ...@@ -1129,8 +1128,7 @@ If you provide initialization, it goes in sitecustomize.py.
>>> cat(sitecustomize_path) >>> cat(sitecustomize_path)
import os import os
os.environ['FOO'] = 'bar baz bing shazam' os.environ['FOO'] = 'bar baz bing shazam'
>>> print system(interpreter_path + >>> print call_py(interpreter_path, "import os; print os.environ['FOO']")
... """ -c 'import os; print os.environ["FOO"]'""")
bar baz bing shazam bar baz bing shazam
<BLANKLINE> <BLANKLINE>
...@@ -1182,8 +1180,8 @@ paths join a base to a path, as with the use of this argument in the ...@@ -1182,8 +1180,8 @@ paths join a base to a path, as with the use of this argument in the
The paths resolve in practice as you would expect. The paths resolve in practice as you would expect.
>>> print system(interpreter_path + >>> print call_py(interpreter_path,
... ' -c "import sys, pprint; pprint.pprint(sys.path)"') ... "import sys, pprint; pprint.pprint(sys.path)")
... # doctest: +ELLIPSIS ... # doctest: +ELLIPSIS
['', ['',
'/interpreter/eggs/demo-0.3-py2.4.egg', '/interpreter/eggs/demo-0.3-py2.4.egg',
...@@ -1210,8 +1208,8 @@ The ``extra_paths`` argument affects the path in site.py. Notice that ...@@ -1210,8 +1208,8 @@ The ``extra_paths`` argument affects the path in site.py. Notice that
'/interpreter/other' '/interpreter/other'
]... ]...
>>> print system(interpreter_path + >>> print call_py(interpreter_path,
... ' -c "import sys, pprint; pprint.pprint(sys.path)"') ... "import sys, pprint; pprint.pprint(sys.path)")
... # doctest: +ELLIPSIS ... # doctest: +ELLIPSIS
['', ['',
'/interpreter/eggs/demo-0.3-pyN.N.egg', '/interpreter/eggs/demo-0.3-pyN.N.egg',
...@@ -1355,9 +1353,8 @@ Here's an example of the new script in use. Other documents and tests in ...@@ -1355,9 +1353,8 @@ Here's an example of the new script in use. Other documents and tests in
this package give the feature a more thorough workout, but this should this package give the feature a more thorough workout, but this should
give you an idea of the feature. give you an idea of the feature.
>>> res = system(join(interpreter_dir, 'bin', 'py') + >>> res = call_py(interpreter_path, "import sys; print sys.path")
... ' -c "import sys, pprint; pprint.pprint(sys.path)"') >>> print res # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
>>> print res # doctest: +ELLIPSIS
['', ['',
'/interpreter/eggs/demo-0.3-py2.4.egg', '/interpreter/eggs/demo-0.3-py2.4.egg',
'/interpreter/eggs/demoneeded-1.1-py2.4.egg', '/interpreter/eggs/demoneeded-1.1-py2.4.egg',
...@@ -1434,7 +1431,10 @@ The demo script runs the entry point defined in the demo egg: ...@@ -1434,7 +1431,10 @@ The demo script runs the entry point defined in the demo egg:
if __name__ == '__main__': if __name__ == '__main__':
eggrecipedemo.main() eggrecipedemo.main()
>>> print system(join(interpreter_bin_dir, 'demo')) >>> demo_call = join(interpreter_bin_dir, 'demo')
>>> if sys.platform == 'win32':
... demo_call = '"%s"' % demo_call
>>> print system(demo_call)
3 1 3 1
<BLANKLINE> <BLANKLINE>
......
...@@ -106,6 +106,16 @@ def system(command, input=''): ...@@ -106,6 +106,16 @@ def system(command, input=''):
e.close() e.close()
return result return result
def call_py(interpreter, cmd, flags=None):
if sys.platform == 'win32':
args = ['"%s"' % arg for arg in (interpreter, flags, cmd) if arg]
args.insert(-1, '"-c"')
return system('"%s"' % ' '.join(args))
else:
cmd = repr(cmd)
return system(
' '.join(arg for arg in (interpreter, flags, '-c', cmd) if arg))
def get(url): def get(url):
return urllib2.urlopen(url).read() return urllib2.urlopen(url).read()
...@@ -336,6 +346,7 @@ def buildoutSetUp(test): ...@@ -336,6 +346,7 @@ def buildoutSetUp(test):
tmpdir = tmpdir, tmpdir = tmpdir,
write = write, write = write,
system = system, system = system,
call_py = call_py,
get = get, get = get,
cd = (lambda *path: os.chdir(os.path.join(*path))), cd = (lambda *path: os.chdir(os.path.join(*path))),
join = os.path.join, join = os.path.join,
...@@ -536,10 +547,14 @@ def _normalize_path(match): ...@@ -536,10 +547,14 @@ def _normalize_path(match):
path = path[1:] path = path[1:]
return '/' + path.replace(os.path.sep, '/') return '/' + path.replace(os.path.sep, '/')
if sys.platform == 'win32':
sep = r'[\\/]' # Windows uses both sometimes.
else:
sep = re.escape(os.path.sep)
normalize_path = ( normalize_path = (
re.compile( re.compile(
r'''[^'" \t\n\r]+\%(sep)s_[Tt][Ee][Ss][Tt]_\%(sep)s([^"' \t\n\r]+)''' r'''[^'" \t\n\r]+%(sep)s_[Tt][Ee][Ss][Tt]_%(sep)s([^"' \t\n\r]+)'''
% dict(sep=os.path.sep)), % dict(sep=sep)),
_normalize_path, _normalize_path,
) )
......
...@@ -1794,11 +1794,9 @@ package, so this demonstrates that our Python does in fact have demo ...@@ -1794,11 +1794,9 @@ package, so this demonstrates that our Python does in fact have demo
version 0.3 and demoneeded version 1.1. version 0.3 and demoneeded version 1.1.
>>> py_path = make_py_with_system_install(make_py, sample_eggs) >>> py_path = make_py_with_system_install(make_py, sample_eggs)
>>> print system( >>> print call_py(
... py_path + " -c '" + ... py_path,
... "import tellmy.version\n" + ... "import tellmy.version; print tellmy.version.__version__"),
... "print tellmy.version.__version__\n" +
... "'"),
1.1 1.1
Now here's a setup that would expose the bug, using the Now here's a setup that would expose the bug, using the
...@@ -1832,11 +1830,9 @@ To demonstrate this, we will create three packages: tellmy.version 1.0, ...@@ -1832,11 +1830,9 @@ To demonstrate this, we will create three packages: tellmy.version 1.0,
tellmy.version 1.1, and tellmy.fortune 1.0. tellmy.version 1.1 is installed. tellmy.version 1.1, and tellmy.fortune 1.0. tellmy.version 1.1 is installed.
>>> py_path = make_py_with_system_install(make_py, sample_eggs) >>> py_path = make_py_with_system_install(make_py, sample_eggs)
>>> print system( >>> print call_py(
... py_path + " -c '" + ... py_path,
... "import tellmy.version\n" + ... "import tellmy.version; print tellmy.version.__version__")
... "print tellmy.version.__version__\n" +
... "'")
1.1 1.1
<BLANKLINE> <BLANKLINE>
...@@ -1895,13 +1891,12 @@ In other words, we got the site-packages version of tellmy.version, and ...@@ -1895,13 +1891,12 @@ In other words, we got the site-packages version of tellmy.version, and
we could not import tellmy.fortune at all. The following are the correct we could not import tellmy.fortune at all. The following are the correct
results for the interpreter and for the script. results for the interpreter and for the script.
>>> print system( >>> print call_py(
... join('bin', 'py') + " -c '" + ... join('bin', 'py'),
... "import tellmy.version\n" + ... "import tellmy.version; " +
... "print tellmy.version.__version__\n" + ... "print tellmy.version.__version__; " +
... "import tellmy.fortune\n" + ... "import tellmy.fortune; " +
... "print tellmy.fortune.__version__\n" + ... "print tellmy.fortune.__version__") # doctest: +ELLIPSIS
... "'") # doctest: +ELLIPSIS
1.0 1.0
1.0... 1.0...
...@@ -1961,11 +1956,9 @@ these unpleasant tricks, and a Python that has an older version installed. ...@@ -1961,11 +1956,9 @@ these unpleasant tricks, and a Python that has an older version installed.
... zc.buildout.testing.sys_install(tmp, site_packages_path) ... zc.buildout.testing.sys_install(tmp, site_packages_path)
... finally: ... finally:
... shutil.rmtree(tmp) ... shutil.rmtree(tmp)
>>> print system( >>> print call_py(
... py_path + " -c '" + ... py_path,
... "import tellmy.version\n" + ... "import tellmy.version; print tellmy.version.__version__")
... "print tellmy.version.__version__\n" +
... "'")
1.0 1.0
<BLANKLINE> <BLANKLINE>
>>> write('buildout.cfg', >>> write('buildout.cfg',
...@@ -3161,7 +3154,7 @@ def test_suite(): ...@@ -3161,7 +3154,7 @@ def test_suite():
'We have a develop egg: zc.buildout X.X.'), 'We have a develop egg: zc.buildout X.X.'),
(re.compile(r'\\[\\]?'), '/'), (re.compile(r'\\[\\]?'), '/'),
(re.compile('WindowsError'), 'OSError'), (re.compile('WindowsError'), 'OSError'),
(re.compile(r'\[Error 17\] Cannot create a file ' (re.compile(r'\[Error \d+\] Cannot create a file '
r'when that file already exists: '), r'when that file already exists: '),
'[Errno 17] File exists: ' '[Errno 17] File exists: '
), ),
...@@ -3215,6 +3208,10 @@ def test_suite(): ...@@ -3215,6 +3208,10 @@ def test_suite():
(re.compile('[-d] setuptools-\S+[.]egg'), 'setuptools.egg'), (re.compile('[-d] setuptools-\S+[.]egg'), 'setuptools.egg'),
(re.compile(r'\\[\\]?'), '/'), (re.compile(r'\\[\\]?'), '/'),
(re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'), (re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'),
# Normalize generate_script's Windows interpreter to UNIX:
(re.compile(r'\nimport subprocess\n'), '\n'),
(re.compile('subprocess\\.call\\(argv, env=environ\\)'),
'os.execve(sys.executable, argv, environ)'),
]+(sys.version_info < (2, 5) and [ ]+(sys.version_info < (2, 5) and [
(re.compile('.*No module named runpy.*', re.S), ''), (re.compile('.*No module named runpy.*', re.S), ''),
(re.compile('.*usage: pdb.py scriptfile .*', re.S), ''), (re.compile('.*usage: pdb.py scriptfile .*', re.S), ''),
......
...@@ -138,7 +138,8 @@ You can also generate an interpreter alone with the ``interpreter`` recipe. ...@@ -138,7 +138,8 @@ You can also generate an interpreter alone with the ``interpreter`` recipe.
Generated interpreter '/sample-buildout/bin/py'. Generated interpreter '/sample-buildout/bin/py'.
In both cases, the bin/py script works by restarting Python after In both cases, the bin/py script works by restarting Python after
specifying a special path in PYTHONPATH. specifying a special path in PYTHONPATH. This example shows the UNIX version;
the Windows version actually uses subprocess instead.
>>> cat(sample_buildout, 'bin', 'py') # doctest: +NORMALIZE_WHITESPACE >>> cat(sample_buildout, 'bin', 'py') # doctest: +NORMALIZE_WHITESPACE
#!/usr/bin/python2.4 -S #!/usr/bin/python2.4 -S
......
...@@ -270,6 +270,10 @@ def test_suite(): ...@@ -270,6 +270,10 @@ def test_suite():
(re.compile(r'eggs\\\\demo'), 'eggs/demo'), (re.compile(r'eggs\\\\demo'), 'eggs/demo'),
(re.compile(r'[a-zA-Z]:\\\\foo\\\\bar'), '/foo/bar'), (re.compile(r'[a-zA-Z]:\\\\foo\\\\bar'), '/foo/bar'),
(re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'), (re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'),
# Normalize generate_script's Windows interpreter to UNIX:
(re.compile(r'\nimport subprocess\n'), '\n'),
(re.compile('subprocess\\.call\\(argv, env=environ\\)'),
'os.execve(sys.executable, argv, environ)'),
]) ])
), ),
doctest.DocTestSuite( doctest.DocTestSuite(
...@@ -279,6 +283,7 @@ def test_suite(): ...@@ -279,6 +283,7 @@ def test_suite():
zc.buildout.testing.normalize_path, zc.buildout.testing.normalize_path,
zc.buildout.testing.normalize_endings, zc.buildout.testing.normalize_endings,
zc.buildout.testing.normalize_egg_py, zc.buildout.testing.normalize_egg_py,
(re.compile(r'[a-zA-Z]:\\\\foo\\\\bar'), '/foo/bar'),
]), ]),
), ),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment