Python 3 support (#3)
* Make compiler and runtime able to run under python3.
* Read python interpreter path from Bazel
* Add integration tests for Python 3 support
* Remove unused shebang lines
* Formatting fix: Use single quotes consistently
* Update README with Python 3 support
diff --git a/README.md b/README.md
index 73d3f74..e5448e0 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,6 @@
* C extension modules in 'deps' is not yet supported
* Automatic re-extraction of '.runfiles' is not yet supported
-* Python 3 is not yet supported
* Does not include a copy of the Python interpreter ('hermetic .par')
## Example
diff --git a/compiler/BUILD b/compiler/BUILD
index 704d583..bbc0f8d 100644
--- a/compiler/BUILD
+++ b/compiler/BUILD
@@ -15,23 +15,40 @@
data = [
"//runtime:support.py",
],
+ srcs_version = "PY2AND3",
)
py_library(
name = "test_utils",
srcs = ["test_utils.py"],
+ srcs_version = "PY2AND3",
)
py_binary(
name = "compiler",
srcs = ["compiler.py"],
+ default_python_version = "PY2",
+ main = "compiler.py",
+ srcs_version = "PY2AND3",
+ deps = [":compiler_lib"],
+)
+
+py_binary(
+ name = "compiler3",
+ srcs = ["compiler.py"],
+ default_python_version = "PY3",
+ main = "compiler.py",
+ srcs_version = "PY2AND3",
deps = [":compiler_lib"],
)
[py_test(
- name = src_name + "_test",
+ name = "%s_%s_test" % (src_name, version),
size = "small",
srcs = [src_name + "_test.py"],
+ default_python_version = version,
+ main = src_name + "_test.py",
+ srcs_version = "PY2AND3",
deps = [
":compiler_lib",
":test_utils",
@@ -41,4 +58,7 @@
"manifest_parser",
"python_archive",
"stored_resource",
+] for version in [
+ "PY2",
+ "PY3",
]]
diff --git a/compiler/cli.py b/compiler/cli.py
index f29f9a2..be6a347 100644
--- a/compiler/cli.py
+++ b/compiler/cli.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +15,7 @@
"""Command line interface to subpar compiler"""
import argparse
+import io
import os
import re
@@ -34,14 +33,6 @@
help='Python source file to use as main entry point')
parser.add_argument(
- '--imports_from_stub',
- help='Read imports attribute from the specified stub file',
- required=True)
- parser.add_argument(
- '--interpreter',
- help='Filename of Python executable to invoke archive with.',
- default='/usr/bin/python2')
- parser.add_argument(
'--manifest_file',
help='File listing all files to be included in this parfile. This is ' +
'typically generated by bazel in a target\'s .runfiles_manifest file.',
@@ -54,26 +45,51 @@
'--outputpar',
help='Filename of generated par file.',
required=True)
+ parser.add_argument(
+ '--stub_file',
+ help='Read imports and interpreter path from the specified stub file',
+ required=True)
return parser
-def parse_imports_from_stub(stub_filename):
- """Parse the imports attribute from a py_binary() stub.
+def parse_stub(stub_filename):
+ """Parse the imports and interpreter path from a py_binary() stub.
+
+ We assume the stub is utf-8 encoded.
TODO(b/29227737): Remove this once we can access imports from skylark.
- Returns a list of relative paths
+ Returns (list of relative paths, path to Python interpreter)
"""
- regex = re.compile(r'''^ python_imports = '([^']*)'$''')
- with open(stub_filename, 'rb') as stub_file:
+ imports_regex = re.compile(r'''^ python_imports = '([^']*)'$''')
+ interpreter_regex = re.compile(r'''^PYTHON_BINARY = '([^']*)'$''')
+ import_roots = None
+ interpreter = None
+ with io.open(stub_filename, 'rt', encoding='utf8') as stub_file:
for line in stub_file:
- match = regex.match(line)
- if match:
- import_roots = match.group(1).split(':')
+ importers_match = imports_regex.match(line)
+ if importers_match:
+ import_roots = importers_match.group(1).split(':')
# Filter out empty paths
import_roots = [x for x in import_roots if x]
- return import_roots
- raise error.Error('Failed to parse stub file [%s]' % stub_filename)
+ interpreter_match = interpreter_regex.match(line)
+ if interpreter_match:
+ interpreter = interpreter_match.group(1)
+ if import_roots is None or not interpreter:
+ raise error.Error('Failed to parse stub file [%s]' % stub_filename)
+
+ # Match the search logic in stub_template.txt
+ if interpreter.startswith('//'):
+ raise error.Error('Python interpreter must not be a label [%s]' %
+ stub_filename)
+ elif interpreter.startswith('/'):
+ pass
+ elif '/' in interpreter:
+ pass
+ else:
+ interpreter = '/usr/bin/env %s' % interpreter
+
+ return (import_roots, interpreter)
def main(argv):
@@ -81,13 +97,13 @@
parser = make_command_line_parser()
args = parser.parse_args(argv[1:])
- # Read import roots
- import_roots = parse_imports_from_stub(args.imports_from_stub)
+ # Parse information from stub file that's too hard to compute in Skylark
+ import_roots, interpreter = parse_stub(args.stub_file)
par = python_archive.PythonArchive(
main_filename=args.main_filename,
import_roots=import_roots,
- interpreter=args.interpreter,
+ interpreter=interpreter,
output_filename=args.outputpar,
manifest_filename=args.manifest_file,
manifest_root=args.manifest_root,
diff --git a/compiler/cli_test.py b/compiler/cli_test.py
index d11e107..95a937b 100644
--- a/compiler/cli_test.py
+++ b/compiler/cli_test.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,37 +24,69 @@
def test_make_command_line_parser(self):
parser = cli.make_command_line_parser()
args = parser.parse_args([
- '--imports_from_stub=quux',
'--manifest_file=bar',
'--manifest_root=bazz',
'--outputpar=baz',
+ '--stub_file=quux',
'foo',
])
self.assertEqual(args.manifest_file, 'bar')
- def test_parse_imports_from_stub(self):
+ def test_stub(self):
valid_cases = [
- [" python_imports = ''",
- []],
- [" python_imports = 'myworkspace/spam/eggs'",
- ['myworkspace/spam/eggs']],
- [" python_imports = 'myworkspace/spam/eggs:otherworkspace'",
- ['myworkspace/spam/eggs', 'otherworkspace']],
+ # Empty list
+ [b"""
+ python_imports = ''
+PYTHON_BINARY = '/usr/bin/python'
+""",
+ ([], '/usr/bin/python')],
+ # Single import
+ [b"""
+ python_imports = 'myworkspace/spam/eggs'
+PYTHON_BINARY = '/usr/bin/python'
+""",
+ (['myworkspace/spam/eggs'], '/usr/bin/python')],
+ # Multiple imports
+ [b"""
+ python_imports = 'myworkspace/spam/eggs:otherworkspace'
+PYTHON_BINARY = '/usr/bin/python'
+""",
+ (['myworkspace/spam/eggs', 'otherworkspace'], '/usr/bin/python')],
+ # Relative path to interpreter
+ [b"""
+ python_imports = ''
+PYTHON_BINARY = 'mydir/python'
+""",
+ ([], 'mydir/python')],
+ # Search for interpreter on $PATH
+ [b"""
+ python_imports = ''
+PYTHON_BINARY = 'python'
+""",
+ ([], '/usr/bin/env python')],
]
for content, expected in valid_cases:
with test_utils.temp_file(content) as stub_file:
- actual = cli.parse_imports_from_stub(stub_file.name)
+ actual = cli.parse_stub(stub_file.name)
self.assertEqual(actual, expected)
invalid_cases = [
- '',
- '\n\n',
- ' python_imports=',
+ b'',
+ b'\n\n',
+ # No interpreter
+ b" python_imports = 'myworkspace/spam/eggs'",
+ # No imports
+ b"PYTHON_BINARY = 'python'\n",
+ # Interpreter is label
+ b"""
+ python_imports = ''
+PYTHON_BINARY = '//mypackage:python'
+""",
]
for content in invalid_cases:
with test_utils.temp_file(content) as stub_file:
with self.assertRaises(error.Error):
- cli.parse_imports_from_stub(stub_file.name)
+ cli.parse_stub(stub_file.name)
if __name__ == '__main__':
diff --git a/compiler/compiler.py b/compiler/compiler.py
index 4882a4e..5eb30d9 100644
--- a/compiler/compiler.py
+++ b/compiler/compiler.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/compiler/manifest_parser.py b/compiler/manifest_parser.py
index 79a87e9..3bd2e70 100644
--- a/compiler/manifest_parser.py
+++ b/compiler/manifest_parser.py
@@ -17,7 +17,10 @@
The format is described in
https://github.com/bazelbuild/bazel/blob/master/src/main/tools/build-runfiles.cc
+We assume manifest files are utf-8 encoded.
+
"""
+import io
from subpar.compiler import error
@@ -41,7 +44,7 @@
"""
manifest = {}
- with open(manifest_filename, 'rb') as f:
+ with io.open(manifest_filename, 'rt', encoding='utf8') as f:
lineno = 0
for line in f:
# Split line into fields
diff --git a/compiler/manifest_parser_test.py b/compiler/manifest_parser_test.py
index e4cee1c..4de3f3f 100644
--- a/compiler/manifest_parser_test.py
+++ b/compiler/manifest_parser_test.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,11 +24,11 @@
def test_parse_manifest_valid(self):
valid = (
# 1 field, no trailing space
- 'ccccc/__init__.py\n' +
+ b'ccccc/__init__.py\n' +
# 1 field, trailing space
- 'ccccc/ddddd/__init__.py \n' +
+ b'ccccc/ddddd/__init__.py \n' +
# 2 fields
- 'ccccc/ddddd/eeeee /code/rrrrr/ccccc/ddddd/eeeee\n'
+ b'ccccc/ddddd/eeeee /code/rrrrr/ccccc/ddddd/eeeee\n'
)
expected = {
'ccccc/__init__.py': None,
@@ -44,13 +42,13 @@
def test_parse_manifest_invalid(self):
invalids = [
# Repeated name
- ('ccccc/__init__.py \n' +
- 'ccccc/ddddd/__init__.py \n' +
- 'ccccc/__init__.py \n'),
+ (b'ccccc/__init__.py \n' +
+ b'ccccc/ddddd/__init__.py \n' +
+ b'ccccc/__init__.py \n'),
# Too many spaces
- 'ccccc/__init__.py foo bar\n',
+ b'ccccc/__init__.py foo bar\n',
# Not enough spaces
- '\n\n',
+ b'\n\n',
]
for invalid in invalids:
with test_utils.temp_file(invalid) as t:
diff --git a/compiler/python_archive.py b/compiler/python_archive.py
index 7895718..db66a0b 100755
--- a/compiler/python_archive.py
+++ b/compiler/python_archive.py
@@ -28,6 +28,7 @@
"""
+import io
import logging
import os
import pkgutil
@@ -49,9 +50,9 @@
"""
# Fully qualified names of subpar packages
-_compiler_package = 'subpar.compiler'
-_runtime_package = 'subpar.runtime'
-_runtime_path = 'subpar/runtime'
+_subpar_package = 'subpar'
+_compiler_package = _subpar_package + '.compiler'
+_runtime_package = _subpar_package + '.runtime'
# List of files from the runtime package to include in every .par file
_runtime_support_files = ['support.py',]
@@ -74,7 +75,7 @@
manifest_filename,
manifest_root,
output_filename,
- ):
+ ):
self.main_filename = main_filename
self.import_roots = import_roots
@@ -135,11 +136,16 @@
Returns:
A StoredResource
"""
- contents = _main_template % {
+ template_contents = _main_template % {
'runtime_package': _runtime_package,
'import_roots': str(self.import_roots),
}
- contents = contents + open(self.main_filename, 'r').read()
+ with open(self.main_filename, 'rb') as main_file:
+ main_contents = main_file.read()
+ # We don't know the encoding of the main source file, so
+ # require that the template be pure ascii, which we can safely
+ # prepend.
+ contents = template_contents.encode('ascii') + main_contents
return stored_resource.StoredContent('__main__.py', contents)
def scan_manifest(self, manifest):
@@ -190,7 +196,8 @@
This tells the operating system (well, UNIX) how to execute the file.
"""
logging.debug('Writing boilerplate...')
- temp_parfile.write('#!%s\n' % self.interpreter)
+ boilerplate = '#!%s\n' % self.interpreter
+ temp_parfile.write(boilerplate.encode('ascii'))
def write_zip_data(self, temp_parfile, stored_resources):
"""Write the second part of a parfile, consisting of ZIP data
@@ -211,7 +218,7 @@
"""Move newly created parfile to its final filename."""
# Python 2 doesn't have os.replace, so use os.rename which is
# not atomic in all cases.
- os.chmod(temp_parfile_name, 0755)
+ os.chmod(temp_parfile_name, 0o0755)
os.rename(temp_parfile_name, self.output_filename)
@@ -230,8 +237,8 @@
Returns:
A StoredResource representing the content of that file
"""
- stored_filename = os.path.join(_runtime_path, name)
- content = pkgutil.get_data(_runtime_package, name)
+ stored_filename = os.path.join(_subpar_package, 'runtime', name)
+ content = pkgutil.get_data(_subpar_package, 'runtime/' + name)
# content is None means the file wasn't found. content == '' is
# valid, it means the file was found and was empty.
if content is None:
diff --git a/compiler/python_archive_test.py b/compiler/python_archive_test.py
index f58a961..aa7922e 100644
--- a/compiler/python_archive_test.py
+++ b/compiler/python_archive_test.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -37,11 +35,12 @@
if not os.path.exists(self.input_dir):
os.makedirs(self.input_dir)
self.manifest_filename = os.path.join(self.input_dir, 'manifest')
- self.main_file = test_utils.temp_file('print("Hello World!")',
+ self.main_file = test_utils.temp_file(b'print("Hello World!")',
suffix='.py')
+ manifest_content = '%s %s\n' % (
+ os.path.basename(self.main_file.name), self.main_file.name)
self.manifest_file = test_utils.temp_file(
- '%s %s\n' %
- (os.path.basename(self.main_file.name), self.main_file.name))
+ manifest_content.encode('utf8'))
self.output_dir = os.path.join(self.tmpdir, 'output')
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
@@ -66,19 +65,19 @@
par.create()
def test_create_manifest_parse_error(self):
- with test_utils.temp_file('blah blah blah\n') as manifest_file:
+ with test_utils.temp_file(b'blah blah blah\n') as manifest_file:
par = self._construct(manifest_filename=manifest_file.name)
with self.assertRaises(error.Error):
par.create()
def test_create_manifest_contains___main___py(self):
- with test_utils.temp_file('__main__.py\n') as manifest_file:
+ with test_utils.temp_file(b'__main__.py\n') as manifest_file:
par = self._construct(manifest_filename=manifest_file.name)
with self.assertRaises(error.Error):
par.create()
def test_create_source_file_not_found(self):
- with test_utils.temp_file('foo.py doesnotexist.py\n') as manifest_file:
+ with test_utils.temp_file(b'foo.py doesnotexist.py\n') as manifest_file:
par = self._construct(manifest_filename=manifest_file.name)
with self.assertRaises(OSError):
par.create()
@@ -114,7 +113,7 @@
par.create()
self.assertTrue(os.path.exists(self.output_filename))
self.assertEqual(
- subprocess.check_output([self.output_filename]), 'Hello World!\n')
+ subprocess.check_output([self.output_filename]), b'Hello World!\n')
def test_create_temp_parfile(self):
par = self._construct()
@@ -146,7 +145,7 @@
def test_scan_manifest_has_collision(self):
par = self._construct()
# Support file already present in manifest, use manifest version
- with test_utils.temp_file('blah blah\n') as shadowing_support_file:
+ with test_utils.temp_file(b'blah blah\n') as shadowing_support_file:
manifest = {
'foo.py': '/something/foo.py',
'subpar/runtime/support.py': shadowing_support_file.name,
@@ -189,7 +188,7 @@
tmpdir = test_utils.mkdtemp()
filename = os.path.join(tmpdir, 'afile')
with open(filename, 'wb') as f:
- f.write('dontcare')
+ f.write(b'dontcare')
# File exists
self.assertTrue(os.path.exists(filename))
python_archive.remove_if_present(filename)
@@ -198,6 +197,8 @@
python_archive.remove_if_present(filename)
self.assertFalse(os.path.exists(filename))
+ def test_fetch_support_file(self):
+ resource = python_archive.fetch_support_file('__init__.py')
if __name__ == '__main__':
unittest.main()
diff --git a/compiler/stored_resource_test.py b/compiler/stored_resource_test.py
index c3664b4..2856b0a 100644
--- a/compiler/stored_resource_test.py
+++ b/compiler/stored_resource_test.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -42,14 +40,14 @@
z.close()
def test_StoredFile(self):
- expected_content = 'Contents of foo/bar'
+ expected_content = b'Contents of foo/bar'
name = 'foo/bar'
f = test_utils.temp_file(expected_content)
resource = stored_resource.StoredFile(name, f.name)
self._write_and_check(resource, name, expected_content)
def test_StoredContent(self):
- expected_content = 'Contents of foo/bar'
+ expected_content = b'Contents of foo/bar'
name = 'foo/bar'
resource = stored_resource.StoredContent(name, expected_content)
self._write_and_check(resource, name, expected_content)
diff --git a/compiler/test_utils.py b/compiler/test_utils.py
index b1056c1..62a16c4 100644
--- a/compiler/test_utils.py
+++ b/compiler/test_utils.py
@@ -21,12 +21,7 @@
def get_test_tmpdir():
"""Get default test temp dir."""
- tmpdir = os.environ.get('TEST_TMPDIR', '')
- if not tmpdir:
- tempfile.gettempdir()
- testname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
- if testname:
- tmpdir = os.path.join(tmpdir, testname)
+ tmpdir = os.environ.get('TEST_TMPDIR', tempfile.gettempdir())
return tmpdir
@@ -35,8 +30,9 @@
return tempfile.mkdtemp(dir=get_test_tmpdir())
-def temp_file(contents, suffix='', tmpdir=None):
+def temp_file(contents, suffix=''):
"""Create a self-deleting temp file with the given content"""
+ tmpdir = get_test_tmpdir()
t = tempfile.NamedTemporaryFile(suffix=suffix, dir=tmpdir)
t.write(contents)
t.flush()
diff --git a/runtime/BUILD b/runtime/BUILD
index be4a37f..5357705 100644
--- a/runtime/BUILD
+++ b/runtime/BUILD
@@ -6,12 +6,28 @@
"support.py",
"//:__init__.py",
],
+ srcs_version = "PY2AND3",
)
py_test(
- name = "support_test",
+ name = "support_PY2_test",
size = "small",
srcs = ["support_test.py"],
+ default_python_version = "PY2",
+ main = "support_test.py",
+ srcs_version = "PY2AND3",
+ deps = [
+ ":support",
+ ],
+)
+
+py_test(
+ name = "support_PY3_test",
+ size = "small",
+ srcs = ["support_test.py"],
+ default_python_version = "PY3",
+ main = "support_test.py",
+ srcs_version = "PY2AND3",
deps = [
":support",
],
diff --git a/runtime/support.py b/runtime/support.py
index 5e6c259..53ad9b6 100644
--- a/runtime/support.py
+++ b/runtime/support.py
@@ -41,7 +41,7 @@
"""Print a debugging message in the same format as python -vv output"""
if sys.flags.verbose:
sys.stderr.write(msg)
- sys.stderr.write("\n")
+ sys.stderr.write('\n')
def _find_archive():
@@ -66,7 +66,7 @@
# Add third-party library entries to sys.path
archive_path = _find_archive()
if not archive_path:
- warnings.warn("Failed to initialize .par file runtime support",
+ warnings.warn('Failed to initialize .par file runtime support',
ImportWarning)
return
diff --git a/runtime/support_test.py b/runtime/support_test.py
index 950d769..9b9d8b0 100644
--- a/runtime/support_test.py
+++ b/runtime/support_test.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import StringIO
+import io
import sys
import unittest
@@ -26,12 +24,12 @@
def test_log(self):
old_stderr = sys.stderr
try:
- mock_stderr = StringIO.StringIO()
+ mock_stderr = io.StringIO()
sys.stderr = mock_stderr
# pylint: disable=protected-access,no-self-use
- support._log("Test Log Message")
+ support._log('Test Log Message')
if sys.flags.verbose:
- expected = "Test Log Message\n"
+ expected = 'Test Log Message\n'
else:
expected = ""
self.assertEqual(mock_stderr.getvalue(), expected)
diff --git a/subpar.bzl b/subpar.bzl
index ad68d30..e9f27ba 100644
--- a/subpar.bzl
+++ b/subpar.bzl
@@ -71,9 +71,9 @@
# Assemble command line for .par compiler
args = [
- '--imports_from_stub', stub_file,
'--manifest_file', sources_file.path,
'--outputpar', ctx.outputs.executable.path,
+ '--stub_file', stub_file,
main_py_file.path,
]
ctx.action(
@@ -82,7 +82,7 @@
progress_message='Building par file %s' % ctx.label,
executable=ctx.executable._compiler,
arguments=args,
- mnemonic="PythonCompile",
+ mnemonic='PythonCompile',
)
# .par file itself has no runfiles and no providers
@@ -109,6 +109,7 @@
single_file = True,
),
"imports": attr.string_list(default = []),
+ "default_python_version": attr.string(mandatory = True),
"_compiler": attr.label(
default = Label("//compiler"),
executable = True,
@@ -152,4 +153,6 @@
native.py_binary(name=name, **kwargs)
main = kwargs.get('main', name + '.py')
imports = kwargs.get('imports')
- parfile(name=name + '.par', src=name, main=main, imports=imports)
+ default_python_version = kwargs.get('default_python_version', 'PY2')
+ parfile(name=name + '.par', src=name, main=main, imports=imports,
+ default_python_version=default_python_version)
diff --git a/tests/BUILD b/tests/BUILD
index 7dd7011..43d3c3e 100644
--- a/tests/BUILD
+++ b/tests/BUILD
@@ -15,100 +15,84 @@
)
# Targets used by tests below
-par_binary(
- name = "package_c/c",
+[par_binary(
+ name = "package_c/c_%s" % version,
srcs = ["package_c/c.py"],
- deps = ["//tests/package_b:b"],
-)
+ default_python_version = version,
+ main = "package_c/c.py",
+ srcs_version = "PY2AND3",
+ deps = ["//tests/package_b:b_%s" % version],
+) for version in [
+ "PY2",
+ "PY3",
+]]
-par_binary(
- name = "package_d/d",
+[par_binary(
+ name = "package_d/d_%s" % version,
srcs = ["package_d/d.py"],
+ default_python_version = version,
imports = [
"package_b",
"package_c",
],
+ main = "package_d/d.py",
+ srcs_version = "PY2AND3",
deps = [
- "//tests:package_c/c",
- "//tests/package_b:b",
+ "//tests:package_c/c_%s" % version,
+ "//tests/package_b:b_%s" % version,
],
-)
+) for version in [
+ "PY2",
+ "PY3",
+]]
-par_binary(
- name = "package_e/e",
+[par_binary(
+ name = "package_e/e_%s" % version,
srcs = ["package_e/e.py"],
data = [
"@test_workspace//:data_file.txt",
],
+ default_python_version = version,
+ main = "package_e/e.py",
+ srcs_version = "PY2AND3",
+) for version in [
+ "PY2",
+ "PY3",
+]]
+
+par_binary(
+ name = "package_f/f_PY2",
+ srcs = ["package_f/f_PY2.py"],
+)
+
+par_binary(
+ name = "package_f/f_PY3",
+ srcs = ["package_f/f_PY3.py"],
+ default_python_version = "PY3",
+ srcs_version = "PY2AND3",
)
# Test targets
-sh_test(
- name = "basic_test",
+[sh_test(
+ name = "%s_%s" % (name, version),
size = "small",
srcs = ["test_harness.sh"],
args = [
- "tests/package_a/a.par",
- "tests/package_a/a_filelist.txt",
+ "tests%s_%s.par" % (path, version),
+ "tests%s_%s_filelist.txt" % (path, version),
],
data = [
- "//tests/package_a:a.par",
- "//tests/package_a:a_filelist.txt",
+ "//tests%s_%s.par" % (label, version),
+ "//tests%s_%s_filelist.txt" % (label, version),
],
-)
-
-sh_test(
- name = "direct_dependency_test",
- size = "small",
- srcs = ["test_harness.sh"],
- args = [
- "tests/package_b/b.par",
- "tests/package_b/b_filelist.txt",
- ],
- data = [
- "//tests/package_b:b.par",
- "//tests/package_b:b_filelist.txt",
- ],
-)
-
-sh_test(
- name = "indirect_dependency_test",
- size = "small",
- srcs = ["test_harness.sh"],
- args = [
- "tests/package_c/c.par",
- "tests/package_c/c_filelist.txt",
- ],
- data = [
- "//tests:package_c/c.par",
- "//tests:package_c/c_filelist.txt",
- ],
-)
-
-sh_test(
- name = "import_root_test",
- size = "small",
- srcs = ["test_harness.sh"],
- args = [
- "tests/package_d/d.par",
- "tests/package_d/d_filelist.txt",
- ],
- data = [
- "//tests:package_d/d.par",
- "//tests:package_d/d_filelist.txt",
- ],
-)
-
-sh_test(
- name = "external_workspace_test",
- size = "small",
- srcs = ["test_harness.sh"],
- args = [
- "tests/package_e/e.par",
- "tests/package_e/e_filelist.txt",
- ],
- data = [
- "//tests:package_e/e.par",
- "//tests:package_e/e_filelist.txt",
- ],
-)
+) for name, label, path in [
+ ("basic_test", "/package_a:a", "/package_a/a"),
+ ("direct_dependency_test", "/package_b:b", "/package_b/b"),
+ ("indirect_dependency_test", ":package_c/c", "/package_c/c"),
+ ("import_root_test", ":package_d/d", "/package_d/d"),
+ ("external_workspace_test", ":package_e/e", "/package_e/e"),
+ ("version_test", ":package_f/f", "/package_f/f"),
+] for version in [
+ "PY2",
+ "PY3",
+]]
diff --git a/tests/package_a/BUILD b/tests/package_a/BUILD
index b25a03e..589defd 100644
--- a/tests/package_a/BUILD
+++ b/tests/package_a/BUILD
@@ -4,19 +4,28 @@
load("//:subpar.bzl", "par_binary")
-exports_files(["a_filelist.txt"])
+exports_files([
+ "a_PY2_filelist.txt",
+ "a_PY3_filelist.txt",
+])
-par_binary(
- name = "a",
+[par_binary(
+ name = "a_%s" % version,
srcs = [
"a.py",
- "//:__init__.py",
],
data = ["a_dat.txt"],
-)
+ default_python_version = version,
+ main = "a.py",
+ srcs_version = "PY2AND3",
+) for version in [
+ "PY2",
+ "PY3",
+]]
py_library(
name = "a_lib",
srcs = ["a_lib.py"],
data = ["a_lib_dat.txt"],
+ srcs_version = "PY2AND3",
)
diff --git a/tests/package_a/a.py b/tests/package_a/a.py
index 9702fc4..2f0eea6 100644
--- a/tests/package_a/a.py
+++ b/tests/package_a/a.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -61,22 +59,22 @@
try:
# pylint: disable=import-self
from . import a as a1
- raise AssertionError("This shouldn't have worked: %r" % a1)
- except (AttributeError, ImportError) as e:
- assert e.message == 'cannot import name a', e
+ raise AssertionError('This shouldn\'t have worked: %r' % a1)
+ except ImportError as e:
+ assert 'cannot import name' in str(e), e
try:
# pylint: disable=import-self
import subpar.tests.package_a.a as a2
- raise AssertionError("This shouldn't have worked: %r" % a2)
- except (AttributeError, ImportError) as e:
- assert e.message == "'module' object has no attribute 'a'", e
+ raise AssertionError('This shouldn\'t have worked: %r' % a2)
+ except AttributeError as e:
+ assert "'module' object has no attribute 'a'" in str(e), e
def main():
print('In a.py main()')
# Test resource extraction
a_dat = pkgutil.get_data('subpar.tests.package_a', 'a_dat.txt')
- assert (a_dat == "Dummy data file for a.py\n"), a_dat
+ assert (a_dat == b'Dummy data file for a.py\n'), a_dat
if __name__ == '__main__':
diff --git a/tests/package_a/a_filelist.txt b/tests/package_a/a_PY2_filelist.txt
similarity index 87%
copy from tests/package_a/a_filelist.txt
copy to tests/package_a/a_PY2_filelist.txt
index bdbf7a5..1976cd0 100644
--- a/tests/package_a/a_filelist.txt
+++ b/tests/package_a/a_PY2_filelist.txt
@@ -4,6 +4,6 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY2
subpar/tests/package_a/a_dat.txt
diff --git a/tests/package_a/a_filelist.txt b/tests/package_a/a_PY3_filelist.txt
similarity index 87%
rename from tests/package_a/a_filelist.txt
rename to tests/package_a/a_PY3_filelist.txt
index bdbf7a5..606238e 100644
--- a/tests/package_a/a_filelist.txt
+++ b/tests/package_a/a_PY3_filelist.txt
@@ -4,6 +4,6 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY3
subpar/tests/package_a/a_dat.txt
diff --git a/tests/package_a/a_lib.py b/tests/package_a/a_lib.py
index 895f253..4bf4df6 100644
--- a/tests/package_a/a_lib.py
+++ b/tests/package_a/a_lib.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,4 +21,4 @@
print('In a_lib.py lib()')
# Test resource extraction
a_lib_dat = pkgutil.get_data('subpar.tests.package_a', 'a_lib_dat.txt')
- assert (a_lib_dat == "Dummy data file for a_lib.py\n"), a_lib_dat
+ assert (a_lib_dat == b'Dummy data file for a_lib.py\n'), a_lib_dat
diff --git a/tests/package_b/BUILD b/tests/package_b/BUILD
index 9f11f8a..e222c8d 100644
--- a/tests/package_b/BUILD
+++ b/tests/package_b/BUILD
@@ -4,14 +4,23 @@
load("//:subpar.bzl", "par_binary")
-exports_files(["b_filelist.txt"])
+exports_files([
+ "b_PY2_filelist.txt",
+ "b_PY3_filelist.txt",
+])
-par_binary(
- name = "b",
+[par_binary(
+ name = "b_%s" % version,
srcs = ["b.py"],
data = ["b_dat.txt"],
+ default_python_version = version,
+ main = "b.py",
+ srcs_version = "PY2AND3",
deps = [
- "//tests/package_a:a",
+ "//tests/package_a:a_%s" % version,
"//tests/package_a:a_lib",
],
-)
+) for version in [
+ "PY2",
+ "PY3",
+]]
diff --git a/tests/package_b/b.py b/tests/package_b/b.py
index a96d0a5..07ed083 100644
--- a/tests/package_b/b.py
+++ b/tests/package_b/b.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,7 +30,7 @@
print('In b.py main()')
# Test resource extraction
b_dat = pkgutil.get_data('subpar.tests.package_b', 'b_dat.txt')
- assert (b_dat == "Dummy data file for b.py\n"), b_dat
+ assert (b_dat == b'Dummy data file for b.py\n'), b_dat
if __name__ == '__main__':
diff --git a/tests/package_b/b_filelist.txt b/tests/package_b/b_PY2_filelist.txt
similarity index 86%
copy from tests/package_b/b_filelist.txt
copy to tests/package_b/b_PY2_filelist.txt
index 9177503..f6b0955 100644
--- a/tests/package_b/b_filelist.txt
+++ b/tests/package_b/b_PY2_filelist.txt
@@ -4,12 +4,12 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY2
subpar/tests/package_a/a_dat.txt
subpar/tests/package_a/a_lib.py
subpar/tests/package_a/a_lib_dat.txt
subpar/tests/package_b/__init__.py
-subpar/tests/package_b/b
subpar/tests/package_b/b.py
+subpar/tests/package_b/b_PY2
subpar/tests/package_b/b_dat.txt
diff --git a/tests/package_b/b_filelist.txt b/tests/package_b/b_PY3_filelist.txt
similarity index 86%
rename from tests/package_b/b_filelist.txt
rename to tests/package_b/b_PY3_filelist.txt
index 9177503..7ace434 100644
--- a/tests/package_b/b_filelist.txt
+++ b/tests/package_b/b_PY3_filelist.txt
@@ -4,12 +4,12 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY3
subpar/tests/package_a/a_dat.txt
subpar/tests/package_a/a_lib.py
subpar/tests/package_a/a_lib_dat.txt
subpar/tests/package_b/__init__.py
-subpar/tests/package_b/b
subpar/tests/package_b/b.py
+subpar/tests/package_b/b_PY3
subpar/tests/package_b/b_dat.txt
diff --git a/tests/package_c/c.py b/tests/package_c/c.py
index 6f941ca..3b8bf3d 100644
--- a/tests/package_c/c.py
+++ b/tests/package_c/c.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tests/package_c/c_filelist.txt b/tests/package_c/c_PY2_filelist.txt
similarity index 83%
copy from tests/package_c/c_filelist.txt
copy to tests/package_c/c_PY2_filelist.txt
index 361772f..5506e91 100644
--- a/tests/package_c/c_filelist.txt
+++ b/tests/package_c/c_PY2_filelist.txt
@@ -4,15 +4,15 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY2
subpar/tests/package_a/a_dat.txt
subpar/tests/package_a/a_lib.py
subpar/tests/package_a/a_lib_dat.txt
subpar/tests/package_b/__init__.py
-subpar/tests/package_b/b
subpar/tests/package_b/b.py
+subpar/tests/package_b/b_PY2
subpar/tests/package_b/b_dat.txt
subpar/tests/package_c/__init__.py
-subpar/tests/package_c/c
subpar/tests/package_c/c.py
+subpar/tests/package_c/c_PY2
diff --git a/tests/package_c/c_filelist.txt b/tests/package_c/c_PY3_filelist.txt
similarity index 83%
rename from tests/package_c/c_filelist.txt
rename to tests/package_c/c_PY3_filelist.txt
index 361772f..507636e 100644
--- a/tests/package_c/c_filelist.txt
+++ b/tests/package_c/c_PY3_filelist.txt
@@ -4,15 +4,15 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY3
subpar/tests/package_a/a_dat.txt
subpar/tests/package_a/a_lib.py
subpar/tests/package_a/a_lib_dat.txt
subpar/tests/package_b/__init__.py
-subpar/tests/package_b/b
subpar/tests/package_b/b.py
+subpar/tests/package_b/b_PY3
subpar/tests/package_b/b_dat.txt
subpar/tests/package_c/__init__.py
-subpar/tests/package_c/c
subpar/tests/package_c/c.py
+subpar/tests/package_c/c_PY3
diff --git a/tests/package_d/d.py b/tests/package_d/d.py
index 12e9373..e05906f 100644
--- a/tests/package_d/d.py
+++ b/tests/package_d/d.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tests/package_d/d_filelist.txt b/tests/package_d/d_PY2_filelist.txt
similarity index 81%
rename from tests/package_d/d_filelist.txt
rename to tests/package_d/d_PY2_filelist.txt
index 5e460e0..f38f57c 100644
--- a/tests/package_d/d_filelist.txt
+++ b/tests/package_d/d_PY2_filelist.txt
@@ -4,18 +4,18 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY2
subpar/tests/package_a/a_dat.txt
subpar/tests/package_a/a_lib.py
subpar/tests/package_a/a_lib_dat.txt
subpar/tests/package_b/__init__.py
-subpar/tests/package_b/b
subpar/tests/package_b/b.py
+subpar/tests/package_b/b_PY2
subpar/tests/package_b/b_dat.txt
subpar/tests/package_c/__init__.py
-subpar/tests/package_c/c
subpar/tests/package_c/c.py
+subpar/tests/package_c/c_PY2
subpar/tests/package_d/__init__.py
-subpar/tests/package_d/d
subpar/tests/package_d/d.py
+subpar/tests/package_d/d_PY2
diff --git a/tests/package_d/d_filelist.txt b/tests/package_d/d_PY3_filelist.txt
similarity index 81%
copy from tests/package_d/d_filelist.txt
copy to tests/package_d/d_PY3_filelist.txt
index 5e460e0..b29dfff 100644
--- a/tests/package_d/d_filelist.txt
+++ b/tests/package_d/d_PY3_filelist.txt
@@ -4,18 +4,18 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_a/__init__.py
-subpar/tests/package_a/a
subpar/tests/package_a/a.py
+subpar/tests/package_a/a_PY3
subpar/tests/package_a/a_dat.txt
subpar/tests/package_a/a_lib.py
subpar/tests/package_a/a_lib_dat.txt
subpar/tests/package_b/__init__.py
-subpar/tests/package_b/b
subpar/tests/package_b/b.py
+subpar/tests/package_b/b_PY3
subpar/tests/package_b/b_dat.txt
subpar/tests/package_c/__init__.py
-subpar/tests/package_c/c
subpar/tests/package_c/c.py
+subpar/tests/package_c/c_PY3
subpar/tests/package_d/__init__.py
-subpar/tests/package_d/d
subpar/tests/package_d/d.py
+subpar/tests/package_d/d_PY3
diff --git a/tests/package_e/e.py b/tests/package_e/e.py
index 83f09e8..1345b93 100644
--- a/tests/package_e/e.py
+++ b/tests/package_e/e.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python2
-
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tests/package_e/e_filelist.txt b/tests/package_e/e_PY2_filelist.txt
similarity index 87%
rename from tests/package_e/e_filelist.txt
rename to tests/package_e/e_PY2_filelist.txt
index d0688e7..4a23e16 100644
--- a/tests/package_e/e_filelist.txt
+++ b/tests/package_e/e_PY2_filelist.txt
@@ -4,6 +4,6 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_e/__init__.py
-subpar/tests/package_e/e
subpar/tests/package_e/e.py
+subpar/tests/package_e/e_PY2
test_workspace/data_file.txt
diff --git a/tests/package_e/e_filelist.txt b/tests/package_e/e_PY3_filelist.txt
similarity index 87%
copy from tests/package_e/e_filelist.txt
copy to tests/package_e/e_PY3_filelist.txt
index d0688e7..48736c6 100644
--- a/tests/package_e/e_filelist.txt
+++ b/tests/package_e/e_PY3_filelist.txt
@@ -4,6 +4,6 @@
subpar/runtime/support.py
subpar/tests/__init__.py
subpar/tests/package_e/__init__.py
-subpar/tests/package_e/e
subpar/tests/package_e/e.py
+subpar/tests/package_e/e_PY3
test_workspace/data_file.txt
diff --git a/tests/package_f/f_PY2.py b/tests/package_f/f_PY2.py
new file mode 100644
index 0000000..74d2b27
--- /dev/null
+++ b/tests/package_f/f_PY2.py
@@ -0,0 +1,28 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Integration test program F for Subpar.
+
+Test Python2 specific functionality
+"""
+
+import sys
+
+def main():
+ assert sys.version_info.major == 2, sys.version
+ print('In f_PY2.py main()')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/package_f/f_PY2_filelist.txt b/tests/package_f/f_PY2_filelist.txt
new file mode 100644
index 0000000..8b382cb
--- /dev/null
+++ b/tests/package_f/f_PY2_filelist.txt
@@ -0,0 +1,8 @@
+__main__.py
+subpar/__init__.py
+subpar/runtime/__init__.py
+subpar/runtime/support.py
+subpar/tests/__init__.py
+subpar/tests/package_f/__init__.py
+subpar/tests/package_f/f_PY2
+subpar/tests/package_f/f_PY2.py
diff --git a/tests/package_f/f_PY3.py b/tests/package_f/f_PY3.py
new file mode 100644
index 0000000..e421ead
--- /dev/null
+++ b/tests/package_f/f_PY3.py
@@ -0,0 +1,28 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Integration test program F for Subpar.
+
+Test Python3 specific functionality
+"""
+
+import sys
+
+def main():
+ assert sys.version_info.major == 3, sys.version
+ print('In f_PY2.py main()')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/package_f/f_PY3_filelist.txt b/tests/package_f/f_PY3_filelist.txt
new file mode 100644
index 0000000..b9ba660
--- /dev/null
+++ b/tests/package_f/f_PY3_filelist.txt
@@ -0,0 +1,8 @@
+__main__.py
+subpar/__init__.py
+subpar/runtime/__init__.py
+subpar/runtime/support.py
+subpar/tests/__init__.py
+subpar/tests/package_f/__init__.py
+subpar/tests/package_f/f_PY3
+subpar/tests/package_f/f_PY3.py