From 441c082c881455a1149fa14ab7de2b91286da554 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 16 Aug 2017 16:35:28 +0200 Subject: [PATCH 1/3] BUG: blacklist MSVC hypot() on win32 --- numpy/core/src/private/npy_config.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/numpy/core/src/private/npy_config.h b/numpy/core/src/private/npy_config.h index b8e18e96152f..1e2151447d49 100644 --- a/numpy/core/src/private/npy_config.h +++ b/numpy/core/src/private/npy_config.h @@ -62,6 +62,15 @@ #endif +/* MSVC _hypot messes with fp precision mode on 32-bit, see gh-9567 */ +#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(_WIN64) + +#undef HAVE_HYPOT +#undef HAVE_HYPOTF +#undef HAVE_HYPOTL + +#endif + /* Intel C for Windows uses POW for 64 bits longdouble*/ #if defined(_MSC_VER) && defined(__INTEL_COMPILER) From b5f472d450522a701fa526f8c85f7eb467d65c1a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 16 Aug 2017 18:00:18 +0200 Subject: [PATCH 2/3] ENH: check for FPU mode changes in the test suite Emit a test failure if the FPU mode changes when running a test case, allowing to pinpoint what test caused the mode change. --- .../src/multiarray/multiarray_tests.c.src | 34 +++++++++++++++++++ numpy/testing/nose_tools/noseclasses.py | 26 ++++++++++++++ numpy/testing/nose_tools/nosetester.py | 15 ++++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src index f5be52d745df..657c4064ee9c 100644 --- a/numpy/core/src/multiarray/multiarray_tests.c.src +++ b/numpy/core/src/multiarray/multiarray_tests.c.src @@ -1560,6 +1560,37 @@ extint_ceildiv_128_64(PyObject *NPY_UNUSED(self), PyObject *args) { } +static char get_fpu_mode_doc[] = ( + "get_fpu_mode()\n" + "\n" + "Get the current FPU control word, in a platform-dependent format.\n" + "Returns None if not implemented on current platform."); + +static PyObject * +get_fpu_mode(PyObject *NPY_UNUSED(self), PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + return NULL; + } + +#if defined(_MSC_VER) + { + unsigned int result = 0; + result = _controlfp(0, 0); + return PyLong_FromLongLong(result); + } +#elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) + { + unsigned short cw = 0; + __asm__("fstcw %w0" : "=m" (cw)); + return PyLong_FromLongLong(cw); + } +#else + return Py_RETURN_NONE; +#endif +} + + static PyMethodDef Multiarray_TestsMethods[] = { {"IsPythonScalar", IsPythonScalar, @@ -1650,6 +1681,9 @@ static PyMethodDef Multiarray_TestsMethods[] = { {"extint_ceildiv_128_64", extint_ceildiv_128_64, METH_VARARGS, NULL}, + {"get_fpu_mode", + get_fpu_mode, + METH_VARARGS, get_fpu_mode_doc}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/numpy/testing/nose_tools/noseclasses.py b/numpy/testing/nose_tools/noseclasses.py index 2f5d05004e54..9756b9b45162 100644 --- a/numpy/testing/nose_tools/noseclasses.py +++ b/numpy/testing/nose_tools/noseclasses.py @@ -7,6 +7,7 @@ from __future__ import division, absolute_import, print_function import os +import sys import doctest import inspect @@ -317,6 +318,31 @@ def configure(self, options, conf): KnownFailure = KnownFailurePlugin # backwards compat +class FPUModeCheckPlugin(Plugin): + """ + Plugin that checks the FPU mode before and after each test, + raising failures if the test changed the mode. + """ + + def prepareTestCase(self, test): + from numpy.core.multiarray_tests import get_fpu_mode + + def run(result): + old_mode = get_fpu_mode() + test.test(result) + new_mode = get_fpu_mode() + + if old_mode != new_mode: + try: + raise AssertionError( + "FPU mode changed from {0:#x} to {1:#x} during the " + "test".format(old_mode, new_mode)) + except AssertionError: + result.addFailure(test, sys.exc_info()) + + return run + + # Class allows us to save the results of the tests in runTests - see runTests # method docstring for details class NumpyTestProgram(nose.core.TestProgram): diff --git a/numpy/testing/nose_tools/nosetester.py b/numpy/testing/nose_tools/nosetester.py index 407653fc35e0..c2cf58377083 100644 --- a/numpy/testing/nose_tools/nosetester.py +++ b/numpy/testing/nose_tools/nosetester.py @@ -154,7 +154,8 @@ class NoseTester(object): want to initialize `NoseTester` objects on behalf of other code. """ - def __init__(self, package=None, raise_warnings="release", depth=0): + def __init__(self, package=None, raise_warnings="release", depth=0, + check_fpu_mode=False): # Back-compat: 'None' used to mean either "release" or "develop" # depending on whether this was a release or develop version of # numpy. Those semantics were fine for testing numpy, but not so @@ -191,6 +192,9 @@ def __init__(self, package=None, raise_warnings="release", depth=0): # Set to "release" in constructor in maintenance branches. self.raise_warnings = raise_warnings + # Whether to check for FPU mode changes + self.check_fpu_mode = check_fpu_mode + def _test_argv(self, label, verbose, extra_argv): ''' Generate argv for nosetest command @@ -289,9 +293,13 @@ def prepare_test_args(self, label='fast', verbose=1, extra_argv=None, # construct list of plugins import nose.plugins.builtin from nose.plugins import EntryPointPluginManager - from .noseclasses import KnownFailurePlugin, Unplugger + from .noseclasses import (KnownFailurePlugin, Unplugger, + FPUModeCheckPlugin) plugins = [KnownFailurePlugin()] plugins += [p() for p in nose.plugins.builtin.plugins] + if self.check_fpu_mode: + plugins += [FPUModeCheckPlugin()] + argv += ["--with-fpumodecheckplugin"] try: # External plugins (like nose-timer) entrypoint_manager = EntryPointPluginManager() @@ -548,4 +556,5 @@ def _numpy_tester(): mode = "develop" else: mode = "release" - return NoseTester(raise_warnings=mode, depth=1) + return NoseTester(raise_warnings=mode, depth=1, + check_fpu_mode=True) From 3deb8fa4f4d25e434c86188593f95dfc2fed041c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 16 Aug 2017 18:15:18 +0200 Subject: [PATCH 3/3] TST: add FPU mode check also for pytest --- numpy/conftest.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 numpy/conftest.py diff --git a/numpy/conftest.py b/numpy/conftest.py new file mode 100644 index 000000000000..ea4197049314 --- /dev/null +++ b/numpy/conftest.py @@ -0,0 +1,54 @@ +""" +Pytest configuration and fixtures for the Numpy test suite. +""" +from __future__ import division, absolute_import, print_function + +import warnings +import pytest + +from numpy.core.multiarray_tests import get_fpu_mode + + +_old_fpu_mode = None +_collect_results = {} + + +@pytest.hookimpl() +def pytest_itemcollected(item): + """ + Check FPU precision mode was not changed during test collection. + + The clumsy way we do it here is mainly necessary because numpy + still uses yield tests, which can execute code at test collection + time. + """ + global _old_fpu_mode + + mode = get_fpu_mode() + + if _old_fpu_mode is None: + _old_fpu_mode = mode + elif mode != _old_fpu_mode: + _collect_results[item] = (_old_fpu_mode, mode) + _old_fpu_mode = mode + + +@pytest.fixture(scope="function", autouse=True) +def check_fpu_mode(request): + """ + Check FPU precision mode was not changed during the test. + """ + old_mode = get_fpu_mode() + yield + new_mode = get_fpu_mode() + + if old_mode != new_mode: + raise AssertionError("FPU precision mode changed from {0:#x} to {1:#x}" + " during the test".format(old_mode, new_mode)) + + collect_result = _collect_results.get(request.node) + if collect_result is not None: + old_mode, new_mode = collect_result + raise AssertionError("FPU precision mode changed from {0:#x} to {1:#x}" + " when collecting the test".format(old_mode, + new_mode))