diff --git a/.appveyor.yml b/.appveyor.yml index 3b94eefe..a07db6b1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -27,8 +27,8 @@ environment: # See: https://www.appveyor.com/docs/installed-software/#python - - NOX_SESSION: "win_wat" - NOX_SESSION: "unit-3.6-32" + - NOX_SESSION: "win_wat" - NOX_SESSION: "unit-3.6" - NOX_SESSION: "unit-3.7-32" - NOX_SESSION: "unit-3.7" @@ -62,6 +62,8 @@ install: } $env:Path += ";$MINGW" + - ps: $env:BEZIER_DLL_PATH = "C:\projects\bezier\.nox\libbezier-debug\usr\bin" + # Packaging requirements - python -m pip install --upgrade "pip !=20.0,!=20.0.1" setuptools - python -m pip install --upgrade wheel diff --git a/noxfile.py b/noxfile.py index 6d878f55..23a54a63 100644 --- a/noxfile.py +++ b/noxfile.py @@ -169,7 +169,11 @@ def unit(session): install_bezier(session, debug=True) # Run pytest against the unit tests. run_args = ["pytest"] + session.posargs + [get_path("tests", "unit")] - session.run(*run_args) + env = {} + bezier_dll_path = os.environ.get("BEZIER_DLL_PATH") + if bezier_dll_path is not None: + env["PATH"] = bezier_dll_path + session.run(*run_args, env=env) @nox.session(py=DEFAULT_INTERPRETER) diff --git a/src/python/bezier/__config__.py b/src/python/bezier/__config__.py deleted file mode 100644 index bb355778..00000000 --- a/src/python/bezier/__config__.py +++ /dev/null @@ -1,95 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Environment configuration for ``bezier`` runtime dependencies. - -Only needed for Windows, to add ``extra-dll`` directory to the DLL search -path so that the ``libbezier`` DLL can be located. -""" - -import os - -import pkg_resources - - -# Error messages for ``handle_import_error``. -TEMPLATE = "No module named 'bezier.{}'" # 3.6, 3.7, 3.8, pypy3 -# NOTE: ``os.add_dll_directory()`` was added on Windows in Python 3.8. -OS_ADD_DLL_DIRECTORY = getattr(os, "add_dll_directory", None) - - -def add_dll_directory(extra_dll_dir): - """Add a DLL directory. - - This is only expected to be invoked on Windows. For Python versions before - 3.8, this will update the ``%PATH%`` environment variable to include - ``extra_dll_dir`` and for 3.8 and later, it will invoke - ``os.add_dll_directory()``. - """ - if not os.path.isdir(extra_dll_dir): - return - - if OS_ADD_DLL_DIRECTORY is not None: - OS_ADD_DLL_DIRECTORY(extra_dll_dir) # pylint: disable=not-callable - return - - path = os.environ.get("PATH", "") - values = [subdir for subdir in path.split(os.pathsep) if subdir] - values.append(extra_dll_dir) - os.environ["PATH"] = os.pathsep.join(values) - - -def modify_path(): - """Add the DLL directory to the module search path. - - This will only modify path if - * on Windows - * the ``extra-dll`` directory is in package resources - """ - if os.name != "nt": - return - - try: - extra_dll_dir = pkg_resources.resource_filename("bezier", "extra-dll") - except ImportError: - return - - add_dll_directory(extra_dll_dir) - - -def handle_import_error(caught_exc, name): - """Allow or re-raise an import error. - - This is to distinguish between expected and unexpected import errors. - If the module is not found, it simply means the Cython / Fortran speedups - were not built with the package. If the error message is different, e.g. - ``... undefined symbol: __curve_intersection_MOD_all_intersections``, then - the import error **should** be raised. - - Args: - caught_exc (ImportError): An exception caught when trying to import - a Cython module. - name (str): The name of the module. For example, for the module - ``bezier._curve_speedup``, the name is ``"_curve_speedup"``. - - Raises: - ImportError: If the error message is different than the basic - "missing module" error message. - """ - expected_msg = TEMPLATE.format(name) - if caught_exc.args == (expected_msg,): - return - - raise caught_exc - - -modify_path() diff --git a/src/python/bezier/__init__.py b/src/python/bezier/__init__.py index a654fdb3..698c88c7 100644 --- a/src/python/bezier/__init__.py +++ b/src/python/bezier/__init__.py @@ -27,9 +27,6 @@ .. autoclass:: Surface """ -# NOTE: ``__config__`` **must** be the first import because it (may) -# modify the search path used to locate shared libraries. -from bezier import __config__ from bezier._legacy import Surface from bezier._py_helpers import UnsupportedDegree from bezier.curve import Curve @@ -41,7 +38,6 @@ _HAS_SPEEDUP = True except ImportError as exc: # pragma: NO COVER - __config__.handle_import_error(exc, "_speedup") _HAS_SPEEDUP = False # NOTE: The ``__version__`` and ``__author__`` are hard-coded here, rather # than using ``pkg_resources.get_distribution("bezier").version`` diff --git a/tests/unit/test___config__.py b/tests/unit/test___config__.py deleted file mode 100644 index 467ebdad..00000000 --- a/tests/unit/test___config__.py +++ /dev/null @@ -1,126 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import unittest -import unittest.mock - - -class Test_modify_path(unittest.TestCase): - @staticmethod - def _call_function_under_test(): - from bezier import __config__ - - return __config__.modify_path() - - @unittest.mock.patch.multiple(os, name="not-nt", environ={}) - def test_non_windows(self): - return_value = self._call_function_under_test() - self.assertIsNone(return_value) - self.assertEqual(os.environ, {}) - - @unittest.mock.patch.multiple(os, name="nt", environ={}) - @unittest.mock.patch( - "pkg_resources.resource_filename", side_effect=ImportError - ) - def test_windows_without_dll(self, resource_filename): - return_value = self._call_function_under_test() - self.assertIsNone(return_value) - self.assertEqual(os.environ, {}) - # Check mock. - resource_filename.assert_called_once_with("bezier", "extra-dll") - - @unittest.mock.patch.multiple( - os, name="nt", environ={"PATH": "before" + os.pathsep} - ) - @unittest.mock.patch("os.path.isdir", return_value=True) - @unittest.mock.patch( - "pkg_resources.resource_filename", return_value="not-a-path" - ) - @unittest.mock.patch("bezier.__config__.OS_ADD_DLL_DIRECTORY", new=None) - def test_windows_with_dll(self, resource_filename, isdir): - return_value = self._call_function_under_test() - self.assertIsNone(return_value) - expected_path = "before" + os.pathsep + resource_filename.return_value - self.assertEqual(os.environ, {"PATH": expected_path}) - # Check mocks. - resource_filename.assert_called_once_with("bezier", "extra-dll") - isdir.assert_called_once_with(resource_filename.return_value) - - @unittest.mock.patch.multiple(os, name="nt", environ={}) - @unittest.mock.patch("os.path.isdir", return_value=True) - @unittest.mock.patch( - "pkg_resources.resource_filename", return_value="not-a-path" - ) - @unittest.mock.patch("bezier.__config__.OS_ADD_DLL_DIRECTORY", new=None) - def test_windows_with_dll_no_path(self, resource_filename, isdir): - return_value = self._call_function_under_test() - self.assertIsNone(return_value) - self.assertEqual(os.environ, {"PATH": resource_filename.return_value}) - # Check mocks. - resource_filename.assert_called_once_with("bezier", "extra-dll") - isdir.assert_called_once_with(resource_filename.return_value) - - @unittest.mock.patch.multiple(os, name="nt", environ={}) - @unittest.mock.patch("os.path.isdir", return_value=True) - @unittest.mock.patch( - "pkg_resources.resource_filename", return_value="not-a-path" - ) - @unittest.mock.patch("bezier.__config__.OS_ADD_DLL_DIRECTORY") - def test_windows_with_dll_at_least_38( - self, os_add_dll_directory, resource_filename, isdir - ): - return_value = self._call_function_under_test() - self.assertIsNone(return_value) - self.assertEqual(os.environ, {}) - # Check mocks. - resource_filename.assert_called_once_with("bezier", "extra-dll") - isdir.assert_called_once_with(resource_filename.return_value) - os_add_dll_directory.assert_called_once_with( - resource_filename.return_value - ) - - @unittest.mock.patch.multiple(os, name="nt", environ={}) - @unittest.mock.patch("os.path.isdir", return_value=False) - @unittest.mock.patch( - "pkg_resources.resource_filename", return_value="not-a-path" - ) - def test_windows_with_dll_but_not_a_dir(self, resource_filename, isdir): - return_value = self._call_function_under_test() - self.assertIsNone(return_value) - self.assertEqual(os.environ, {}) - # Check mocks. - resource_filename.assert_called_once_with("bezier", "extra-dll") - isdir.assert_called_once_with(resource_filename.return_value) - - -class Test_handle_import_error(unittest.TestCase): - @staticmethod - def _call_function_under_test(caught_exc, name): - from bezier import __config__ - - return __config__.handle_import_error(caught_exc, name) - - def test_valid_exception(self): - from bezier import __config__ - - name = "this_module" - single_arg = __config__.TEMPLATE.format(name) - caught_exc = ImportError(single_arg) - return_value = self._call_function_under_test(caught_exc, name) - self.assertIsNone(return_value) - - def test_invalid_exception(self): - caught_exc = ImportError("two", "args") - with self.assertRaises(ImportError) as exc_info: - self._call_function_under_test(caught_exc, "name") - self.assertIs(exc_info.exception, caught_exc)