diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b2d324be..4fb53b11 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,7 @@ jobs: inputs: versionSpec: '3.8' - bash: | - python -m pip install -r requirements-dev.txt + python -m pip install -r requirements-dev.txt pytest-azurepipelines python ./bin/run_tests.py - job: macos_38 @@ -16,7 +16,7 @@ jobs: inputs: versionSpec: '3.8' - bash: | - python -m pip install -r requirements-dev.txt + python -m pip install -r requirements-dev.txt pytest-azurepipelines python ./bin/run_tests.py - job: windows_36 @@ -28,7 +28,7 @@ jobs: - script: choco install vcpython27 -f -y displayName: Install Visual C++ for Python 2.7 - bash: | - python -m pip install -r requirements-dev.txt + python -m pip install -r requirements-dev.txt pytest-azurepipelines python ./bin/run_tests.py - job: windows_38 @@ -40,6 +40,6 @@ jobs: - script: choco install vcpython27 -f -y displayName: Install Visual C++ for Python 2.7 - bash: | - python -m pip install -r requirements-dev.txt + python -m pip install -r requirements-dev.txt pytest-azurepipelines python ./bin/run_tests.py diff --git a/bin/dev_run_test b/bin/dev_run_test index 86500be2..85a0d81c 100755 --- a/bin/dev_run_test +++ b/bin/dev_run_test @@ -1,6 +1,3 @@ #!/bin/bash -cd "$(dirname "$0")" -cd .. - -CIBW_PLATFORM=linux ./bin/run_test.py $1 +CIBW_PLATFORM=linux pytest "$@" diff --git a/bin/run_test.py b/bin/run_test.py deleted file mode 100755 index bee65de4..00000000 --- a/bin/run_test.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import os -import subprocess -import sys - - -def single_run(test_project): - # run the test - subprocess.check_call( - [sys.executable, '-m', 'pytest', '-vv', os.path.join(test_project, 'cibuildwheel_test.py')], - ) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("test_project_dir") - args = parser.parse_args() - - project_path = os.path.abspath(args.test_project_dir) - - if not os.path.exists(project_path): - print('No test project not found.', file=sys.stderr) - exit(2) - - single_run(project_path) diff --git a/bin/run_tests.py b/bin/run_tests.py index 4963a883..93c23e88 100755 --- a/bin/run_tests.py +++ b/bin/run_tests.py @@ -3,28 +3,13 @@ import os import subprocess import sys -from glob import glob if __name__ == '__main__': # move cwd to the project root os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # run the unit tests - subprocess.check_call([sys.executable, '-m', 'pytest', 'unit_test']) # run the integration tests - - test_projects = sorted(glob('test/??_*')) - - if len(test_projects) == 0: - print('No test projects found. Aborting.', file=sys.stderr) - exit(2) - - print('Testing projects:', test_projects) - - run_test_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'run_test.py') - for project_path in test_projects: - subprocess.check_call([sys.executable, run_test_path, project_path]) - - print('%d projects built successfully.' % len(test_projects)) + subprocess.check_call([sys.executable, '-m', 'pytest', '-x', '--durations', '0', 'test']) diff --git a/setup.cfg b/setup.cfg index e01a4e25..d54a42bb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] -ignore = E501,W503 +ignore = E501,W503,E741 application-import-names = cibuildwheel exclude = cibuildwheel/resources/, @@ -12,6 +12,29 @@ exclude = .venv/, site/ +[tool:pytest] +junit_family=xunit2 + [mypy] -files=cibuildwheel/ -strict=True +python_version = 3.6 +files = cibuildwheel,test + +warn_unused_configs = True +warn_redundant_casts = True + +[mypy-cibuildwheel] +disallow_any_generics = True +disallow_subclassing_any = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True +warn_unused_ignores = True +warn_return_any = True +no_implicit_reexport = True +strict_equality = True + +[mypy-pytest,setuptools] +ignore_missing_imports = True diff --git a/test/01_basic/setup.py b/test/01_basic/setup.py deleted file mode 100644 index 83f6d53d..00000000 --- a/test/01_basic/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - -from setuptools import ( - Extension, - setup, -) - -if os.environ.get('CIBUILDWHEEL', '0') != '1': - raise Exception('CIBUILDWHEEL environment variable is not set to 1') - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/01_basic/spam.c b/test/01_basic/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/01_basic/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/02_test/cibuildwheel_test.py b/test/02_test/cibuildwheel_test.py deleted file mode 100644 index 21878d2c..00000000 --- a/test/02_test/cibuildwheel_test.py +++ /dev/null @@ -1,55 +0,0 @@ -import os -import subprocess - -import pytest - -import utils - - -def test(): - project_dir = os.path.dirname(__file__) - - # build and test the wheels - actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ - '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': 'COLOR 00 || 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) - - -def test_extras_require(): - project_dir = os.path.dirname(__file__) - - # build and test the wheels - actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ - 'CIBW_TEST_EXTRAS': 'test', - # 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': 'COLOR 00 || 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) - - -def test_failing_test(tmp_path): - '''Ensure a failing test causes cibuildwheel to error out and exit''' - project_dir = os.path.dirname(__file__) - - with pytest.raises(subprocess.CalledProcessError): - utils.cibuildwheel_run(project_dir, output_dir=tmp_path, add_env={ - 'CIBW_TEST_COMMAND': 'false', - # manylinux1 has a version of bash that's been shown to have - # problems with this, so let's check that. - 'CIBW_MANYLINUX_I686_IMAGE': 'manylinux1', - 'CIBW_MANYLINUX_X86_64_IMAGE': 'manylinux1', - }) - assert len(os.listdir(str(tmp_path))) == 0 diff --git a/test/02_test/setup.py b/test/02_test/setup.py deleted file mode 100644 index 3a150b3d..00000000 --- a/test/02_test/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -from setuptools import ( - Extension, - setup, -) - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - extras_require={'test': ['nose']}, - version="0.1.0", -) diff --git a/test/02_test/spam.c b/test/02_test/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/02_test/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/02_test/test/spam_test.py b/test/02_test/test/spam_test.py deleted file mode 100644 index f70bdae8..00000000 --- a/test/02_test/test/spam_test.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import print_function -import os -import platform -import sys -import struct -from unittest import TestCase - -import spam - - -def path_contains(parent, child): - ''' returns True if `child` is inside `parent`. - - Works around path-comparison bugs caused by short-paths on Windows e.g. - vssadm~1 instead of vssadministrator - ''' - parent = os.path.abspath(parent) - child = os.path.abspath(child) - - while child != os.path.dirname(child): - child = os.path.dirname(child) - if os.stat(parent) == os.stat(child): - # parent and child refer to the same directory on the filesystem - return True - return False - - -class TestSpam(TestCase): - def test_system(self): - self.assertEqual(0, spam.system('python -c "exit(0)"')) - self.assertNotEqual(0, spam.system('python -c "exit(1)"')) - - def test_virtualenv(self): - virtualenv_path = os.environ.get("__CIBW_VIRTUALENV_PATH__") - if not virtualenv_path: - self.fail("No virtualenv path defined in environment variable __CIBW_VIRTUALENV_PATH__") - - print("=[executable]", sys.executable) - print("=[spam location]", spam.__file__) - print("=[virtualenv path]", virtualenv_path) - print("=[listdir]", os.listdir(virtualenv_path)) - if os.path.exists(os.path.join(virtualenv_path, 'Scripts')): - print("=[listdir]2", os.listdir(os.path.join(virtualenv_path, 'Scripts'))) - if os.path.exists(os.path.join(virtualenv_path, 'bin')): - print("=[listdir]2", os.listdir(os.path.join(virtualenv_path, 'bin'))) - self.assertTrue(path_contains(virtualenv_path, sys.executable)) - self.assertTrue(path_contains(virtualenv_path, spam.__file__)) - - def test_uname(self): - if platform.system() == "Windows": - return - # if we're running in 32-bit Python, check that the machine is i686. - # See #336 for more info. - bits = struct.calcsize("P") * 8 - if bits == 32: - self.assertEqual(platform.machine(), "i686") diff --git a/test/03_before_build/cibuildwheel_test.py b/test/03_before_build/cibuildwheel_test.py deleted file mode 100644 index 8b0bfcb6..00000000 --- a/test/03_before_build/cibuildwheel_test.py +++ /dev/null @@ -1,19 +0,0 @@ -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_BUILD': '''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_BUILD_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)"''', - }) - - # 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/03_before_build/setup.py b/test/03_before_build/setup.py deleted file mode 100644 index fd78c005..00000000 --- a/test/03_before_build/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -import sys - -from setuptools import ( - Extension, - setup, -) - -# 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 - -# 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() - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/03_before_build/spam.c b/test/03_before_build/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/03_before_build/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/04_build_skip/cibuildwheel_test.py b/test/04_build_skip/cibuildwheel_test.py deleted file mode 100644 index 2c6a622c..00000000 --- a/test/04_build_skip/cibuildwheel_test.py +++ /dev/null @@ -1,18 +0,0 @@ -import os - -import utils - - -def test(): - project_dir = os.path.dirname(__file__) - - # build the wheels - actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ - 'CIBW_BUILD': 'cp3?-*', - 'CIBW_SKIP': 'cp37-*', - }) - - # check that we got the right wheels. There should be no 2.7 or 3.7. - expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') - if ('-cp3' in w) and ('-cp37' not in w)] - assert set(actual_wheels) == set(expected_wheels) diff --git a/test/04_build_skip/setup.py b/test/04_build_skip/setup.py deleted file mode 100644 index 2e3fd5c6..00000000 --- a/test/04_build_skip/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -import sys - -from setuptools import ( - Extension, - setup, -) - -# explode if run on Python 2.7 or Python 3.4 (these should be skipped) -if sys.version_info[0:2] == (2, 7): - raise Exception('Python 2.7 should not be built') -if sys.version_info[0:2] == (3, 4): - raise Exception('Python 3.4 should be skipped') - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/04_build_skip/spam.c b/test/04_build_skip/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/04_build_skip/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/05_environment/cibuildwheel_test.py b/test/05_environment/cibuildwheel_test.py deleted file mode 100644 index ab467111..00000000 --- a/test/05_environment/cibuildwheel_test.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -import pytest -import subprocess -import utils - - -def test(): - project_dir = os.path.dirname(__file__) - - # write some information into the CIBW_ENVIRONMENT, for expansion and - # insertion into the environment by cibuildwheel. This is checked - # in setup.py - actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ - 'CIBW_ENVIRONMENT': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path''', - 'CIBW_ENVIRONMENT_WINDOWS': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''', - }) - - # also check that we got the right wheels built - expected_wheels = utils.expected_wheels('spam', '0.1.0') - assert set(actual_wheels) == set(expected_wheels) - - -def test_overridden_path(tmp_path): - project_dir = os.path.dirname(__file__) - - # mess up PATH, somehow - with pytest.raises(subprocess.CalledProcessError): - utils.cibuildwheel_run(project_dir, output_dir=tmp_path, add_env={ - 'CIBW_ENVIRONMENT': '''SOMETHING="$(mkdir new_path && touch new_path/python)" PATH="$(realpath new_path):$PATH"''', - 'CIBW_ENVIRONMENT_WINDOWS': '''SOMETHING="$(mkdir new_path && type nul > new_path/python.exe)" PATH="$CD\\new_path;$PATH"''', - }) - assert len(os.listdir(str(tmp_path))) == 0 diff --git a/test/05_environment/setup.py b/test/05_environment/setup.py deleted file mode 100644 index da5e8d59..00000000 --- a/test/05_environment/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -import os - -from setuptools import ( - Extension, - setup, -) - -# explode if environment isn't correct, as set in CIBW_ENVIRONMENT -CIBW_TEST_VAR = os.environ.get('CIBW_TEST_VAR') -CIBW_TEST_VAR_2 = os.environ.get('CIBW_TEST_VAR_2') -PATH = os.environ.get('PATH') - -if CIBW_TEST_VAR != 'a b c': - raise Exception('CIBW_TEST_VAR should equal "a b c". It was "%s"' % CIBW_TEST_VAR) -if CIBW_TEST_VAR_2 != '1': - raise Exception('CIBW_TEST_VAR_2 should equal "1". It was "%s"' % CIBW_TEST_VAR_2) -if '/opt/cibw_test_path' not in PATH: - raise Exception('PATH should contain "/opt/cibw_test_path". It was "%s"' % PATH) -if '$PATH' in PATH: - raise Exception('$PATH should be expanded in PATH. It was "%s"' % PATH) - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/05_environment/spam.c b/test/05_environment/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/05_environment/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/06_docker_images/setup.py b/test/06_docker_images/setup.py deleted file mode 100644 index 5680349d..00000000 --- a/test/06_docker_images/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import sys - -from setuptools import ( - Extension, - setup, -) - -# check that we're running in the correct docker image as specified in the -# environment options CIBW_MANYLINUX1_*_IMAGE -if 'linux' in sys.platform and not os.path.exists('/dockcross'): - raise Exception('/dockcross directory not found. Is this test running in the correct docker image?') - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/06_docker_images/spam.c b/test/06_docker_images/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/06_docker_images/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/07_ssl/cibuildwheel_test.py b/test/07_ssl/cibuildwheel_test.py deleted file mode 100644 index 787ba866..00000000 --- a/test/07_ssl/cibuildwheel_test.py +++ /dev/null @@ -1,11 +0,0 @@ -import os - -import utils - - -def test(): - project_dir = os.path.dirname(__file__) - # this test checks that SSL is working in the build environment using - # some checks in setup.py. - - utils.cibuildwheel_run(project_dir) diff --git a/test/07_ssl/setup.py b/test/07_ssl/setup.py deleted file mode 100644 index 4b6a4f24..00000000 --- a/test/07_ssl/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -import ssl -import sys - -from setuptools import ( - Extension, - setup, -) - -if sys.version_info[0] == 2: - from urllib2 import urlopen -else: - from urllib.request import urlopen - -if sys.version_info[0:2] == (3, 3): - data = urlopen('https://www.nist.gov') -else: - context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) - data = urlopen('https://www.nist.gov', context=context) - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/07_ssl/spam.c b/test/07_ssl/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/07_ssl/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/08_manylinuxXXXX_only/setup.py b/test/08_manylinuxXXXX_only/setup.py deleted file mode 100644 index 209f45b0..00000000 --- a/test/08_manylinuxXXXX_only/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import ( - Extension, - setup, -) - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/08_manylinuxXXXX_only/spam.c b/test/08_manylinuxXXXX_only/spam.c deleted file mode 100644 index 0e7c5597..00000000 --- a/test/08_manylinuxXXXX_only/spam.c +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include - -#if !defined(__GLIBC_PREREQ) -#error "Must run on a glibc linux environment" -#endif - -#if !__GLIBC_PREREQ(2, 5) /* manylinux1 is glibc 2.5 */ -#error "Must run on a glibc >= 2.5 linux environment" -#endif - -static PyObject * -spam_system(PyObject *self, PyObject *args) -{ - const char *command; - int sts = 0; - - if (!PyArg_ParseTuple(args, "s", &command)) - return NULL; - -#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) /* manylinux2014 is glibc 2.17 */ - // secure_getenv is only available in manylinux2014, ensuring - // that only a manylinux2014 wheel is produced - sts = (int)secure_getenv("NON_EXISTING_ENV_VARIABLE"); -#elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10) /* manylinux2010 is glibc 2.12 */ - // malloc_info is only available on manylinux2010+ - sts = malloc_info(0, stdout); -#endif - if (sts == 0) { - 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/09_setup_cfg/cibuildwheel_test.py b/test/09_setup_cfg/cibuildwheel_test.py deleted file mode 100644 index b6c3de01..00000000 --- a/test/09_setup_cfg/cibuildwheel_test.py +++ /dev/null @@ -1,14 +0,0 @@ -import os - -import utils - -project_dir = os.path.dirname(__file__) - - -def test(): - # build the wheels - actual_wheels = utils.cibuildwheel_run(project_dir) - - # check that the expected wheels are produced - expected_wheels = utils.expected_wheels('spam', '0.1.0') - assert set(actual_wheels) == set(expected_wheels) diff --git a/test/09_setup_cfg/setup.cfg b/test/09_setup_cfg/setup.cfg deleted file mode 100644 index b42c221a..00000000 --- a/test/09_setup_cfg/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[metadata] -name = spam -version = attr: spam.__version__ - -[options] -packages = find: \ No newline at end of file diff --git a/test/09_setup_cfg/setup.py b/test/09_setup_cfg/setup.py deleted file mode 100644 index a4c19252..00000000 --- a/test/09_setup_cfg/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -from setuptools import ( - Extension, - setup, -) - -setup( - ext_modules=[Extension('spam.spam', sources=['spam/spam.c'])], -) diff --git a/test/09_setup_cfg/spam/__init__.py b/test/09_setup_cfg/spam/__init__.py deleted file mode 100644 index 3dc1f76b..00000000 --- a/test/09_setup_cfg/spam/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" diff --git a/test/09_setup_cfg/spam/spam.c b/test/09_setup_cfg/spam/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/09_setup_cfg/spam/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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_cpp_standards/setup.py b/test/10_cpp_standards/setup.py deleted file mode 100644 index f848d0b1..00000000 --- a/test/10_cpp_standards/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import platform - -from setuptools import ( - Extension, - setup, -) - -standard = os.environ["STANDARD"] - -language_standard = "/std:c++" + standard if platform.system() == "Windows" else "-std=c++" + standard - -extra_compile_args = [language_standard, "-DSTANDARD=" + standard] - -if standard == "17": - if platform.system() == "Windows": - extra_compile_args.append("/wd5033") - else: - extra_compile_args.append("-Wno-register") - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.cpp'], language="c++", extra_compile_args=extra_compile_args)], - version="0.1.0", -) diff --git a/test/10_cpp_standards/spam.cpp b/test/10_cpp_standards/spam.cpp deleted file mode 100644 index f9b56d07..00000000 --- a/test/10_cpp_standards/spam.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include - -// Depending on the requested standard, use a modern C++ feature -// that was introduced in that standard. -#if STANDARD == 11 - #include -#elif STANDARD == 14 - int a = 100'000; -#elif STANDARD == 17 - #include - auto a = std::pair(5.0, false); -#else - #error Standard needed -#endif - -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/11_before_test/cibuildwheel_test.py b/test/11_before_test/cibuildwheel_test.py deleted file mode 100644 index 5aa1c4ca..00000000 --- a/test/11_before_test/cibuildwheel_test.py +++ /dev/null @@ -1,23 +0,0 @@ -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/pythonprefix.txt', 'w').write(sys.prefix)"''', - 'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix.txt', 'w').write(sys.prefix)"''', - '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/11_before_test/setup.py b/test/11_before_test/setup.py deleted file mode 100644 index e3ea2938..00000000 --- a/test/11_before_test/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -from setuptools import setup, Extension - - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/11_before_test/spam.c b/test/11_before_test/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/11_before_test/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/11_before_test/test/spam_test.py b/test/11_before_test/test/spam_test.py deleted file mode 100644 index 21bad8c2..00000000 --- a/test/11_before_test/test/spam_test.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys -import 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_TEST step - # is the same one as is currently running. - # because of use symlinks in MacOS run this test is also need - 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_prefix(self): - # check that the prefix also was written - prefix_file = 'c:\\pythonprefix.txt' if sys.platform == 'win32' else '/tmp/pythonprefix.txt' - with open(prefix_file) as f: - stored_prefix = f.read() - print('stored_prefix', stored_prefix) - print('sys.prefix', sys.prefix) - # Works around path-comparison bugs caused by short-paths on Windows e.g. - # vssadm~1 instead of vssadministrator - - assert os.stat(stored_prefix) == os.stat(sys.prefix) diff --git a/test/12_dependency_versions/setup.py b/test/12_dependency_versions/setup.py deleted file mode 100644 index 61e4719e..00000000 --- a/test/12_dependency_versions/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -import subprocess -from setuptools import ( - Extension, - setup, -) - -versions_output_text = subprocess.check_output( - ['pip', 'freeze', '--all', '-qq'], - universal_newlines=True, -) -versions = versions_output_text.strip().splitlines() - -# `versions` now looks like: -# ['pip==x.x.x', 'setuptools==x.x.x', 'wheel==x.x.x'] - -print('Gathered versions', versions) - -for package_name in ['pip', 'setuptools', 'wheel']: - env_name = 'EXPECTED_{}_VERSION'.format(package_name.upper()) - expected_version = os.environ[env_name] - - assert '{}=={}'.format(package_name, expected_version) in versions, ( - 'error: {} version should equal {}'.format(package_name, expected_version) - ) - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/12_dependency_versions/spam.c b/test/12_dependency_versions/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/12_dependency_versions/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/13_subdir_package/bin/before_build.py b/test/13_subdir_package/bin/before_build.py deleted file mode 100755 index 3bc8ab16..00000000 --- a/test/13_subdir_package/bin/before_build.py +++ /dev/null @@ -1 +0,0 @@ -print('before_build.py executed!') diff --git a/test/13_subdir_package/cibuildwheel_test.py b/test/13_subdir_package/cibuildwheel_test.py deleted file mode 100644 index 404811db..00000000 --- a/test/13_subdir_package/cibuildwheel_test.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import utils - -project_dir = os.path.dirname(__file__) - - -def test(capfd): - package_dir = os.path.join(project_dir, 'src', 'spam') - # build the wheels - actual_wheels = utils.cibuildwheel_run(project_dir, package_dir=package_dir, add_env={ - 'CIBW_BEFORE_BUILD': 'python {project}/bin/before_build.py', - 'CIBW_TEST_COMMAND': 'python {package}/test/run_tests.py', - # this shouldn't depend on the version of python, so build only - # CPython 3.6 - 'CIBW_BUILD': 'cp36-*', - }) - - # check that the expected wheels are produced - expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') - if 'cp36' in w] - assert set(actual_wheels) == set(expected_wheels) - - captured = capfd.readouterr() - assert "before_build.py executed!" in captured.out - assert "run_tests.py executed!" in captured.out diff --git a/test/13_subdir_package/src/spam/setup.py b/test/13_subdir_package/src/spam/setup.py deleted file mode 100644 index 637d7ff0..00000000 --- a/test/13_subdir_package/src/spam/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -from setuptools import Extension, setup - - -setup( - name="spam", - ext_modules=[Extension('spam', sources=['spam.c'])], - version="0.1.0", -) diff --git a/test/13_subdir_package/src/spam/spam.c b/test/13_subdir_package/src/spam/spam.c deleted file mode 100644 index d1ab0f22..00000000 --- a/test/13_subdir_package/src/spam/spam.c +++ /dev/null @@ -1,48 +0,0 @@ -#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/13_subdir_package/src/spam/test/run_tests.py b/test/13_subdir_package/src/spam/test/run_tests.py deleted file mode 100644 index 8104e1b3..00000000 --- a/test/13_subdir_package/src/spam/test/run_tests.py +++ /dev/null @@ -1 +0,0 @@ -print('run_tests.py executed!') diff --git a/test/README.md b/test/README.md index d95aacea..5f34f5e9 100644 --- a/test/README.md +++ b/test/README.md @@ -1,3 +1 @@ -This folder contains repos that are built by cibuildwheel to test cibuildwheel. Confusing, I know. - -Basically, if cibuildwheel can build these projects, we're good. +This folder contains integration tests for cibuildwheel. diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index d89278e8..00000000 --- a/test/conftest.py +++ /dev/null @@ -1,4 +0,0 @@ -import os -import sys - -sys.path.append(os.path.join(os.path.dirname(__file__), 'shared')) diff --git a/test/01_basic/cibuildwheel_test.py b/test/test_0_basic.py similarity index 64% rename from test/01_basic/cibuildwheel_test.py rename to test/test_0_basic.py index 51875a8a..60b309ab 100644 --- a/test/01_basic/cibuildwheel_test.py +++ b/test/test_0_basic.py @@ -1,12 +1,22 @@ -import os +import textwrap import platform +from . import test_projects +from . import utils -import utils +basic_project = test_projects.new_c_project( + setup_py_add=textwrap.dedent(''' + import os -project_dir = os.path.dirname(__file__) + if os.environ.get("CIBUILDWHEEL", "0") != "1": + raise Exception("CIBUILDWHEEL environment variable is not set to 1") + ''') +) -def test(): +def test(tmp_path): + project_dir = tmp_path / 'project' + basic_project.generate(project_dir) + # build the wheels actual_wheels = utils.cibuildwheel_run(project_dir) @@ -15,7 +25,10 @@ def test(): assert set(actual_wheels) == set(expected_wheels) -def test_build_identifiers(): +def test_build_identifiers(tmp_path): + project_dir = tmp_path / 'project' + basic_project.generate(project_dir) + # check that the number of expected wheels matches the number of build # identifiers # after adding CIBW_MANYLINUX_IMAGE to support manylinux2010, there diff --git a/test/test_before_build.py b/test/test_before_build.py new file mode 100644 index 00000000..11c752f0 --- /dev/null +++ b/test/test_before_build.py @@ -0,0 +1,45 @@ +import textwrap + +from . import utils +from . import test_projects + +project_with_before_build_asserts = test_projects.new_c_project( + setup_py_add=textwrap.dedent(r''' + 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' + 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 + + # 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() + ''') +) + + +def test(tmp_path): + project_dir = tmp_path / 'project' + project_with_before_build_asserts.generate(project_dir) + + # 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_BUILD': '''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_BUILD_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)"''', + }) + + # 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/test_before_test.py b/test/test_before_test.py new file mode 100644 index 00000000..d0dad59d --- /dev/null +++ b/test/test_before_test.py @@ -0,0 +1,56 @@ +from . import test_projects +from . import utils + +before_test_project = test_projects.new_c_project() +before_test_project.files['test/spam_test.py'] = r''' +import sys +import 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_TEST step + # is the same one as is currently running. + # because of use symlinks in MacOS run this test is also need + 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_prefix(self): + # check that the prefix also was written + prefix_file = 'c:\\pythonprefix.txt' if sys.platform == 'win32' else '/tmp/pythonprefix.txt' + with open(prefix_file) as f: + stored_prefix = f.read() + print('stored_prefix', stored_prefix) + print('sys.prefix', sys.prefix) + # Works around path-comparison bugs caused by short-paths on Windows e.g. + # vssadm~1 instead of vssadministrator + + assert os.stat(stored_prefix) == os.stat(sys.prefix) +''' + + +def test(tmp_path): + project_dir = tmp_path / 'project' + before_test_project.generate(project_dir) + + # 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/pythonprefix.txt', 'w').write(sys.prefix)"''', + 'CIBW_BEFORE_TEST_WINDOWS': '''python -c "import sys; open('c:\\pythonversion.txt', 'w').write(sys.version)" && python -c "import sys; open('c:\\pythonprefix.txt', 'w').write(sys.prefix)"''', + '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/test_build_skip.py b/test/test_build_skip.py new file mode 100644 index 00000000..11d125f6 --- /dev/null +++ b/test/test_build_skip.py @@ -0,0 +1,30 @@ +import textwrap + +from . import utils +from . import test_projects + +project_with_skip_asserts = test_projects.new_c_project( + setup_py_add=textwrap.dedent(r''' + # explode if run on Python 2.7 or Python 3.7 (these should be skipped) + if sys.version_info[0:2] == (2, 7): + raise Exception("Python 2.7 should not be built") + if sys.version_info[0:2] == (3, 7): + raise Exception("Python 3.7 should be skipped") + ''') +) + + +def test(tmp_path): + project_dir = tmp_path / 'project' + project_with_skip_asserts.generate(project_dir) + + # build the wheels + actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ + 'CIBW_BUILD': 'cp3?-*', + 'CIBW_SKIP': 'cp37-*', + }) + + # check that we got the right wheels. There should be no 2.7 or 3.7. + expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') + if ('-cp3' in w) and ('-cp37' not in w)] + assert set(actual_wheels) == set(expected_wheels) diff --git a/test/10_cpp_standards/cibuildwheel_test.py b/test/test_cpp_standards.py similarity index 52% rename from test/10_cpp_standards/cibuildwheel_test.py rename to test/test_cpp_standards.py index 3a1e57d5..0ecf196d 100644 --- a/test/10_cpp_standards/cibuildwheel_test.py +++ b/test/test_cpp_standards.py @@ -1,17 +1,92 @@ import os +import jinja2 import pytest -import utils +from . import utils +from .test_projects import TestProject -project_dir = os.path.dirname(__file__) +cpp_test_project = TestProject() + +cpp_test_project.files['setup.py'] = jinja2.Template(r''' +from setuptools import Extension, setup + +setup( + name="spam", + ext_modules=[Extension('spam', sources=['spam.cpp'], language="c++", extra_compile_args={{ extra_compile_args }})], + version="0.1.0", +) +''') + +cpp_test_project.files['spam.cpp'] = jinja2.Template(r''' +#include + +{{ spam_cpp_top_level_add }} + +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) +} +''') -def test_cpp11(): +cpp11_project = cpp_test_project.copy() +cpp11_project.template_context['extra_compile_args'] = ( + ['/std:c++11'] if utils.platform == 'windows' else ['-std=c++11'] +) +cpp11_project.template_context['spam_cpp_top_level_add'] = '#include ' + + +def test_cpp11(tmp_path): # This test checks that the C++11 standard is supported + project_dir = tmp_path / 'project' + + cpp11_project.generate(project_dir) # VC++ for Python 2.7 does not support modern standards - add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32', 'CIBW_ENVIRONMENT': 'STANDARD=11'} + add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32'} actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env) expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') @@ -20,12 +95,22 @@ def test_cpp11(): assert set(actual_wheels) == set(expected_wheels) -def test_cpp14(): +cpp14_project = cpp_test_project.copy() +cpp14_project.template_context['extra_compile_args'] = ( + ['/std:c++14'] if utils.platform == 'windows' else ['-std=c++14'] +) +cpp14_project.template_context['spam_cpp_top_level_add'] = "int a = 100'000;" + + +def test_cpp14(tmp_path): # This test checks that the C++14 standard is supported + project_dir = tmp_path / 'project' + + cpp14_project.generate(project_dir) # VC++ for Python 2.7 does not support modern standards # The manylinux1 docker image does not have a compiler which supports C++11 - add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32', 'CIBW_ENVIRONMENT': 'STANDARD=14'} + add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32'} actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env) expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') @@ -35,15 +120,31 @@ def test_cpp14(): assert set(actual_wheels) == set(expected_wheels) -def test_cpp17(): +cpp17_project = cpp_test_project.copy() + +# Python and PyPy 2.7 headers use the `register` keyword, which is forbidden in +# the C++17 standard, so we need the -Wno-register or /wd5033 options +cpp17_project.template_context['extra_compile_args'] = ( + ['/std:c++17', '/wd5033'] if utils.platform == 'windows' else ['-std=c++17', '-Wno-register'] +) +cpp17_project.template_context['spam_cpp_top_level_add'] = r''' +#include +auto a = std::pair(5.0, false); +''' + + +def test_cpp17(tmp_path): # This test checks that the C++17 standard is supported + project_dir = tmp_path / 'project' + + cpp17_project.generate(project_dir) - # Python and PyPy 2.7 use the `register` keyword which is forbidden in the C++17 standard - # The manylinux1 docker image does not have a compiler which supports C++11 if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') == 'Visual Studio 2015': pytest.skip('Visual Studio 2015 does not support C++17') - add_env = {'CIBW_SKIP': 'cp27-win* pp27-win32', 'CIBW_ENVIRONMENT': 'STANDARD=17'} + # Pypy's distutils sets the default compiler to 'msvc9compiler', which + # is too old to support cpp17. + add_env = {'CIBW_SKIP': 'cp27-win* pp??-*'} if utils.platform == 'macos': add_env['MACOSX_DEPLOYMENT_TARGET'] = '10.13' @@ -51,12 +152,12 @@ def test_cpp17(): actual_wheels = utils.cibuildwheel_run(project_dir, add_env=add_env) expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0', macosx_deployment_target='10.13') if 'cp27-cp27m-win' not in w - and 'pp27-pypy_73-win32' not in w] + and '-pp' not in w] assert set(actual_wheels) == set(expected_wheels) -def test_cpp17_py27_modern_msvc_workaround(): +def test_cpp17_py27_modern_msvc_workaround(tmp_path): # This test checks the workaround for building Python 2.7 wheel with MSVC 14 if utils.platform != 'windows': @@ -65,14 +166,16 @@ def test_cpp17_py27_modern_msvc_workaround(): if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') == 'Visual Studio 2015': pytest.skip('Visual Studio 2015 does not support C++17') + project_dir = tmp_path / 'project' + cpp17_project.generate(project_dir) + # VC++ for Python 2.7 (i.e., MSVC 9) does not support modern standards # This is a workaround which forces distutils/setupstools to a newer version # Wheels compiled need a more modern C++ redistributable installed, which is not # included with Python: see documentation for more info # DISTUTILS_USE_SDK and MSSdk=1 tell distutils/setuptools that we are adding # MSVC's compiler, tools, and libraries to PATH ourselves - add_env = {'CIBW_ENVIRONMENT': 'STANDARD=17', - 'DISTUTILS_USE_SDK': '1', 'MSSdk': '1'} + add_env = {'DISTUTILS_USE_SDK': '1', 'MSSdk': '1'} # Use existing setuptools code to run Visual Studio's vcvarsall.bat and get the # necessary environment variables, since running vcvarsall.bat in a subprocess diff --git a/test/12_dependency_versions/cibuildwheel_test.py b/test/test_dependency_versions.py similarity index 71% rename from test/12_dependency_versions/cibuildwheel_test.py rename to test/test_dependency_versions.py index 376a18ff..611350f1 100644 --- a/test/12_dependency_versions/cibuildwheel_test.py +++ b/test/test_dependency_versions.py @@ -4,7 +4,36 @@ import pytest import textwrap import cibuildwheel.util -import utils +from . import utils +from . import test_projects + + +project_with_expected_version_checks = test_projects.new_c_project( + setup_py_add=textwrap.dedent(r''' + import subprocess + import os + + versions_output_text = subprocess.check_output( + ['pip', 'freeze', '--all', '-qq'], + universal_newlines=True, + ) + versions = versions_output_text.strip().splitlines() + + # `versions` now looks like: + # ['pip==x.x.x', 'setuptools==x.x.x', 'wheel==x.x.x'] + + print('Gathered versions', versions) + + for package_name in ['pip', 'setuptools', 'wheel']: + env_name = 'EXPECTED_{}_VERSION'.format(package_name.upper()) + expected_version = os.environ[env_name] + + assert '{}=={}'.format(package_name, expected_version) in versions, ( + 'error: {} version should equal {}'.format(package_name, expected_version) + ) + ''') +) + VERSION_REGEX = r'([\w-]+)==([^\s]+)' @@ -21,12 +50,13 @@ def get_versions_from_constraint_file(constraint_file): return versions -@pytest.mark.parametrize('python_version', ['2.7', '3.5', '3.8']) -def test_pinned_versions(python_version): +@pytest.mark.parametrize('python_version', ['2.7', '3.5', '3.6', '3.8']) +def test_pinned_versions(tmp_path, python_version): if utils.platform == 'linux': pytest.skip('linux doesn\'t pin individual tool versions, it pins manylinux images instead') - project_dir = os.path.dirname(__file__) + project_dir = tmp_path / 'project' + project_with_expected_version_checks.generate(project_dir) build_environment = {} @@ -36,6 +66,9 @@ def test_pinned_versions(python_version): elif python_version == '3.5': constraint_filename = 'constraints-python35.txt' build_pattern = '[cp]p35-*' + elif python_version == '3.6': + constraint_filename = 'constraints-python36.txt' + build_pattern = '[cp]p36-*' else: constraint_filename = 'constraints.txt' build_pattern = '[cp]p38-*' @@ -64,6 +97,9 @@ def test_pinned_versions(python_version): elif python_version == '3.5': expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') if '-cp35' in w or '-pp35' in w] + elif python_version == '3.6': + expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') + if '-cp36' in w or '-pp36' in w] elif python_version == '3.8': expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') if '-cp38' in w or '-pp38' in w] @@ -78,7 +114,8 @@ def test_dependency_constraints_file(tmp_path, python_version): if utils.platform == 'linux': pytest.skip('linux doesn\'t pin individual tool versions, it pins manylinux images instead') - project_dir = os.path.dirname(__file__) + project_dir = tmp_path / 'project' + project_with_expected_version_checks.generate(project_dir) tool_versions = { 'pip': '20.0.2', diff --git a/test/06_docker_images/cibuildwheel_test.py b/test/test_docker_images.py similarity index 54% rename from test/06_docker_images/cibuildwheel_test.py rename to test/test_docker_images.py index 7be4a4c3..f101c4fd 100644 --- a/test/06_docker_images/cibuildwheel_test.py +++ b/test/test_docker_images.py @@ -1,19 +1,34 @@ -import os import platform +import textwrap import pytest -import utils +from . import utils +from . import test_projects + +dockcross_only_project = test_projects.new_c_project( + setup_py_add=textwrap.dedent(r''' + import os, sys + + # check that we're running in the correct docker image as specified in the + # environment options CIBW_MANYLINUX1_*_IMAGE + if "linux" in sys.platform and not os.path.exists("/dockcross"): + raise Exception( + "/dockcross directory not found. Is this test running in the correct docker image?" + ) + ''') +) -def test(): - project_dir = os.path.dirname(__file__) - +def test(tmp_path): if utils.platform != 'linux': pytest.skip('the test is only relevant to the linux build') if platform.machine() not in ['x86_64', 'i686']: pytest.skip('this test is currently only possible on x86_64/i686 due to availability of alternative images') + project_dir = tmp_path / 'project' + dockcross_only_project.generate(project_dir) + actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ 'CIBW_MANYLINUX_X86_64_IMAGE': 'dockcross/manylinux2010-x64', 'CIBW_MANYLINUX_I686_IMAGE': 'dockcross/manylinux2010-x86', diff --git a/test/test_environment.py b/test/test_environment.py new file mode 100644 index 00000000..470a151f --- /dev/null +++ b/test/test_environment.py @@ -0,0 +1,61 @@ +import os +import pytest +import subprocess +import textwrap +from . import utils +from . import test_projects + + +project_with_environment_asserts = test_projects.new_c_project( + setup_py_add=textwrap.dedent(r''' + import os + + # explode if environment isn't correct, as set in CIBW_ENVIRONMENT + CIBW_TEST_VAR = os.environ.get("CIBW_TEST_VAR") + CIBW_TEST_VAR_2 = os.environ.get("CIBW_TEST_VAR_2") + PATH = os.environ.get("PATH") + + if CIBW_TEST_VAR != "a b c": + raise Exception('CIBW_TEST_VAR should equal "a b c". It was "%s"' % CIBW_TEST_VAR) + if CIBW_TEST_VAR_2 != "1": + raise Exception('CIBW_TEST_VAR_2 should equal "1". It was "%s"' % CIBW_TEST_VAR_2) + if "/opt/cibw_test_path" not in PATH: + raise Exception('PATH should contain "/opt/cibw_test_path". It was "%s"' % PATH) + if "$PATH" in PATH: + raise Exception('$PATH should be expanded in PATH. It was "%s"' % PATH) + ''') +) + + +def test(tmp_path): + project_dir = tmp_path / 'project' + project_with_environment_asserts.generate(project_dir) + + # write some information into the CIBW_ENVIRONMENT, for expansion and + # insertion into the environment by cibuildwheel. This is checked + # in setup.py + actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ + 'CIBW_ENVIRONMENT': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH=$PATH:/opt/cibw_test_path''', + 'CIBW_ENVIRONMENT_WINDOWS': '''CIBW_TEST_VAR="a b c" CIBW_TEST_VAR_2=1 CIBW_TEST_VAR_3="$(echo 'test string 3')" PATH="$PATH;/opt/cibw_test_path"''', + }) + + # also check that we got the right wheels built + expected_wheels = utils.expected_wheels('spam', '0.1.0') + assert set(actual_wheels) == set(expected_wheels) + + +def test_overridden_path(tmp_path): + project_dir = tmp_path / 'project' + output_dir = tmp_path / 'output' + + project = test_projects.new_c_project() + project.generate(project_dir) + output_dir.mkdir() + + # mess up PATH, somehow + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={ + 'CIBW_ENVIRONMENT': '''SOMETHING="$(mkdir new_path && touch new_path/python)" PATH="$(realpath new_path):$PATH"''', + 'CIBW_ENVIRONMENT_WINDOWS': '''SOMETHING="$(mkdir new_path && type nul > new_path/python.exe)" PATH="$CD\\new_path;$PATH"''', + }) + assert len(os.listdir(output_dir)) == 0 diff --git a/test/08_manylinuxXXXX_only/cibuildwheel_test.py b/test/test_manylinuxXXXX_only.py similarity index 61% rename from test/08_manylinuxXXXX_only/cibuildwheel_test.py rename to test/test_manylinuxXXXX_only.py index 2c20441d..68160688 100644 --- a/test/08_manylinuxXXXX_only/cibuildwheel_test.py +++ b/test/test_manylinuxXXXX_only.py @@ -1,21 +1,48 @@ -import os import platform +import textwrap import pytest -import utils +from . import utils +from . import test_projects + +# TODO: specify these at runtime according to manylinux_image +project_with_manylinux_symbols = test_projects.new_c_project( + spam_c_top_level_add=textwrap.dedent(r''' + #include + + #if !defined(__GLIBC_PREREQ) + #error "Must run on a glibc linux environment" + #endif + + #if !__GLIBC_PREREQ(2, 5) /* manylinux1 is glibc 2.5 */ + #error "Must run on a glibc >= 2.5 linux environment" + #endif + '''), + spam_c_function_add=textwrap.dedent(r''' + #if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 17) /* manylinux2014 is glibc 2.17 */ + // secure_getenv is only available in manylinux2014, ensuring + // that only a manylinux2014 wheel is produced + secure_getenv("NON_EXISTING_ENV_VARIABLE"); + #elif defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10) /* manylinux2010 is glibc 2.12 */ + // malloc_info is only available on manylinux2010+ + malloc_info(0, stdout); + #endif + '''), +) @pytest.mark.parametrize('manylinux_image', ['manylinux1', 'manylinux2010', 'manylinux2014']) -def test(manylinux_image): - project_dir = os.path.dirname(__file__) - +def test(manylinux_image, tmp_path): if utils.platform != 'linux': pytest.skip('the docker test is only relevant to the linux build') elif platform.machine() not in ['x86_64', 'i686']: if manylinux_image in ['manylinux1', 'manylinux2010']: pytest.skip("manylinux1 and 2010 doesn't exist for non-x86 architectures") + project_dir = tmp_path / 'project' + project_with_manylinux_symbols.generate(project_dir) + # build the wheels # CFLAGS environment variable is necessary to fail on 'malloc_info' (on manylinux1) during compilation/linking, # rather than when dynamically loading the Python diff --git a/test/test_projects/__init__.py b/test/test_projects/__init__.py new file mode 100644 index 00000000..48d7dbec --- /dev/null +++ b/test/test_projects/__init__.py @@ -0,0 +1,2 @@ +from .base import TestProject # noqa +from .c import new_c_project # noqa diff --git a/test/test_projects/__main__.py b/test/test_projects/__main__.py new file mode 100644 index 00000000..af5b14dc --- /dev/null +++ b/test/test_projects/__main__.py @@ -0,0 +1,37 @@ +from argparse import ArgumentParser +import importlib +import tempfile +import sys +import subprocess + + +def main(): + parser = ArgumentParser( + prog="python -m test.test_projects", + description='Generate a test project to check it out' + ) + parser.add_argument('PROJECT', help=''' + Python path to a project object. E.g. test.test_0_basic.basic_project + ''') + options = parser.parse_args() + + module, _, name = options.PROJECT.rpartition('.') + + project = getattr(importlib.import_module(module), name) + + project_dir = tempfile.mkdtemp() + project.generate(project_dir) + + print('Project generated at', project_dir) + print() + + if sys.platform == 'darwin': + subprocess.check_call(['open', '--', project_dir]) + elif sys.platform == 'linux2': + subprocess.check_call(['xdg-open', '--', project_dir]) + elif sys.platform == 'win32': + subprocess.check_call(['explorer', project_dir]) + + +if __name__ == '__main__': + main() diff --git a/test/test_projects/base.py b/test/test_projects/base.py new file mode 100644 index 00000000..5cac80ad --- /dev/null +++ b/test/test_projects/base.py @@ -0,0 +1,39 @@ +import os +import jinja2 +from typing import Union, Dict, Any + + +FilesDict = Dict[str, Union[str, jinja2.Template]] +TemplateContext = Dict[str, Any] + + +class TestProject: + ''' + An object that represents a project that can be built by cibuildwheel. + Can be manipulated in tests by changing `files` and `template_context`. + + Write out to the filesystem using `generate`. + ''' + files: FilesDict + template_context: TemplateContext + + def __init__(self): + self.files = {} + self.template_context = {} + + def generate(self, path: str): + for filename, content in self.files.items(): + file_path = os.path.join(path, filename) + os.makedirs(os.path.dirname(file_path), exist_ok=True) + + with open(file_path, 'w', encoding='utf8') as f: + if isinstance(content, jinja2.Template): + content = content.render(self.template_context) + + f.write(content) + + def copy(self): + other = TestProject() + other.files = self.files.copy() + other.template_context = self.template_context.copy() + return other diff --git a/test/test_projects/c.py b/test/test_projects/c.py new file mode 100644 index 00000000..6f06c1ba --- /dev/null +++ b/test/test_projects/c.py @@ -0,0 +1,100 @@ +import jinja2 +from .base import TestProject + + +SPAM_C_TEMPLATE = r''' +#include + +{{ spam_c_top_level_add }} + +static PyObject * +spam_system(PyObject *self, PyObject *args) +{ + const char *command; + int sts; + + if (!PyArg_ParseTuple(args, "s", &command)) + return NULL; + + sts = system(command); + + {{ spam_c_function_add | indent(4) }} + + 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) +} +''' + +SETUP_PY_TEMPLATE = r''' +from setuptools import setup, Extension + +{{ setup_py_add }} + +setup( + ext_modules=[Extension('spam', sources=['spam.c'])], + {{ setup_py_setup_args_add | indent(4) }} +) +''' + +SETUP_CFG_TEMPLATE = r''' +[metadata] +name = spam +version = 0.1.0 + +{{ setup_cfg_add }} +''' + + +def new_c_project(*, spam_c_top_level_add='', spam_c_function_add='', setup_py_add='', + setup_py_setup_args_add='', setup_cfg_add=''): + project = TestProject() + + project.files.update({ + 'spam.c': jinja2.Template(SPAM_C_TEMPLATE), + 'setup.py': jinja2.Template(SETUP_PY_TEMPLATE), + 'setup.cfg': jinja2.Template(SETUP_CFG_TEMPLATE), + }) + + project.template_context.update({ + 'spam_c_top_level_add': spam_c_top_level_add, + 'spam_c_function_add': spam_c_function_add, + 'setup_py_add': setup_py_add, + 'setup_py_setup_args_add': setup_py_setup_args_add, + 'setup_cfg_add': setup_cfg_add, + }) + + return project diff --git a/test/test_ssl.py b/test/test_ssl.py new file mode 100644 index 00000000..4ade9bb8 --- /dev/null +++ b/test/test_ssl.py @@ -0,0 +1,34 @@ +import textwrap + +from . import utils +from . import test_projects + +project_with_ssl_tests = test_projects.new_c_project( + setup_py_add=textwrap.dedent(r''' + import ssl + import sys + + if sys.version_info[0] == 2: + from urllib2 import urlopen + else: + from urllib.request import urlopen + + if sys.version_info[0:2] == (3, 3): + data = urlopen("https://www.nist.gov") + else: + context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + data = urlopen("https://www.nist.gov", context=context) + ''') +) + + +def test(tmp_path): + # this test checks that SSL is working in the build environment using + # some checks in setup.py. + project_dir = tmp_path / 'project' + project_with_ssl_tests.generate(project_dir) + + actual_wheels = utils.cibuildwheel_run(project_dir) + + expected_wheels = utils.expected_wheels('spam', '0.1.0') + assert set(actual_wheels) == set(expected_wheels) diff --git a/test/test_subdir_package.py b/test/test_subdir_package.py new file mode 100644 index 00000000..53bd9520 --- /dev/null +++ b/test/test_subdir_package.py @@ -0,0 +1,54 @@ +import os + +import jinja2 + +from . import utils +from .test_projects import TestProject +from .test_projects.c import SPAM_C_TEMPLATE + +subdir_package_project = TestProject() + +subdir_package_project.files['src/spam/spam.c'] = jinja2.Template(SPAM_C_TEMPLATE) +subdir_package_project.template_context['spam_c_top_level_add'] = '' +subdir_package_project.template_context['spam_c_function_add'] = '' + +subdir_package_project.files['src/spam/setup.py'] = r''' +from setuptools import Extension, setup + +setup( + name="spam", + ext_modules=[Extension('spam', sources=['spam.c'])], + version="0.1.0", +) +''' + +subdir_package_project.files['src/spam/test/run_tests.py'] = r''' +print('run_tests.py executed!') +''' + +subdir_package_project.files['bin/before_build.py'] = r''' +print('before_build.py executed!') +''' + + +def test(capfd, tmp_path): + project_dir = tmp_path / 'project' + subdir_package_project.generate(project_dir) + + package_dir = os.path.join(project_dir, 'src', 'spam') + # build the wheels + actual_wheels = utils.cibuildwheel_run(project_dir, package_dir=package_dir, add_env={ + 'CIBW_BEFORE_BUILD': 'python {project}/bin/before_build.py', + 'CIBW_TEST_COMMAND': 'python {package}/test/run_tests.py', + # this shouldn't depend on the version of python, so build only CPython 3.6 + 'CIBW_BUILD': 'cp36-*', + }) + + # check that the expected wheels are produced + expected_wheels = [w for w in utils.expected_wheels('spam', '0.1.0') + if 'cp36' in w] + assert set(actual_wheels) == set(expected_wheels) + + captured = capfd.readouterr() + assert "before_build.py executed!" in captured.out + assert "run_tests.py executed!" in captured.out diff --git a/test/test_testing.py b/test/test_testing.py new file mode 100644 index 00000000..dda833ea --- /dev/null +++ b/test/test_testing.py @@ -0,0 +1,118 @@ +import os +import subprocess +import textwrap + +import pytest + +from . import utils +from . import test_projects + +project_with_a_test = test_projects.new_c_project( + setup_cfg_add=textwrap.dedent(r''' + [options.extras_require] + test = nose + ''') +) + +project_with_a_test.files['test/spam_test.py'] = r''' +import os +import platform +import sys +import struct +from unittest import TestCase + +import spam + + +def path_contains(parent, child): + """ returns True if `child` is inside `parent`. + Works around path-comparison bugs caused by short-paths on Windows e.g. + vssadm~1 instead of vssadministrator + """ + parent = os.path.abspath(parent) + child = os.path.abspath(child) + + while child != os.path.dirname(child): + child = os.path.dirname(child) + if os.stat(parent) == os.stat(child): + # parent and child refer to the same directory on the filesystem + return True + return False + + +class TestSpam(TestCase): + def test_system(self): + self.assertEqual(0, spam.system('python -c "exit(0)"')) + self.assertNotEqual(0, spam.system('python -c "exit(1)"')) + + def test_virtualenv(self): + virtualenv_path = os.environ.get("__CIBW_VIRTUALENV_PATH__") + if not virtualenv_path: + self.fail("No virtualenv path defined in environment variable __CIBW_VIRTUALENV_PATH__") + + self.assertTrue(path_contains(virtualenv_path, sys.executable)) + self.assertTrue(path_contains(virtualenv_path, spam.__file__)) + + def test_uname(self): + if platform.system() == "Windows": + return + # if we're running in 32-bit Python, check that the machine is i686. + # See #336 for more info. + bits = struct.calcsize("P") * 8 + if bits == 32: + self.assertEqual(platform.machine(), "i686") +''' + + +def test(tmp_path): + project_dir = tmp_path / 'project' + project_with_a_test.generate(project_dir) + + # build and test the wheels + actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ + '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': 'COLOR 00 || 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) + + +def test_extras_require(tmp_path): + project_dir = tmp_path / 'project' + project_with_a_test.generate(project_dir) + + # build and test the wheels + actual_wheels = utils.cibuildwheel_run(project_dir, add_env={ + 'CIBW_TEST_EXTRAS': 'test', + # 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': 'COLOR 00 || 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) + + +def test_failing_test(tmp_path): + """Ensure a failing test causes cibuildwheel to error out and exit""" + project_dir = tmp_path / 'project' + output_dir = tmp_path / 'output' + project_with_a_test.generate(project_dir) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={ + 'CIBW_TEST_COMMAND': 'false', + # manylinux1 has a version of bash that's been shown to have + # problems with this, so let's check that. + 'CIBW_MANYLINUX_I686_IMAGE': 'manylinux1', + 'CIBW_MANYLINUX_X86_64_IMAGE': 'manylinux1', + }) + + assert len(os.listdir(output_dir)) == 0 diff --git a/test/shared/utils.py b/test/utils.py similarity index 99% rename from test/shared/utils.py rename to test/utils.py index 190e2fe2..15ce402a 100644 --- a/test/shared/utils.py +++ b/test/utils.py @@ -33,7 +33,7 @@ def cibuildwheel_get_build_identifiers(project_path, env=None): for the current platform. ''' cmd_output = subprocess.check_output( - [sys.executable, '-m', 'cibuildwheel', '--print-build-identifiers', project_path], + [sys.executable, '-m', 'cibuildwheel', '--print-build-identifiers', str(project_path)], universal_newlines=True, env=env, )