From 2fda76737dba484a3b1c250c1c69b331099f276d Mon Sep 17 00:00:00 2001 From: Adrian Peter Krone Date: Mon, 16 Dec 2024 20:37:59 +0100 Subject: [PATCH] Fixed importskips --- tests/test_draw.py | 249 ------------------------------------ tests/test_ipywidget.py | 129 +++++++++++++++++++ tests/test_qtwidget.py | 126 ++++++++++++++++++ tests/test_without_pyqt6.py | 4 +- 4 files changed, 256 insertions(+), 252 deletions(-) create mode 100644 tests/test_ipywidget.py create mode 100644 tests/test_qtwidget.py diff --git a/tests/test_draw.py b/tests/test_draw.py index 9442a850..9cd3ad5f 100644 --- a/tests/test_draw.py +++ b/tests/test_draw.py @@ -2,8 +2,6 @@ from iminuit import Minuit from pathlib import Path import numpy as np -from numpy.testing import assert_allclose -import contextlib mpl = pytest.importorskip("matplotlib") plt = pytest.importorskip("matplotlib.pyplot") @@ -15,12 +13,6 @@ def f1(x, y): return (1 - x) ** 2 + np.exp((y - 1) ** 2) -def qtinteractive(m, plot=None, raise_on_exception=False, **kwargs): - from iminuit.qtwidget import make_widget - - return make_widget(m, plot, kwargs, raise_on_exception, run_event_loop=False) - - @pytest.fixture def minuit(): m = Minuit(f1, x=0, y=0) @@ -139,244 +131,3 @@ def test_mnmatrix_7(fig): m = Minuit(lambda x: abs(x) ** 2 + x**4 + 10 * x, x=0) m.migrad() m.draw_mnmatrix(cl=[1, 3]) - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_interactive_ipywidgets(mock_ipython): - ipywidgets = pytest.importorskip("ipywidgets") - - def cost(a, b): - return a**2 + b**2 - - class Plot: - def __init__(self): - self.called = False - self.raises = False - - def __call__(self, args): - self.called = True - if self.raises: - raise ValueError("foo") - - @contextlib.contextmanager - def assert_call(self): - self.called = False - yield - assert self.called - - plot = Plot() - - m = Minuit(cost, 1, 1) - - with pytest.raises(AttributeError, match="no visualize method"): - m.interactive(raise_on_exception=True) - - with plot.assert_call(): - out1 = m.interactive(plot) - assert isinstance(out1, ipywidgets.HBox) - - # manipulate state to also check this code - ui = out1.children[1] - header, parameters = ui.children - fit_button, update_button, reset_button, algo_select = header.children - with plot.assert_call(): - fit_button.click() - assert_allclose(m.values, (0, 0), atol=1e-5) - with plot.assert_call(): - reset_button.click() - assert_allclose(m.values, (1, 1), atol=1e-5) - - algo_select.value = "Scipy" - with plot.assert_call(): - fit_button.click() - - algo_select.value = "Simplex" - with plot.assert_call(): - fit_button.click() - - update_button.value = False - with plot.assert_call(): - # because of implementation details, we have to trigger the slider several times - for i in range(5): - parameters.children[0].slider.value = i # change first slider - parameters.children[0].fix.value = True - with plot.assert_call(): - parameters.children[0].fit.value = True - - class Cost: - def visualize(self, args): - return plot(args) - - def __call__(self, a, b): - return (a - 100) ** 2 + (b + 100) ** 2 - - c = Cost() - m = Minuit(c, 0, 0) - with plot.assert_call(): - out = m.interactive(raise_on_exception=True) - - # this should modify slider range - ui = out.children[1] - header, parameters = ui.children - fit_button, update_button, reset_button, algo_select = header.children - assert parameters.children[0].slider.max == 1 - assert parameters.children[1].slider.min == -1 - with plot.assert_call(): - fit_button.click() - assert_allclose(m.values, (100, -100), atol=1e-5) - # this should trigger an exception - plot.raises = True - with plot.assert_call(): - fit_button.click() - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_interactive_pyqt6(qtbot): - PyQt6 = pytest.importorskip("PyQt6") - - def cost(a, b): - return a**2 + b**2 - - class Plot: - def __init__(self): - self.called = False - self.raises = False - - def __call__(self, args): - self.called = True - if self.raises: - raise ValueError("foo") - - @contextlib.contextmanager - def assert_call(self): - self.called = False - yield - assert self.called - - plot = Plot() - - m = Minuit(cost, 1, 1) - - with plot.assert_call(): - mw1 = qtinteractive(m, plot) - qtbot.addWidget(mw1) - assert isinstance(mw1, PyQt6.QtWidgets.QWidget) - - # manipulate state to also check this code - with plot.assert_call(): - mw1.fit_button.click() - assert_allclose(m.values, (0, 0), atol=1e-5) - with plot.assert_call(): - mw1.reset_button.click() - assert_allclose(m.values, (1, 1), atol=1e-5) - - mw1.algo_choice.setCurrentText("Scipy") - with plot.assert_call(): - mw1.fit_button.click() - - mw1.algo_choice.setCurrentText("Simplex") - with plot.assert_call(): - mw1.fit_button.click() - - mw1.update_button.click() - with plot.assert_call(): - mw1.parameters[0].slider.valueChanged.emit(int(5e7)) - mw1.parameters[0].fix.click() - with plot.assert_call(): - mw1.parameters[0].fit.click() - - class Cost: - def visualize(self, args): - return plot(args) - - def __call__(self, a, b): - return (a - 100) ** 2 + (b + 100) ** 2 - - c = Cost() - m = Minuit(c, 0, 0) - with plot.assert_call(): - mw = qtinteractive(m, raise_on_exception=True) - qtbot.addWidget(mw) - - # this should modify slider range - assert mw.parameters[0].vmax == 1 - assert mw.parameters[1].vmin == -1 - with plot.assert_call(): - mw.fit_button.click() - assert_allclose(m.values, (100, -100), atol=1e-5) - # this should trigger an exception - # plot.raises = True - # with plot.assert_call(): - # mw.fit_button.click() - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_interactive_ipywidgets_raises(mock_ipython): - pytest.importorskip("ipywidgets") - - def raiser(args): - raise ValueError - - m = Minuit(lambda x, y: 0, 0, 1) - - # by default do not raise - m.interactive(raiser) - - with pytest.raises(ValueError): - m.interactive(raiser, raise_on_exception=True) - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_interactive_pyqt6_raises(qtbot): - pytest.importorskip("PyQt6") - - def raiser(args): - raise ValueError - - m = Minuit(lambda x, y: 0, 0, 1) - - # by default do not raise - qtinteractive(m, raiser) - - with pytest.raises(ValueError): - qtinteractive(m, raiser, raise_on_exception=True) - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_interactive_ipywidgets_with_array_func(mock_ipython): - pytest.importorskip("ipywidgets") - - def cost(par): - return par[0] ** 2 + (par[1] / 2) ** 2 - - class TraceArgs: - nargs = 0 - - def __call__(self, par): - self.nargs = len(par) - - trace_args = TraceArgs() - m = Minuit(cost, (1, 2)) - - m.interactive(trace_args) - assert trace_args.nargs > 0 - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_interactive_pyqt6_with_array_func(qtbot): - pytest.importorskip("PyQt6") - - def cost(par): - return par[0] ** 2 + (par[1] / 2) ** 2 - - class TraceArgs: - nargs = 0 - - def __call__(self, par): - self.nargs = len(par) - - trace_args = TraceArgs() - m = Minuit(cost, (1, 2)) - - qtinteractive(m, trace_args) - assert trace_args.nargs > 0 diff --git a/tests/test_ipywidget.py b/tests/test_ipywidget.py new file mode 100644 index 00000000..5b511af5 --- /dev/null +++ b/tests/test_ipywidget.py @@ -0,0 +1,129 @@ +import pytest +from iminuit import Minuit +from numpy.testing import assert_allclose +import contextlib + +mpl = pytest.importorskip("matplotlib") +plt = pytest.importorskip("matplotlib.pyplot") +ipywidgets = pytest.importorskip("ipywidgets") + +mpl.use("Agg") + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_interactive_ipywidgets(mock_ipython): + def cost(a, b): + return a**2 + b**2 + + class Plot: + def __init__(self): + self.called = False + self.raises = False + + def __call__(self, args): + self.called = True + if self.raises: + raise ValueError("foo") + + @contextlib.contextmanager + def assert_call(self): + self.called = False + yield + assert self.called + + plot = Plot() + + m = Minuit(cost, 1, 1) + + with pytest.raises(AttributeError, match="no visualize method"): + m.interactive(raise_on_exception=True) + + with plot.assert_call(): + out1 = m.interactive(plot) + assert isinstance(out1, ipywidgets.HBox) + + # manipulate state to also check this code + ui = out1.children[1] + header, parameters = ui.children + fit_button, update_button, reset_button, algo_select = header.children + with plot.assert_call(): + fit_button.click() + assert_allclose(m.values, (0, 0), atol=1e-5) + with plot.assert_call(): + reset_button.click() + assert_allclose(m.values, (1, 1), atol=1e-5) + + algo_select.value = "Scipy" + with plot.assert_call(): + fit_button.click() + + algo_select.value = "Simplex" + with plot.assert_call(): + fit_button.click() + + update_button.value = False + with plot.assert_call(): + # because of implementation details, we have to trigger the slider several times + for i in range(5): + parameters.children[0].slider.value = i # change first slider + parameters.children[0].fix.value = True + with plot.assert_call(): + parameters.children[0].fit.value = True + + class Cost: + def visualize(self, args): + return plot(args) + + def __call__(self, a, b): + return (a - 100) ** 2 + (b + 100) ** 2 + + c = Cost() + m = Minuit(c, 0, 0) + with plot.assert_call(): + out = m.interactive(raise_on_exception=True) + + # this should modify slider range + ui = out.children[1] + header, parameters = ui.children + fit_button, update_button, reset_button, algo_select = header.children + assert parameters.children[0].slider.max == 1 + assert parameters.children[1].slider.min == -1 + with plot.assert_call(): + fit_button.click() + assert_allclose(m.values, (100, -100), atol=1e-5) + # this should trigger an exception + plot.raises = True + with plot.assert_call(): + fit_button.click() + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_interactive_ipywidgets_raises(mock_ipython): + def raiser(args): + raise ValueError + + m = Minuit(lambda x, y: 0, 0, 1) + + # by default do not raise + m.interactive(raiser) + + with pytest.raises(ValueError): + m.interactive(raiser, raise_on_exception=True) + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_interactive_ipywidgets_with_array_func(mock_ipython): + def cost(par): + return par[0] ** 2 + (par[1] / 2) ** 2 + + class TraceArgs: + nargs = 0 + + def __call__(self, par): + self.nargs = len(par) + + trace_args = TraceArgs() + m = Minuit(cost, (1, 2)) + + m.interactive(trace_args) + assert trace_args.nargs > 0 diff --git a/tests/test_qtwidget.py b/tests/test_qtwidget.py new file mode 100644 index 00000000..9cb28589 --- /dev/null +++ b/tests/test_qtwidget.py @@ -0,0 +1,126 @@ +import pytest +from iminuit import Minuit +from numpy.testing import assert_allclose +import contextlib + +mpl = pytest.importorskip("matplotlib") +plt = pytest.importorskip("matplotlib.pyplot") +PyQt6 = pytest.importorskip("PyQt6") + +mpl.use("Agg") + + +def qtinteractive(m, plot=None, raise_on_exception=False, **kwargs): + from iminuit.qtwidget import make_widget + + return make_widget(m, plot, kwargs, raise_on_exception, run_event_loop=False) + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_interactive_pyqt6(qtbot): + def cost(a, b): + return a**2 + b**2 + + class Plot: + def __init__(self): + self.called = False + self.raises = False + + def __call__(self, args): + self.called = True + if self.raises: + raise ValueError("foo") + + @contextlib.contextmanager + def assert_call(self): + self.called = False + yield + assert self.called + + plot = Plot() + + m = Minuit(cost, 1, 1) + + with plot.assert_call(): + mw1 = qtinteractive(m, plot) + qtbot.addWidget(mw1) + assert isinstance(mw1, PyQt6.QtWidgets.QWidget) + + # manipulate state to also check this code + with plot.assert_call(): + mw1.fit_button.click() + assert_allclose(m.values, (0, 0), atol=1e-5) + with plot.assert_call(): + mw1.reset_button.click() + assert_allclose(m.values, (1, 1), atol=1e-5) + + mw1.algo_choice.setCurrentText("Scipy") + with plot.assert_call(): + mw1.fit_button.click() + + mw1.algo_choice.setCurrentText("Simplex") + with plot.assert_call(): + mw1.fit_button.click() + + mw1.update_button.click() + with plot.assert_call(): + mw1.parameters[0].slider.valueChanged.emit(int(5e7)) + mw1.parameters[0].fix.click() + with plot.assert_call(): + mw1.parameters[0].fit.click() + + class Cost: + def visualize(self, args): + return plot(args) + + def __call__(self, a, b): + return (a - 100) ** 2 + (b + 100) ** 2 + + c = Cost() + m = Minuit(c, 0, 0) + with plot.assert_call(): + mw = qtinteractive(m, raise_on_exception=True) + qtbot.addWidget(mw) + + # this should modify slider range + assert mw.parameters[0].vmax == 1 + assert mw.parameters[1].vmin == -1 + with plot.assert_call(): + mw.fit_button.click() + assert_allclose(m.values, (100, -100), atol=1e-5) + # this should trigger an exception + # plot.raises = True + # with plot.assert_call(): + # mw.fit_button.click() + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_interactive_pyqt6_raises(qtbot): + def raiser(args): + raise ValueError + + m = Minuit(lambda x, y: 0, 0, 1) + + # by default do not raise + qtinteractive(m, raiser) + + with pytest.raises(ValueError): + qtinteractive(m, raiser, raise_on_exception=True) + + +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_interactive_pyqt6_with_array_func(qtbot): + def cost(par): + return par[0] ** 2 + (par[1] / 2) ** 2 + + class TraceArgs: + nargs = 0 + + def __call__(self, par): + self.nargs = len(par) + + trace_args = TraceArgs() + m = Minuit(cost, (1, 2)) + + qtinteractive(m, trace_args) + assert trace_args.nargs > 0 diff --git a/tests/test_without_pyqt6.py b/tests/test_without_pyqt6.py index 3886b2a8..84e49c2b 100644 --- a/tests/test_without_pyqt6.py +++ b/tests/test_without_pyqt6.py @@ -2,10 +2,8 @@ from iminuit.cost import LeastSquares import pytest -pytest.importorskip("PyQt6") - -def test_interactive(qtbot): +def test_interactive(): pytest.importorskip("matplotlib") import iminuit