From 4e9316036c43b15c1264ca0bedbb1a7bb37591ba Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 7 Jan 2020 00:17:03 +0100 Subject: [PATCH] add before test variable --- cibuildwheel/__main__.py | 2 + cibuildwheel/linux.py | 9 +- cibuildwheel/macos.py | 6 +- cibuildwheel/windows.py | 6 +- test/10_before_test/cibuildwheel_test.py | 22 +++ test/10_before_test/setup.py | 19 ++ test/10_before_test/spam.c | 48 +++++ test/10_before_test/test/spam_test.py | 24 +++ unit_test/main_options_test.py | 221 +++++++++++++++++++++++ 9 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 test/10_before_test/cibuildwheel_test.py create mode 100644 test/10_before_test/setup.py create mode 100644 test/10_before_test/spam.c create mode 100644 test/10_before_test/test/spam_test.py create mode 100644 unit_test/main_options_test.py diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index aa7f6587..e20028d8 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -113,6 +113,7 @@ def main(): repair_command_default = '' repair_command = get_option_from_environment('CIBW_REPAIR_WHEEL_COMMAND', platform=platform, default=repair_command_default) environment_config = get_option_from_environment('CIBW_ENVIRONMENT', platform=platform, default='') + before_test = get_option_from_environment('CIBW_BEFORE_TEST', platform=platform, default='') if test_extras: test_extras = '[{0}]'.format(test_extras) @@ -154,6 +155,7 @@ def main(): build_selector=build_selector, repair_command=repair_command, environment=environment, + before_test=before_test ) if platform == 'linux': diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 20dae7e6..753da624 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -68,7 +68,7 @@ def get_python_configurations(build_selector): return [c for c in python_configurations if matches_platform(c.identifier) and build_selector(c.identifier)] -def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, manylinux_images): +def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, before_test, manylinux_images): try: subprocess.check_call(['docker', '--version']) except Exception: @@ -154,6 +154,10 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef echo "Running tests using `which python`" + if [ ! -z {before_test} ]; then + sh -c {before_test} + fi + # Install the wheel we just built # Note: If auditwheel produced two wheels, it's because the earlier produced wheel # conforms to multiple manylinux standards. These multiple versions of the wheel are @@ -202,6 +206,9 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef environment_exports='\n'.join(environment.as_shell_commands()), uid=os.getuid(), gid=os.getgid(), + before_test=shlex_quote( + prepare_command(before_test, project='/project') if before_test else '' + ), ) container_name = 'cibuildwheel-{}'.format(uuid.uuid4()) diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index 3b3edcba..50b6392a 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -105,7 +105,7 @@ def install_pypy(version, url): return installation_bin_path -def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment): +def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, before_test): abs_project_dir = os.path.abspath(project_dir) temp_dir = tempfile.mkdtemp(prefix='cibuildwheel') built_wheel_dir = os.path.join(temp_dir, 'built_wheel') @@ -214,6 +214,10 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef # check that we are using the Python from the virtual environment call(['which', 'python'], env=virtualenv_env) + if before_test: + before_test_prepared = prepare_command(before_test, project=abs_project_dir) + call(before_test_prepared, env=virtualenv_env, shell=True) + # install the wheel call(['pip', 'install', repaired_wheel + test_extras], env=virtualenv_env) diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 24e19c38..15a53209 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -87,7 +87,7 @@ def install_pypy(version, arch, url): return installation_path -def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment): +def build(project_dir, output_dir, test_command, test_requires, test_extras, before_build, build_verbosity, build_selector, repair_command, environment, before_test): abs_project_dir = os.path.abspath(project_dir) temp_dir = tempfile.mkdtemp(prefix='cibuildwheel') built_wheel_dir = os.path.join(temp_dir, 'built_wheel') @@ -193,6 +193,10 @@ def build(project_dir, output_dir, test_command, test_requires, test_extras, bef # check that we are using the Python from the virtual environment shell(['which', 'python'], env=virtualenv_env) + if before_test: + before_test_prepared = prepare_command(before_test, project=abs_project_dir) + shell([before_test_prepared], env=virtualenv_env) + # install the wheel shell(['pip', 'install', repaired_wheel + test_extras], env=virtualenv_env) diff --git a/test/10_before_test/cibuildwheel_test.py b/test/10_before_test/cibuildwheel_test.py new file mode 100644 index 00000000..f6a56af4 --- /dev/null +++ b/test/10_before_test/cibuildwheel_test.py @@ -0,0 +1,22 @@ +import os +import utils + +def test(): + project_dir = os.path.dirname(__file__) + + # build the wheels + actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ + # write python version information to a temporary file, this is + # checked in setup.py + 'CIBW_BEFORE_TEST': '''python -c "import sys; open('/tmp/pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('/tmp/pythonexecutable.txt', 'w').write(sys.executable)"''', + 'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonexecutable.txt', 'w').write(sys.executable)"''', + 'CIBW_TEST_REQUIRES': 'nose', + # the 'false ||' bit is to ensure this command runs in a shell on + # mac/linux. + 'CIBW_TEST_COMMAND': 'false || nosetests {project}/test', + 'CIBW_TEST_COMMAND_WINDOWS': 'nosetests {project}/test', + }) + + # also check that we got the right wheels + expected_wheels = utils.expected_wheels('spam', '0.1.0') + assert set(actual_wheels) == set(expected_wheels) diff --git a/test/10_before_test/setup.py b/test/10_before_test/setup.py new file mode 100644 index 00000000..8abc7921 --- /dev/null +++ b/test/10_before_test/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, Extension +import sys, os + +# assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_BUILD step +# is the same one as is currently running. +version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt' +if os.path.exists(version_file): + os.remove(version_file) + +# check that the executable also was written +executable_file = 'c:\\pythonexecutable.txt' if sys.platform == 'win32' else '/tmp/pythonexecutable.txt' +if os.path.exists(executable_file): + os.remove(executable_file) + +setup( + name="spam", + ext_modules=[Extension('spam', sources=['spam.c'])], + version="0.1.0", +) diff --git a/test/10_before_test/spam.c b/test/10_before_test/spam.c new file mode 100644 index 00000000..d1ab0f22 --- /dev/null +++ b/test/10_before_test/spam.c @@ -0,0 +1,48 @@ +#include + +static PyObject * +spam_system(PyObject *self, PyObject *args) +{ + const char *command; + int sts; + + if (!PyArg_ParseTuple(args, "s", &command)) + return NULL; + sts = system(command); + return PyLong_FromLong(sts); +} + +/* Module initialization */ + +#if PY_MAJOR_VERSION >= 3 + #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void) + #define MOD_DEF(m, name, doc, methods, module_state_size) \ + static struct PyModuleDef moduledef = { \ + PyModuleDef_HEAD_INIT, name, doc, module_state_size, methods, }; \ + m = PyModule_Create(&moduledef); + #define MOD_RETURN(m) return m; +#else + #define MOD_INIT(name) PyMODINIT_FUNC init##name(void) + #define MOD_DEF(m, name, doc, methods, module_state_size) \ + m = Py_InitModule3(name, methods, doc); + #define MOD_RETURN(m) return; +#endif + +static PyMethodDef module_methods[] = { + {"system", (PyCFunction)spam_system, METH_VARARGS, + "Execute a shell command."}, + {NULL} /* Sentinel */ +}; + +MOD_INIT(spam) +{ + PyObject* m; + + MOD_DEF(m, + "spam", + "Example module", + module_methods, + -1) + + MOD_RETURN(m) +} diff --git a/test/10_before_test/test/spam_test.py b/test/10_before_test/test/spam_test.py new file mode 100644 index 00000000..7560b438 --- /dev/null +++ b/test/10_before_test/test/spam_test.py @@ -0,0 +1,24 @@ +import sys, os +from unittest import TestCase + + +class TestBeforeTest(TestCase): + def test_version(self): + # assert that the Python version as written to pythonversion.txt in the CIBW_BEFORE_BUILD step + # is the same one as is currently running. + version_file = 'c:\\pythonversion.txt' if sys.platform == 'win32' else '/tmp/pythonversion.txt' + with open(version_file) as f: + stored_version = f.read() + print('stored_version', stored_version) + print('sys.version', sys.version) + assert stored_version == sys.version + + def test_executable(self): + # check that the executable also was written + executable_file = 'c:\\pythonexecutable.txt' if sys.platform == 'win32' else '/tmp/pythonexecutable.txt' + with open(executable_file) as f: + stored_executable = f.read() + print('stored_executable', stored_executable) + print('sys.executable', sys.executable) + # windows/mac are case insensitive + assert os.path.realpath(stored_executable).lower() == os.path.realpath(sys.executable).lower() diff --git a/unit_test/main_options_test.py b/unit_test/main_options_test.py new file mode 100644 index 00000000..99387bf5 --- /dev/null +++ b/unit_test/main_options_test.py @@ -0,0 +1,221 @@ +import pytest + +import sys + +from cibuildwheel.__main__ import main +from cibuildwheel.environment import ParsedEnvironment +from cibuildwheel.util import BuildSelector + +from main_util_fixtures import mock_protection, fake_project_dir, platform, intercepted_build_args + + + +# CIBW_PLATFORM is tested in main_platform_test.py + + +def test_output_dir(platform, intercepted_build_args, monkeypatch): + OUTPUT_DIR = 'some_output_dir' + + monkeypatch.setenv('CIBW_OUTPUT_DIR', OUTPUT_DIR) + + main() + + assert intercepted_build_args.kwargs['output_dir'] == OUTPUT_DIR + + +def test_output_dir_default(platform, intercepted_build_args, monkeypatch): + main() + + assert intercepted_build_args.kwargs['output_dir'] == 'wheelhouse' + + +@pytest.mark.parametrize('also_set_environment', [False, True]) +def test_output_dir_argument(also_set_environment, platform, intercepted_build_args, monkeypatch): + OUTPUT_DIR = 'some_output_dir' + + monkeypatch.setattr(sys, 'argv', sys.argv + ['--output-dir', OUTPUT_DIR]) + if also_set_environment: + monkeypatch.setenv('CIBW_OUTPUT_DIR', 'not_this_output_dir') + + main() + + assert intercepted_build_args.kwargs['output_dir'] == OUTPUT_DIR + + +def test_build_selector(platform, intercepted_build_args, monkeypatch): + BUILD = 'some build* *-selector' + SKIP = 'some skip* *-selector' + + monkeypatch.setenv('CIBW_BUILD', BUILD) + monkeypatch.setenv('CIBW_SKIP', SKIP) + + main() + + intercepted_build_selector = intercepted_build_args.kwargs['build_selector'] + assert isinstance(intercepted_build_selector, BuildSelector) + assert intercepted_build_selector('build-this') + assert not intercepted_build_selector('skip-that') + # This unit test is just testing the options of 'main' + # Unit tests for BuildSelector are in build_selector_test.py + + +@pytest.mark.parametrize('architecture, image, full_image', [ + ('x86_64', None, 'quay.io/pypa/manylinux2010_x86_64'), + ('x86_64', 'manylinux1', 'quay.io/pypa/manylinux1_x86_64'), + ('x86_64', 'manylinux2010', 'quay.io/pypa/manylinux2010_x86_64'), + ('x86_64', 'manylinux2014', 'quay.io/pypa/manylinux2014_x86_64'), + ('x86_64', 'custom_image', 'custom_image'), + ('i686', None, 'quay.io/pypa/manylinux2010_i686'), + ('i686', 'manylinux1', 'quay.io/pypa/manylinux1_i686'), + ('i686', 'manylinux2010', 'quay.io/pypa/manylinux2010_i686'), + ('i686', 'manylinux2014', 'quay.io/pypa/manylinux2014_i686'), + ('i686', 'custom_image', 'custom_image'), +]) +def test_manylinux_images(architecture, image, full_image, platform, intercepted_build_args, monkeypatch): + if image is not None: + monkeypatch.setenv('CIBW_MANYLINUX_' + architecture.upper() + '_IMAGE', image) + + main() + + if platform == 'linux': + assert intercepted_build_args.kwargs['manylinux_images'][architecture] == full_image + else: + assert 'manylinux_images' not in intercepted_build_args.kwargs + + +def get_default_repair_command(platform): + if platform == 'linux': + return 'auditwheel repair -w {dest_dir} {wheel}' + elif platform == 'macos': + return 'delocate-listdeps {wheel} && delocate-wheel -w {dest_dir} {wheel}' + elif platform == 'windows': + return '' + else: + raise ValueError('Unknown platform', platform) + +@pytest.mark.parametrize('repair_command', [None, 'repair', 'repair -w {dest_dir} {wheel}']) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_repair_command(repair_command, platform_specific, platform, intercepted_build_args, monkeypatch): + if repair_command is not None: + if platform_specific: + monkeypatch.setenv('CIBW_REPAIR_WHEEL_COMMAND_' + platform.upper(), repair_command) + monkeypatch.setenv('CIBW_REPAIR_WHEEL_COMMAND', 'overwritten') + else: + monkeypatch.setenv('CIBW_REPAIR_WHEEL_COMMAND', repair_command) + + main() + + expected_repair = repair_command or get_default_repair_command(platform) + assert intercepted_build_args.kwargs['repair_command'] == expected_repair + + +@pytest.mark.parametrize('environment', [ + {}, + {'something': 'value'}, + {'something': 'value', 'something_else': 'other_value'} +]) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_environment(environment, platform_specific, platform, intercepted_build_args, monkeypatch): + env_string = ' '.join(['{}={}'.format(k, v) for k, v in environment.items()]) + if platform_specific: + monkeypatch.setenv('CIBW_ENVIRONMENT_' + platform.upper(), env_string) + monkeypatch.setenv('CIBW_ENVIRONMENT', 'overwritten') + else: + monkeypatch.setenv('CIBW_ENVIRONMENT', env_string) + + main() + + intercepted_environment = intercepted_build_args.kwargs['environment'] + assert isinstance(intercepted_environment, ParsedEnvironment) + assert intercepted_environment.as_dictionary(prev_environment={}) == environment + + +@pytest.mark.parametrize('test_requires', [None, 'requirement other_requirement']) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_test_requires(test_requires, platform_specific, platform, intercepted_build_args, monkeypatch): + if test_requires is not None: + if platform_specific: + monkeypatch.setenv('CIBW_TEST_REQUIRES_' + platform.upper(), test_requires) + monkeypatch.setenv('CIBW_TEST_REQUIRES', 'overwritten') + else: + monkeypatch.setenv('CIBW_TEST_REQUIRES', test_requires) + + main() + + assert intercepted_build_args.kwargs['test_requires'] == (test_requires or '').split() + + +@pytest.mark.parametrize('test_extras', [None, 'extras']) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_test_extras(test_extras, platform_specific, platform, intercepted_build_args, monkeypatch): + if test_extras is not None: + if platform_specific: + monkeypatch.setenv('CIBW_TEST_EXTRAS_' + platform.upper(), test_extras) + monkeypatch.setenv('CIBW_TEST_EXTRAS', 'overwritten') + else: + monkeypatch.setenv('CIBW_TEST_EXTRAS', test_extras) + + main() + + assert intercepted_build_args.kwargs['test_extras'] == ('[' + test_extras + ']' if test_extras else '') + + +@pytest.mark.parametrize('test_command', [None, 'test --command']) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_test_command(test_command, platform_specific, platform, intercepted_build_args, monkeypatch): + if test_command is not None: + if platform_specific: + monkeypatch.setenv('CIBW_TEST_COMMAND_' + platform.upper(), test_command) + monkeypatch.setenv('CIBW_TEST_COMMAND', 'overwritten') + else: + monkeypatch.setenv('CIBW_TEST_COMMAND', test_command) + + main() + + assert intercepted_build_args.kwargs['test_command'] == test_command + + +@pytest.mark.parametrize('before_build', [None, 'before --build']) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_before_build(before_build, platform_specific, platform, intercepted_build_args, monkeypatch): + if before_build is not None: + if platform_specific: + monkeypatch.setenv('CIBW_BEFORE_BUILD_' + platform.upper(), before_build) + monkeypatch.setenv('CIBW_BEFORE_BUILD', 'overwritten') + else: + monkeypatch.setenv('CIBW_BEFORE_BUILD', before_build) + + main() + + assert intercepted_build_args.kwargs['before_build'] == before_build + + +@pytest.mark.parametrize('build_verbosity', [None, 0, 2, -2, 4, -4]) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_build_verbosity(build_verbosity, platform_specific, platform, intercepted_build_args, monkeypatch): + if build_verbosity is not None: + if platform_specific: + monkeypatch.setenv('CIBW_BUILD_VERBOSITY_' + platform.upper(), str(build_verbosity)) + monkeypatch.setenv('CIBW_BUILD_VERBOSITY', 'overwritten') + else: + monkeypatch.setenv('CIBW_BUILD_VERBOSITY', str(build_verbosity)) + + main() + + expected_verbosity = max(-3, min(3, int(build_verbosity or 0))) + assert intercepted_build_args.kwargs['build_verbosity'] == expected_verbosity + + +@pytest.mark.parametrize('before_test', ["", 'before --test']) +@pytest.mark.parametrize('platform_specific', [False, True]) +def test_before_test(before_test, platform_specific, platform, intercepted_build_args, monkeypatch): + if before_test is not None: + if platform_specific: + monkeypatch.setenv('CIBW_BEFORE_TEST_' + platform.upper(), before_test) + monkeypatch.setenv('CIBW_BEFORE_TEST', 'overwritten') + else: + monkeypatch.setenv('CIBW_BEFORE_TEST', before_test) + + main() + + assert intercepted_build_args.kwargs['before_test'] == before_test \ No newline at end of file