diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c119f8d5b7..11b0d93abf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,9 @@ name: CI on: - # Triggers the workflow on push only for the master branch or pull request events push: - branches: [master] + branches: [main] pull_request: - - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: defaults: @@ -16,134 +13,89 @@ defaults: jobs: # This workflow contains a single job called "build" build: - name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.name-suffix }}" + name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.QT_API }}" runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: include: - - name-suffix: "PyQt5 sdist" - os: ubuntu-20.04 + - os: ubuntu-20.04 python-version: "3.8" - BUILD_OPTION: --sdist - QT_BINDING: PyQt5 - RUN_TESTS_OPTIONS: --qt-binding=PyQt5 --no-opencl --low-mem - - name-suffix: "PyQt5 wheel" - os: macos-latest - python-version: "3.10" - BUILD_OPTION: --wheel - QT_BINDING: PyQt5 - RUN_TESTS_OPTIONS: --qt-binding=PyQt5 --no-opencl --low-mem + QT_API: PyQt5 + - os: ubuntu-latest + python-version: "3.11" + QT_API: PyQt6 + - os: ubuntu-latest + python-version: "3.12" + QT_API: PySide6 - - name-suffix: "PySide6 sdist" - os: ubuntu-latest - python-version: "3.8" - BUILD_OPTION: --sdist - QT_BINDING: PySide6 - RUN_TESTS_OPTIONS: --qt-binding=PySide6 --no-opencl --low-mem - - name-suffix: "PySide6 wheel" - os: macos-latest + - os: macos-13 + python-version: "3.10" + QT_API: PyQt5 + - os: macos-13 + python-version: "3.12" + QT_API: PyQt6 + - os: macos-13 python-version: "3.9" - BUILD_OPTION: --wheel - QT_BINDING: "PySide6<6.7" - RUN_TESTS_OPTIONS: --qt-binding=PySide6 --no-opencl --low-mem + QT_API: PySide6 - - name-suffix: "PyQt6 wheel" - os: ubuntu-latest - python-version: "3.11" - BUILD_OPTION: --wheel - QT_BINDING: PyQt6 - RUN_TESTS_OPTIONS: --qt-binding=PyQt6 --no-opengl --low-mem - - name-suffix: "PyQt6 wheel" - os: macos-latest - python-version: "3.11" - BUILD_OPTION: --wheel - QT_BINDING: PyQt6 - RUN_TESTS_OPTIONS: --qt-binding=PyQt6 --no-opencl --low-mem - - - name-suffix: "No GUI wheel" - os: windows-latest + - os: windows-latest python-version: "3.9" - BUILD_COMMAND: --wheel - QT_BINDING: PyQt5 - RUN_TESTS_OPTIONS: --no-gui --low-mem - # No GUI tests on Windows + QT_API: PyQt5 + - os: windows-latest + python-version: "3.12" + QT_API: PyQt6 + - os: windows-latest + python-version: "3.10" + QT_API: PySide6 - # Steps represent a sequence of tasks that will be executed as part of the job steps: - uses: actions/checkout@v4 - # Install X server packages + # Install packages: + # OpenCL lib and icd + # xvfb to run the GUI test headless # libegl1-mesa: Required by Qt xcb platform plugin - # ocl-icd-opencl-dev: OpenCL headers, lib and icd loader # libgl1-mesa-glx: For OpenGL # xserver-xorg-video-dummy: For OpenGL - # libxkbcommon-x11-0: needed for Qt plugins - - name: Install X server + # libxkbcommon-x11-0, ..: needed for Qt plugins + - name: Install system packages if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install libegl1-mesa ocl-icd-opencl-dev intel-opencl-icd libgl1-mesa-glx xserver-xorg-video-dummy libxkbcommon-x11-0 libxkbcommon0 libxkbcommon-dev libxcb-icccm4 libxcb-image0 libxcb-shm0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-render0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxcb-cursor0 libxcb1 + sudo apt-get install ocl-icd-opencl-dev intel-opencl-icd xvfb libegl1-mesa libgl1-mesa-glx xserver-xorg-video-dummy libxkbcommon-x11-0 libxkbcommon0 libxkbcommon-dev libxcb-icccm4 libxcb-image0 libxcb-shm0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-render0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxcb-cursor0 libxcb1 - # Runs a single command using the runners shell - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "pip" - - name: Upgrade distribution modules + - name: Install build dependencies run: | - python -m pip install --upgrade pip - pip install --upgrade build setuptools wheel - pip install --upgrade --pre cython - - - name: Print python info used for build - run: | - python ./ci/info_platform.py + pip install --upgrade --pre build cython setuptools wheel pip list - - name: Generate source package or wheel + - name: Build + env: + MACOSX_DEPLOYMENT_TARGET: "10.9" run: | - if [ ${{ runner.os }} == 'macOS' ]; then - export MACOSX_DEPLOYMENT_TARGET=10.9; - fi - python -m build --no-isolation ${{ matrix.BUILD_OPTION }} + python -m build --no-isolation ls dist - - name: Pre-install dependencies - run: | - if [ -s "ci/requirements-pinned.txt" ]; - then - pip install -r ci/requirements-pinned.txt; - fi - pip install --pre -r requirements.txt - pip uninstall -y PyQt5 PyQt6 PySide6 - pip install --pre "${{ matrix.QT_BINDING }}" - - - name: Install pytest - run: | - pip install pytest - pip install pytest-xvfb - pip install pytest-mock - - - name: Install silx package - run: pip install --pre --find-links dist/ --no-cache-dir --no-index --no-build-isolation silx - - - name: Print python info used for tests + - name: Install run: | + pip install -r ci/requirements-pinned.txt + pip install --pre "${{ matrix.QT_API }}" + pip install --pre "$(ls dist/silx*.whl)[full,test]" python ./ci/info_platform.py pip list - # For Linux: Start X server with dummy video dirver - # Use this instead of Xvfb to have RANDR extension - # Otherwise there is a bug with Qt5.10.0 - - name: Run the tests + - name: Test + env: + QT_API: ${{ matrix.QT_API }} + SILX_TEST_LOW_MEM: "False" run: | - if [ ${{ runner.os }} == 'Linux' ]; then - export OCL_ICD_VENDORS=$(pwd)/intel_opencl_icd/vendors - export DISPLAY=:99.0 - Xorg -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./99.log -config ./ci/xorg.conf :99 & - sleep 3 + if [ ${{ runner.os }} == 'Windows' ]; then + export WITH_GL_TEST=False fi - echo "RUN_TESTS_OPTIONS="${{ matrix.RUN_TESTS_OPTIONS }} - python run_tests.py --installed -v ${{ matrix.RUN_TESTS_OPTIONS }} + python -c "import silx.test, sys; sys.exit(silx.test.run_tests(verbosity=1, args=['--qt-binding=${{ matrix.QT_API }}']));" diff --git a/ci/appveyor.yml b/ci/appveyor.yml index d4b0fa1790..4b5118dbd2 100644 --- a/ci/appveyor.yml +++ b/ci/appveyor.yml @@ -21,25 +21,22 @@ environment: global: WIN_SDK_ROOT: "C:\\Program Files\\Microsoft SDKs\\Windows" VENV_BUILD_DIR: "venv_build" - VENV_TEST_DIR: "venv_test" + VENV_TEST_DIR: "..\\venv_test" matrix: # Python 3.9 - PYTHON_DIR: "C:\\Python39-x64" - QT_BINDING: "PyQt5" - WITH_GL_TEST: True + QT_API: "PyQt5" PIP_OPTIONS: "-q --pre" # Python 3.12 - PYTHON_DIR: "C:\\Python312-x64" - QT_BINDING: "PySide6<6.7" - WITH_GL_TEST: True + QT_API: "PySide6" PIP_OPTIONS: "-q --pre" # Python 3.11 - PYTHON_DIR: "C:\\Python311-x64" - QT_BINDING: "PyQt6" - WITH_GL_TEST: True + QT_API: "PyQt6" PIP_OPTIONS: "-q" @@ -56,7 +53,7 @@ install: - "python -m pip install %PIP_OPTIONS% --upgrade pip" # Download Mesa OpenGL in Python directory when testing OpenGL - - IF %WITH_GL_TEST%==True curl -fsS -o %PYTHON_DIR%\\opengl32.dll http://www.silx.org/pub/silx/continuous_integration/opengl32_mingw-mesa-x86_64.dll + - curl -fsS -o %PYTHON_DIR%\\opengl32.dll https://www.silx.org/pub/silx/continuous_integration/opengl32_mingw-mesa-x86_64.dll build_script: # Create build virtualenv @@ -94,8 +91,7 @@ before_test: - pip install %PIP_OPTIONS% -r requirements.txt # Install selected Qt binding - - "pip uninstall -y PyQt5 PySide6 PyQt6" - - pip install %PIP_OPTIONS% "%QT_BINDING%" + - pip install %PIP_OPTIONS% "%QT_API%" # Install pytest - "pip install %PIP_OPTIONS% pytest" @@ -118,8 +114,7 @@ before_test: test_script: # Run tests with selected Qt binding and without OpenCL - - echo "WITH_GL_TEST=%WITH_GL_TEST%" - - "python run_tests.py --installed -v --no-opencl --low-mem" + - python -c "import silx.test, sys; sys.exit(silx.test.run_tests(verbosity=1, args=('--no-opencl', '--low-mem', '--qt-binding=%QT_API%')));" after_test: # Leave test virtualenv diff --git a/ci/xorg.conf b/ci/xorg.conf deleted file mode 100644 index 4038f31888..0000000000 --- a/ci/xorg.conf +++ /dev/null @@ -1,21 +0,0 @@ -Section "Device" - Identifier "Video Device" - Driver "dummy" -EndSection - -Section "Monitor" - Identifier "Monitor" - HorizSync 31.5-48.5 - VertRefresh 50-70 -EndSection - -Section "Screen" - Identifier "Default Screen" - Monitor "Monitor" - Device "Video Device" - DefaultDepth 24 - SubSection "Display" - Depth 24 - Modes "1024x768" - EndSubSection -EndSection diff --git a/pyproject.toml b/pyproject.toml index 4b95617e42..8094c56e8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,16 +13,3 @@ safe = true [tool.pytest.ini_options] minversion = "6.0" -python_files = [ - "test/test*.py", - "test/Test*.py", -] -python_classes = "Test" -python_functions = "test" -filterwarnings = [ - "error", - # note the use of single quote below to denote "raw" strings in TOML - 'ignore:tostring\(\) is deprecated. Use tobytes\(\) instead\.:DeprecationWarning:OpenGL', - 'ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning', - 'ignore:Unable to import recommended hash:UserWarning:pytools', -] diff --git a/run_tests.py b/run_tests.py index 63f5612862..1dc6c99a46 100755 --- a/run_tests.py +++ b/run_tests.py @@ -170,22 +170,10 @@ def normalize_option(option): return os.path.join(PROJECT_PATH, *option_parts[2:]) return option - args = [normalize_option(p) for p in sys.argv[1:] if p != "--installed"] - - # Run test on PROJECT_PATH if nothing is specified - without_options = [a for a in args if not a.startswith("-")] - if len(without_options) == 0: - args += [PROJECT_PATH] - - argv = ["--rootdir", PROJECT_PATH] + args + test_module = importlib.import_module(f"{PROJECT_NAME}.test") sys.exit( - subprocess.run( - [ - sys.executable, - "-m", - "pytest", - ] - + argv, - check=False, - ).returncode + test_module.run_tests( + module=None, + args=[normalize_option(p) for p in sys.argv[1:] if p != "--installed"], + ) ) diff --git a/src/silx/gui/dialog/test/test_datafiledialog.py b/src/silx/gui/dialog/test/test_datafiledialog.py index 1837f22609..7f6b17c09a 100644 --- a/src/silx/gui/dialog/test/test_datafiledialog.py +++ b/src/silx/gui/dialog/test/test_datafiledialog.py @@ -23,6 +23,8 @@ # ###########################################################################*/ """Test for silx.gui.hdf5 module""" +from __future__ import annotations + __authors__ = ["V. Valls"] __license__ = "MIT" __date__ = "08/03/2019" @@ -47,7 +49,7 @@ def setUpModule(): global _tmpDirectory - _tmpDirectory = tempfile.mkdtemp(prefix=__name__) + _tmpDirectory = os.path.realpath(tempfile.mkdtemp(prefix=__name__)) data = numpy.arange(100 * 100) data.shape = 100, 100 @@ -123,18 +125,27 @@ def qWaitForPendingActions(self, dialog): raise RuntimeError("Still have pending actions") def assertSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ != path2_: - # Use the unittest API to log and display error - self.assertEqual(path1, path2) + self.assertEqual( + os.path.normcase(os.path.realpath(path1)), + os.path.normcase(os.path.realpath(path2)), + msg=f"Paths differs: {path1} != {path2}", + ) - def assertNotSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ == path2_: - # Use the unittest API to log and display error - self.assertNotEqual(path1, path2) + def assertSameUrls( + self, + url1: silx.io.url.DataUrl | str, + url2: silx.io.url.DataUrl | str, + ): + """Check that both DataUrls are equivalent""" + if isinstance(url1, str): + url1 = silx.io.url.DataUrl(url1) + if isinstance(url2, str): + url2 = silx.io.url.DataUrl(url2) + + self.assertEqual(url1.scheme(), url2.scheme()) + self.assertSamePath(url1.file_path(), url2.file_path()) + self.assertEqual(url1.data_path(), url2.data_path()) + self.assertEqual(url1.data_slice(), url2.data_slice()) class TestDataFileDialogInteraction(testutils.TestCaseQt, _UtilsMixin): @@ -273,7 +284,7 @@ def testClickOnBackToParentTool(self): dialog.show() self.qWaitForWindowExposed(dialog) - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] + urlLineEdit = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] action = testutils.findChildren(dialog, qt.QAction, name="toParentAction")[0] toParentButton = testutils.getQToolButtonFromAction(action) filename = _tmpDirectory + "/data/data.h5" @@ -282,51 +293,47 @@ def testClickOnBackToParentTool(self): path = silx.io.url.DataUrl(file_path=filename, data_path="/group/image").path() dialog.selectUrl(path) self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl( + url = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/group/image" - ).path() - self.assertSamePath(url.text(), path) + ) + self.assertSameUrls(urlLineEdit.text(), url) # test self.mouseClick(toParentButton, qt.Qt.LeftButton) self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/" - ).path() - self.assertSamePath(url.text(), path) + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/") + self.assertSameUrls(urlLineEdit.text(), url) self.mouseClick(toParentButton, qt.Qt.LeftButton) self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory + "/data") + self.assertSamePath(urlLineEdit.text(), _tmpDirectory + "/data") self.mouseClick(toParentButton, qt.Qt.LeftButton) self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory) + self.assertSamePath(urlLineEdit.text(), _tmpDirectory) def testClickOnBackToRootTool(self): dialog = self.createDialog() dialog.show() self.qWaitForWindowExposed(dialog) - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] + urlLineEdit = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] action = testutils.findChildren(dialog, qt.QAction, name="toRootFileAction")[0] button = testutils.getQToolButtonFromAction(action) filename = _tmpDirectory + "/data.h5" # init state - path = silx.io.url.DataUrl( + url = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/group/image" - ).path() - dialog.selectUrl(path) + ) + dialog.selectUrl(url.path()) self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), path) + self.assertSameUrls(urlLineEdit.text(), url) self.assertTrue(button.isEnabled()) # test self.mouseClick(button, qt.Qt.LeftButton) self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/" - ).path() - self.assertSamePath(url.text(), path) + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/") + self.assertSameUrls(urlLineEdit.text(), url) # self.assertFalse(button.isEnabled()) def testClickOnBackToDirectoryTool(self): @@ -334,24 +341,24 @@ def testClickOnBackToDirectoryTool(self): dialog.show() self.qWaitForWindowExposed(dialog) - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] + urlLineEdit = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] action = testutils.findChildren(dialog, qt.QAction, name="toDirectoryAction")[0] button = testutils.getQToolButtonFromAction(action) filename = _tmpDirectory + "/data.h5" # init state - path = silx.io.url.DataUrl(file_path=filename, data_path="/group/image").path() - dialog.selectUrl(path) + url = silx.io.url.DataUrl(file_path=filename, data_path="/group/image") + dialog.selectUrl(url.path()) self.qWaitForPendingActions(dialog) - path = silx.io.url.DataUrl( + url = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/group/image" - ).path() - self.assertSamePath(url.text(), path) + ) + self.assertSameUrls(urlLineEdit.text(), url) self.assertTrue(button.isEnabled()) # test self.mouseClick(button, qt.Qt.LeftButton) self.qWaitForPendingActions(dialog) - self.assertSamePath(url.text(), _tmpDirectory) + self.assertSamePath(urlLineEdit.text(), _tmpDirectory) self.assertFalse(button.isEnabled()) # FIXME: There is an unreleased qt.QWidget without nameObject @@ -363,7 +370,7 @@ def testClickOnHistoryTools(self): dialog.show() self.qWaitForWindowExposed(dialog) - url = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] + urlLineEdit = testutils.findChildren(dialog, qt.QLineEdit, name="url")[0] forwardAction = testutils.findChildren( dialog, qt.QAction, name="forwardAction" )[0] @@ -378,15 +385,13 @@ def testClickOnHistoryTools(self): # Then we feed the history using selectPath dialog.selectUrl(filename) self.qWaitForPendingActions(dialog) - path2 = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/" - ).path() - dialog.selectUrl(path2) + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/") + dialog.selectUrl(url.path()) self.qWaitForPendingActions(dialog) - path3 = silx.io.url.DataUrl( + url2 = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/group" - ).path() - dialog.selectUrl(path3) + ) + dialog.selectUrl(url2.path()) self.qWaitForPendingActions(dialog) self.assertFalse(forwardAction.isEnabled()) self.assertTrue(backwardAction.isEnabled()) @@ -396,14 +401,14 @@ def testClickOnHistoryTools(self): self.qWaitForPendingActions(dialog) self.assertTrue(forwardAction.isEnabled()) self.assertTrue(backwardAction.isEnabled()) - self.assertSamePath(url.text(), path2) + self.assertSameUrls(urlLineEdit.text(), url) button = testutils.getQToolButtonFromAction(forwardAction) self.mouseClick(button, qt.Qt.LeftButton) self.qWaitForPendingActions(dialog) self.assertFalse(forwardAction.isEnabled()) self.assertTrue(backwardAction.isEnabled()) - self.assertSamePath(url.text(), path3) + self.assertSameUrls(urlLineEdit.text(), url2) def testSelectImageFromEdf(self): dialog = self.createDialog() @@ -420,7 +425,7 @@ def testSelectImageFromEdf(self): dialog.selectUrl(url.path()) self.assertEqual(dialog._selectedData().shape, (100, 100)) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), url.path()) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectImage(self): dialog = self.createDialog() @@ -429,14 +434,12 @@ def testSelectImage(self): # init state filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/image" - ).path() - dialog.selectUrl(path) + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/image") + dialog.selectUrl(url.path()) # test self.assertEqual(dialog._selectedData().shape, (100, 100)) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectScalar(self): dialog = self.createDialog() @@ -445,14 +448,14 @@ def testSelectScalar(self): # init state filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( + url = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/scalar" - ).path() - dialog.selectUrl(path) + ) + dialog.selectUrl(url.path()) # test self.assertEqual(dialog._selectedData()[()], 10) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectGroup(self): dialog = self.createDialog() @@ -496,9 +499,7 @@ def testSelectH5_Activate(self): self.qWaitForPendingActions(dialog) browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/" - ).path() + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/") index = browser.rootIndex().model().index(filename) # click browser.selectIndex(index) @@ -506,7 +507,7 @@ def testSelectH5_Activate(self): browser.activated.emit(index) self.qWaitForPendingActions(dialog) # test - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectBadFileFormat_Activate(self): dialog = self.createDialog() diff --git a/src/silx/gui/dialog/test/test_imagefiledialog.py b/src/silx/gui/dialog/test/test_imagefiledialog.py index 9704b245a4..44a3929b8e 100644 --- a/src/silx/gui/dialog/test/test_imagefiledialog.py +++ b/src/silx/gui/dialog/test/test_imagefiledialog.py @@ -23,6 +23,8 @@ # ###########################################################################*/ """Test for silx.gui.hdf5 module""" +from __future__ import annotations + __authors__ = ["V. Valls"] __license__ = "MIT" __date__ = "08/03/2019" @@ -130,18 +132,34 @@ def qWaitForPendingActions(self, dialog): raise RuntimeError("Still have pending actions") def assertSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ != path2_: - # Use the unittest API to log and display error - self.assertEqual(path1, path2) + self.assertEqual( + os.path.normcase(os.path.realpath(path1)), + os.path.normcase(os.path.realpath(path2)), + msg=f"Paths differs: {path1} != {path2}", + ) def assertNotSamePath(self, path1, path2): - path1_ = os.path.normcase(path1) - path2_ = os.path.normcase(path2) - if path1_ == path2_: - # Use the unittest API to log and display error - self.assertNotEqual(path1, path2) + self.assertNotEqual( + os.path.normcase(os.path.realpath(path1)), + os.path.normcase(os.path.realpath(path2)), + msg=f"Paths are equals: {path1} == {path2}", + ) + + def assertSameUrls( + self, + url1: silx.io.url.DataUrl | str, + url2: silx.io.url.DataUrl | str, + ): + """Check that both DataUrls are equivalent""" + if isinstance(url1, str): + url1 = silx.io.url.DataUrl(url1) + if isinstance(url2, str): + url2 = silx.io.url.DataUrl(url2) + + self.assertEqual(url1.scheme(), url2.scheme()) + self.assertSamePath(url1.file_path(), url2.file_path()) + self.assertEqual(url1.data_path(), url2.data_path()) + self.assertEqual(url1.data_slice(), url2.data_slice()) class TestImageFileDialogInteraction(testutils.TestCaseQt, _UtilsMixin): @@ -395,12 +413,11 @@ def testSelectImageFromEdf(self): # init state filename = _tmpDirectory + "/singleimage.edf" - path = filename - dialog.selectUrl(path) + dialog.selectUrl(filename) self.assertEqual(dialog.selectedImage().shape, (100, 100)) self.assertSamePath(dialog.selectedFile(), filename) - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() - self.assertSamePath(dialog.selectedUrl(), path) + url = silx.io.url.DataUrl(scheme="fabio", file_path=filename) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectImageFromEdf_Activate(self): dialog = self.createDialog() @@ -412,7 +429,7 @@ def testSelectImageFromEdf_Activate(self): self.qWaitForPendingActions(dialog) browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] filename = _tmpDirectory + "/singleimage.edf" - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() + url = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() index = browser.rootIndex().model().index(filename) # click browser.selectIndex(index) @@ -422,7 +439,7 @@ def testSelectImageFromEdf_Activate(self): # test self.assertEqual(dialog.selectedImage().shape, (100, 100)) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectFrameFromEdf(self): dialog = self.createDialog() @@ -431,16 +448,14 @@ def testSelectFrameFromEdf(self): # init state filename = _tmpDirectory + "/multiframe.edf" - path = silx.io.url.DataUrl( - scheme="fabio", file_path=filename, data_slice=(1,) - ).path() - dialog.selectUrl(path) + url = silx.io.url.DataUrl(scheme="fabio", file_path=filename, data_slice=(1,)) + dialog.selectUrl(url.path()) # test image = dialog.selectedImage() self.assertEqual(image.shape, (100, 100)) self.assertEqual(image[0, 0], 1) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectImageFromMsk(self): dialog = self.createDialog() @@ -449,12 +464,12 @@ def testSelectImageFromMsk(self): # init state filename = _tmpDirectory + "/singleimage.msk" - path = silx.io.url.DataUrl(scheme="fabio", file_path=filename).path() - dialog.selectUrl(path) + url = silx.io.url.DataUrl(scheme="fabio", file_path=filename) + dialog.selectUrl(url.path()) # test self.assertEqual(dialog.selectedImage().shape, (100, 100)) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectImageFromH5(self): dialog = self.createDialog() @@ -463,14 +478,12 @@ def testSelectImageFromH5(self): # init state filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/image" - ).path() - dialog.selectUrl(path) + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/image") + dialog.selectUrl(url.path()) # test self.assertEqual(dialog.selectedImage().shape, (100, 100)) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectH5_Activate(self): dialog = self.createDialog() @@ -482,9 +495,7 @@ def testSelectH5_Activate(self): self.qWaitForPendingActions(dialog) browser = testutils.findChildren(dialog, qt.QWidget, name="browser")[0] filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( - scheme="silx", file_path=filename, data_path="/" - ).path() + url = silx.io.url.DataUrl(scheme="silx", file_path=filename, data_path="/") index = browser.rootIndex().model().index(filename) # click browser.selectIndex(index) @@ -492,7 +503,7 @@ def testSelectH5_Activate(self): browser.activated.emit(index) self.qWaitForPendingActions(dialog) # test - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectFrameFromH5(self): dialog = self.createDialog() @@ -501,15 +512,15 @@ def testSelectFrameFromH5(self): # init state filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( + url = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/cube", data_slice=(1,) - ).path() - dialog.selectUrl(path) + ) + dialog.selectUrl(url.path()) # test self.assertEqual(dialog.selectedImage().shape, (100, 100)) self.assertEqual(dialog.selectedImage()[0, 0], 1) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectSingleFrameFromH5(self): dialog = self.createDialog() @@ -518,18 +529,18 @@ def testSelectSingleFrameFromH5(self): # init state filename = _tmpDirectory + "/data.h5" - path = silx.io.url.DataUrl( + url = silx.io.url.DataUrl( scheme="silx", file_path=filename, data_path="/single_frame", data_slice=(0,), - ).path() - dialog.selectUrl(path) + ) + dialog.selectUrl(url.path()) # test self.assertEqual(dialog.selectedImage().shape, (100, 100)) self.assertEqual(dialog.selectedImage()[0, 0], 5) self.assertSamePath(dialog.selectedFile(), filename) - self.assertSamePath(dialog.selectedUrl(), path) + self.assertSameUrls(dialog.selectedUrl(), url) def testSelectBadFileFormat_Activate(self): dialog = self.createDialog() @@ -546,7 +557,7 @@ def testSelectBadFileFormat_Activate(self): browser.activated.emit(index) self.qWaitForPendingActions(dialog) # test - self.assertSamePath(dialog.selectedUrl(), filename) + self.assertSameUrls(dialog.selectedUrl(), filename) def _countSelectableItems(self, model, rootIndex): selectable = 0 diff --git a/src/silx/gui/fit/test/testBackgroundWidget.py b/src/silx/gui/fit/test/test_backgroundwidget.py similarity index 93% rename from src/silx/gui/fit/test/testBackgroundWidget.py rename to src/silx/gui/fit/test/test_backgroundwidget.py index 73e3fbab07..2001ad75b0 100644 --- a/src/silx/gui/fit/test/testBackgroundWidget.py +++ b/src/silx/gui/fit/test/test_backgroundwidget.py @@ -21,6 +21,7 @@ # THE SOFTWARE. # # ###########################################################################*/ +from silx.gui import qt from silx.gui.utils.testutils import TestCaseQt from .. import BackgroundWidget @@ -38,12 +39,16 @@ def setUp(self): self.qWaitForWindowExposed(self.bgdialog) def tearDown(self): + self.bgdialog.setAttribute(qt.Qt.WA_DeleteOnClose) + self.bgdialog.close() del self.bgdialog super(TestBackgroundWidget, self).tearDown() def testShow(self): self.bgdialog.show() + self.qWaitForWindowExposed(self.bgdialog) self.bgdialog.hide() + self.qapp.processEvents() def testAccept(self): self.bgdialog.accept() diff --git a/src/silx/gui/fit/test/testFitConfig.py b/src/silx/gui/fit/test/test_fitconfig.py similarity index 100% rename from src/silx/gui/fit/test/testFitConfig.py rename to src/silx/gui/fit/test/test_fitconfig.py diff --git a/src/silx/gui/fit/test/testFitWidget.py b/src/silx/gui/fit/test/test_fitwidget.py similarity index 100% rename from src/silx/gui/fit/test/testFitWidget.py rename to src/silx/gui/fit/test/test_fitwidget.py diff --git a/src/silx/gui/hdf5/test/test_hdf5.py b/src/silx/gui/hdf5/test/test_hdf5.py index 2bb25304f6..a3b8f43a35 100755 --- a/src/silx/gui/hdf5/test/test_hdf5.py +++ b/src/silx/gui/hdf5/test/test_hdf5.py @@ -73,10 +73,10 @@ def setUp(self): super(TestHdf5TreeModel, self).setUp() def waitForPendingOperations(self, model): - for _ in range(10): + for _ in range(20): if not model.hasPendingOperations(): break - self.qWait(10) + self.qWait(200) else: raise RuntimeError("Still waiting for a pending operation") @@ -383,7 +383,9 @@ def testDropLastAsFirst(self): h5_1 = commonh5.File("/foo/bar/1.mock", "w") h5_2 = commonh5.File("/foo/bar/2.mock", "w") model.insertH5pyObject(h5_1) + self.qapp.processEvents() model.insertH5pyObject(h5_2) + self.qapp.processEvents() self.assertEqual(self.getItemName(model, 0), "1.mock") self.assertEqual(self.getItemName(model, 1), "2.mock") index = model.index(1, 0, qt.QModelIndex()) @@ -397,7 +399,9 @@ def testDropFirstAsLast(self): h5_1 = commonh5.File("/foo/bar/1.mock", "w") h5_2 = commonh5.File("/foo/bar/2.mock", "w") model.insertH5pyObject(h5_1) + self.qapp.processEvents() model.insertH5pyObject(h5_2) + self.qapp.processEvents() self.assertEqual(self.getItemName(model, 0), "1.mock") self.assertEqual(self.getItemName(model, 1), "2.mock") index = model.index(0, 0, qt.QModelIndex()) @@ -440,10 +444,10 @@ def tearDown(self): TestCaseQt.tearDown(self) def waitForPendingOperations(self, model): - for _ in range(10): + for _ in range(20): if not model.hasPendingOperations(): break - self.qWait(10) + self.qWait(200) else: raise RuntimeError("Still waiting for a pending operation") diff --git a/src/silx/gui/plot/MaskToolsWidget.py b/src/silx/gui/plot/MaskToolsWidget.py index 11bfc0629c..da8a4d17ee 100644 --- a/src/silx/gui/plot/MaskToolsWidget.py +++ b/src/silx/gui/plot/MaskToolsWidget.py @@ -396,7 +396,7 @@ def showEvent(self, event): self.plot.sigActiveImageChanged.disconnect( self._activeImageChangedAfterCare ) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): pass # Sync with current active image @@ -406,14 +406,14 @@ def showEvent(self, event): def hideEvent(self, event): try: self.plot.sigActiveImageChanged.disconnect(self._activeImageChanged) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): pass image = self.getMaskedItem() if image is not None: try: image.sigItemChanged.disconnect(self.__imageChanged) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): pass # TODO should not happen if self.isMaskInteractionActivated(): @@ -503,7 +503,7 @@ def _setMaskedImage(self, image): # Disconnect from previous image try: previous.sigItemChanged.disconnect(self.__imageChanged) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): pass # TODO fixme should not happen # Set the image diff --git a/src/silx/gui/plot/ScatterMaskToolsWidget.py b/src/silx/gui/plot/ScatterMaskToolsWidget.py index bf34220061..f036d2d808 100644 --- a/src/silx/gui/plot/ScatterMaskToolsWidget.py +++ b/src/silx/gui/plot/ScatterMaskToolsWidget.py @@ -284,7 +284,7 @@ def showEvent(self, event): self.plot.sigActiveScatterChanged.disconnect( self._activeScatterChangedAfterCare ) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): pass self._activeScatterChanged(None, None) # Init mask + enable/disable widget self.plot.sigActiveScatterChanged.connect(self._activeScatterChanged) @@ -294,7 +294,7 @@ def hideEvent(self, event): # if the method is not connected this raises a TypeError and there is no way # to know the connected slots self.plot.sigActiveScatterChanged.disconnect(self._activeScatterChanged) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): _logger.info(sys.exc_info()[1]) if self.isMaskInteractionActivated(): diff --git a/src/silx/gui/plot/test/testAlphaSlider.py b/src/silx/gui/plot/test/test_alphaslider.py similarity index 100% rename from src/silx/gui/plot/test/testAlphaSlider.py rename to src/silx/gui/plot/test/test_alphaslider.py diff --git a/src/silx/gui/plot/test/testAxis.py b/src/silx/gui/plot/test/test_axis.py similarity index 100% rename from src/silx/gui/plot/test/testAxis.py rename to src/silx/gui/plot/test/test_axis.py diff --git a/src/silx/gui/plot/test/testColorBar.py b/src/silx/gui/plot/test/test_colorbar.py similarity index 100% rename from src/silx/gui/plot/test/testColorBar.py rename to src/silx/gui/plot/test/test_colorbar.py diff --git a/src/silx/gui/plot/test/testCompareImages.py b/src/silx/gui/plot/test/test_compareimages.py similarity index 100% rename from src/silx/gui/plot/test/testCompareImages.py rename to src/silx/gui/plot/test/test_compareimages.py diff --git a/src/silx/gui/plot/test/testComplexImageView.py b/src/silx/gui/plot/test/test_compleximageview.py similarity index 100% rename from src/silx/gui/plot/test/testComplexImageView.py rename to src/silx/gui/plot/test/test_compleximageview.py diff --git a/src/silx/gui/plot/test/testCurvesROIWidget.py b/src/silx/gui/plot/test/test_curvesroiwidget.py similarity index 100% rename from src/silx/gui/plot/test/testCurvesROIWidget.py rename to src/silx/gui/plot/test/test_curvesroiwidget.py diff --git a/src/silx/gui/plot/test/testImageStack.py b/src/silx/gui/plot/test/test_imagestack.py similarity index 100% rename from src/silx/gui/plot/test/testImageStack.py rename to src/silx/gui/plot/test/test_imagestack.py diff --git a/src/silx/gui/plot/test/testImageView.py b/src/silx/gui/plot/test/test_imageview.py similarity index 100% rename from src/silx/gui/plot/test/testImageView.py rename to src/silx/gui/plot/test/test_imageview.py diff --git a/src/silx/gui/plot/test/testInteraction.py b/src/silx/gui/plot/test/test_interaction.py similarity index 100% rename from src/silx/gui/plot/test/testInteraction.py rename to src/silx/gui/plot/test/test_interaction.py diff --git a/src/silx/gui/plot/test/testItem.py b/src/silx/gui/plot/test/test_item.py similarity index 100% rename from src/silx/gui/plot/test/testItem.py rename to src/silx/gui/plot/test/test_item.py diff --git a/src/silx/gui/plot/test/testLegendSelector.py b/src/silx/gui/plot/test/test_legendselector.py similarity index 100% rename from src/silx/gui/plot/test/testLegendSelector.py rename to src/silx/gui/plot/test/test_legendselector.py diff --git a/src/silx/gui/plot/test/testLimitConstraints.py b/src/silx/gui/plot/test/test_limitconstraints.py similarity index 100% rename from src/silx/gui/plot/test/testLimitConstraints.py rename to src/silx/gui/plot/test/test_limitconstraints.py diff --git a/src/silx/gui/plot/test/testMaskToolsWidget.py b/src/silx/gui/plot/test/test_masktoolswidget.py similarity index 99% rename from src/silx/gui/plot/test/testMaskToolsWidget.py rename to src/silx/gui/plot/test/test_masktoolswidget.py index 1428687311..7f97739d55 100644 --- a/src/silx/gui/plot/test/testMaskToolsWidget.py +++ b/src/silx/gui/plot/test/test_masktoolswidget.py @@ -57,6 +57,8 @@ def setUp(self): self.maskWidget = self.widget.widget() def tearDown(self): + self.widget.setAttribute(qt.Qt.WA_DeleteOnClose) + self.widget.close() del self.maskWidget del self.widget super(TestMaskToolsWidget, self).tearDown() diff --git a/src/silx/gui/plot/test/testPixelIntensityHistoAction.py b/src/silx/gui/plot/test/test_pixelintensityhistoaction.py similarity index 100% rename from src/silx/gui/plot/test/testPixelIntensityHistoAction.py rename to src/silx/gui/plot/test/test_pixelintensityhistoaction.py diff --git a/src/silx/gui/plot/test/testPlotActions.py b/src/silx/gui/plot/test/test_plotactions.py similarity index 100% rename from src/silx/gui/plot/test/testPlotActions.py rename to src/silx/gui/plot/test/test_plotactions.py diff --git a/src/silx/gui/plot/test/testPlotInteraction.py b/src/silx/gui/plot/test/test_plotinteraction.py similarity index 100% rename from src/silx/gui/plot/test/testPlotInteraction.py rename to src/silx/gui/plot/test/test_plotinteraction.py diff --git a/src/silx/gui/plot/test/testPlotWidget.py b/src/silx/gui/plot/test/test_plotwidget.py similarity index 99% rename from src/silx/gui/plot/test/testPlotWidget.py rename to src/silx/gui/plot/test/test_plotwidget.py index 9a8b277991..0e273c11ef 100755 --- a/src/silx/gui/plot/test/testPlotWidget.py +++ b/src/silx/gui/plot/test/test_plotwidget.py @@ -1,6 +1,6 @@ # /*########################################################################## # -# Copyright (c) 2016-2023 European Synchrotron Radiation Facility +# Copyright (c) 2016-2024 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -2057,6 +2057,8 @@ class TestSpecial_ExplicitMplBackend(TestSpecialBackend): @pytest.mark.filterwarnings("ignore:All-NaN slice encountered:RuntimeWarning") +@pytest.mark.filterwarnings("ignore:.* converting a masked element to nan.:UserWarning") +@pytest.mark.filterwarnings("ignore:All-NaN axis encountered:RuntimeWarning") @pytest.mark.parametrize("plotWidget", ("mpl", "gl"), indirect=True) @pytest.mark.parametrize( "xerror,yerror", diff --git a/src/silx/gui/plot/test/testPlotWidgetActiveItem.py b/src/silx/gui/plot/test/test_plotwidgetactiveitem.py similarity index 100% rename from src/silx/gui/plot/test/testPlotWidgetActiveItem.py rename to src/silx/gui/plot/test/test_plotwidgetactiveitem.py diff --git a/src/silx/gui/plot/test/testPlotWidgetDataMargins.py b/src/silx/gui/plot/test/test_plotwidgetdatamargins.py similarity index 100% rename from src/silx/gui/plot/test/testPlotWidgetDataMargins.py rename to src/silx/gui/plot/test/test_plotwidgetdatamargins.py diff --git a/src/silx/gui/plot/test/testPlotWidgetNoBackend.py b/src/silx/gui/plot/test/test_plotwidgetnobackend.py similarity index 100% rename from src/silx/gui/plot/test/testPlotWidgetNoBackend.py rename to src/silx/gui/plot/test/test_plotwidgetnobackend.py diff --git a/src/silx/gui/plot/test/testPlotWindow.py b/src/silx/gui/plot/test/test_plotwindow.py similarity index 100% rename from src/silx/gui/plot/test/testPlotWindow.py rename to src/silx/gui/plot/test/test_plotwindow.py diff --git a/src/silx/gui/plot/test/testRoiStatsWidget.py b/src/silx/gui/plot/test/test_roistatswidget.py similarity index 100% rename from src/silx/gui/plot/test/testRoiStatsWidget.py rename to src/silx/gui/plot/test/test_roistatswidget.py diff --git a/src/silx/gui/plot/test/testSaveAction.py b/src/silx/gui/plot/test/test_saveaction.py similarity index 100% rename from src/silx/gui/plot/test/testSaveAction.py rename to src/silx/gui/plot/test/test_saveaction.py diff --git a/src/silx/gui/plot/test/testScatterMaskToolsWidget.py b/src/silx/gui/plot/test/test_scattermasktoolswidget.py similarity index 99% rename from src/silx/gui/plot/test/testScatterMaskToolsWidget.py rename to src/silx/gui/plot/test/test_scattermasktoolswidget.py index 5dc14e1a7b..0ba3d5955b 100644 --- a/src/silx/gui/plot/test/testScatterMaskToolsWidget.py +++ b/src/silx/gui/plot/test/test_scattermasktoolswidget.py @@ -60,6 +60,8 @@ def setUp(self): self.maskWidget = self.widget.widget() def tearDown(self): + self.widget.setAttribute(qt.Qt.WA_DeleteOnClose) + self.widget.close() del self.maskWidget del self.widget super(TestScatterMaskToolsWidget, self).tearDown() diff --git a/src/silx/gui/plot/test/testScatterView.py b/src/silx/gui/plot/test/test_scatterview.py similarity index 100% rename from src/silx/gui/plot/test/testScatterView.py rename to src/silx/gui/plot/test/test_scatterview.py diff --git a/src/silx/gui/plot/test/testStackView.py b/src/silx/gui/plot/test/test_stackview.py similarity index 100% rename from src/silx/gui/plot/test/testStackView.py rename to src/silx/gui/plot/test/test_stackview.py diff --git a/src/silx/gui/plot/test/testStats.py b/src/silx/gui/plot/test/test_stats.py similarity index 100% rename from src/silx/gui/plot/test/testStats.py rename to src/silx/gui/plot/test/test_stats.py diff --git a/src/silx/gui/plot/test/testUtilsAxis.py b/src/silx/gui/plot/test/test_utilsaxis.py similarity index 100% rename from src/silx/gui/plot/test/testUtilsAxis.py rename to src/silx/gui/plot/test/test_utilsaxis.py diff --git a/src/silx/gui/plot/tools/profile/editors.py b/src/silx/gui/plot/tools/profile/editors.py index d53f7755e4..29388a57d8 100644 --- a/src/silx/gui/plot/tools/profile/editors.py +++ b/src/silx/gui/plot/tools/profile/editors.py @@ -249,7 +249,7 @@ def __setEditor(self, widget, editor): if previousEditor is not None: try: previousEditor.sigDataCommited.disconnect(self._editorDataCommited) - except (RuntimeError, TypeError): + except (RuntimeError, TypeError, SystemError): pass layout.removeWidget(previousEditor) previousEditor.deleteLater() diff --git a/src/silx/gui/plot/tools/test/testCurveLegendsWidget.py b/src/silx/gui/plot/tools/test/test_curvelegendswidget.py similarity index 100% rename from src/silx/gui/plot/tools/test/testCurveLegendsWidget.py rename to src/silx/gui/plot/tools/test/test_curvelegendswidget.py diff --git a/src/silx/gui/plot/tools/test/testProfile.py b/src/silx/gui/plot/tools/test/test_profile.py similarity index 97% rename from src/silx/gui/plot/tools/test/testProfile.py rename to src/silx/gui/plot/tools/test/test_profile.py index 61b95a6433..50e95f147d 100644 --- a/src/silx/gui/plot/tools/test/testProfile.py +++ b/src/silx/gui/plot/tools/test/test_profile.py @@ -63,9 +63,10 @@ def defaultPlot(self): self.qWaitForWindowExposed(widget) yield widget finally: + widget.setAttribute(qt.Qt.WA_DeleteOnClose) widget.close() - widget = None - self.qWait() + del widget + self.qapp.processEvents() @contextlib.contextmanager def imagePlot(self): @@ -77,9 +78,10 @@ def imagePlot(self): self.qWaitForWindowExposed(widget) yield widget finally: + widget.setAttribute(qt.Qt.WA_DeleteOnClose) widget.close() - widget = None - self.qWait() + del widget + self.qapp.processEvents() @contextlib.contextmanager def scatterPlot(self): @@ -101,9 +103,10 @@ def scatterPlot(self): self.qWaitForWindowExposed(widget) yield widget.getPlotWidget() finally: + widget.setAttribute(qt.Qt.WA_DeleteOnClose) widget.close() - widget = None - self.qWait() + del widget + self.qapp.processEvents() @contextlib.contextmanager def stackPlot(self): @@ -117,9 +120,10 @@ def stackPlot(self): self.qWaitForWindowExposed(widget) yield widget.getPlotWidget() finally: + widget.setAttribute(qt.Qt.WA_DeleteOnClose) widget.close() - widget = None - self.qWait() + del widget + self.qapp.processEvents() def waitPendingOperations(self, proflie): for _ in range(10): diff --git a/src/silx/gui/plot/tools/test/testRoiCore.py b/src/silx/gui/plot/tools/test/test_roicore.py similarity index 100% rename from src/silx/gui/plot/tools/test/testRoiCore.py rename to src/silx/gui/plot/tools/test/test_roicore.py diff --git a/src/silx/gui/plot/tools/test/testRoiItems.py b/src/silx/gui/plot/tools/test/test_roiitems.py similarity index 100% rename from src/silx/gui/plot/tools/test/testRoiItems.py rename to src/silx/gui/plot/tools/test/test_roiitems.py diff --git a/src/silx/gui/plot/tools/test/testScatterProfileToolBar.py b/src/silx/gui/plot/tools/test/test_scatterprofiletoolbar.py similarity index 100% rename from src/silx/gui/plot/tools/test/testScatterProfileToolBar.py rename to src/silx/gui/plot/tools/test/test_scatterprofiletoolbar.py diff --git a/src/silx/gui/plot/tools/test/testTools.py b/src/silx/gui/plot/tools/test/test_tools.py similarity index 100% rename from src/silx/gui/plot/tools/test/testTools.py rename to src/silx/gui/plot/tools/test/test_tools.py diff --git a/src/silx/gui/plot3d/SFViewParamTree.py b/src/silx/gui/plot3d/SFViewParamTree.py index 6eea5aeef1..f83fb3c89e 100644 --- a/src/silx/gui/plot3d/SFViewParamTree.py +++ b/src/silx/gui/plot3d/SFViewParamTree.py @@ -159,7 +159,7 @@ def _disconnectSignals(self): for signal, slot in self.__slots: try: signal.disconnect(slot) - except TypeError: + except (RuntimeError, TypeError, SystemError): pass def _enableRow(self, enable): @@ -1692,7 +1692,7 @@ def setModel(self, model): self.__openPersistentEditors(qt.QModelIndex(), False) try: prevModel.rowsRemoved.disconnect(self.rowsRemoved) - except TypeError: + except (RuntimeError, TypeError, SystemError): pass super(TreeView, self).setModel(model) diff --git a/src/silx/gui/plot3d/test/testGL.py b/src/silx/gui/plot3d/test/test_gl.py similarity index 100% rename from src/silx/gui/plot3d/test/testGL.py rename to src/silx/gui/plot3d/test/test_gl.py diff --git a/src/silx/gui/plot3d/test/testScalarFieldView.py b/src/silx/gui/plot3d/test/test_scalarfieldview.py similarity index 100% rename from src/silx/gui/plot3d/test/testScalarFieldView.py rename to src/silx/gui/plot3d/test/test_scalarfieldview.py diff --git a/src/silx/gui/plot3d/test/testSceneWidget.py b/src/silx/gui/plot3d/test/test_scenewidget.py similarity index 100% rename from src/silx/gui/plot3d/test/testSceneWidget.py rename to src/silx/gui/plot3d/test/test_scenewidget.py diff --git a/src/silx/gui/plot3d/test/testSceneWidgetPicking.py b/src/silx/gui/plot3d/test/test_scenewidgetpicking.py similarity index 100% rename from src/silx/gui/plot3d/test/testSceneWidgetPicking.py rename to src/silx/gui/plot3d/test/test_scenewidgetpicking.py diff --git a/src/silx/gui/plot3d/test/testSceneWindow.py b/src/silx/gui/plot3d/test/test_scenewindow.py similarity index 100% rename from src/silx/gui/plot3d/test/testSceneWindow.py rename to src/silx/gui/plot3d/test/test_scenewindow.py diff --git a/src/silx/gui/plot3d/test/testStatsWidget.py b/src/silx/gui/plot3d/test/test_statswidget.py similarity index 100% rename from src/silx/gui/plot3d/test/testStatsWidget.py rename to src/silx/gui/plot3d/test/test_statswidget.py diff --git a/src/silx/gui/plot3d/tools/test/testPositionInfoWidget.py b/src/silx/gui/plot3d/tools/test/test_positioninfowidget.py similarity index 100% rename from src/silx/gui/plot3d/tools/test/testPositionInfoWidget.py rename to src/silx/gui/plot3d/tools/test/test_positioninfowidget.py diff --git a/src/silx/gui/utils/test/test.py b/src/silx/gui/utils/test/test_blocksignals.py similarity index 100% rename from src/silx/gui/utils/test/test.py rename to src/silx/gui/utils/test/test_blocksignals.py diff --git a/src/silx/gui/utils/testutils.py b/src/silx/gui/utils/testutils.py index 76d0b9b4ad..f97e0fab7b 100644 --- a/src/silx/gui/utils/testutils.py +++ b/src/silx/gui/utils/testutils.py @@ -323,17 +323,7 @@ def qWait(cls, ms=None): """ if ms is None: ms = cls.DEFAULT_TIMEOUT_WAIT - - if qt.BINDING == "PySide6": - # PySide has no qWait, provide a replacement - timeout = int(ms) - endTimeMS = int(time.time() * 1000) + timeout - qapp = qt.QApplication.instance() - while timeout > 0: - qapp.processEvents(qt.QEventLoop.AllEvents, timeout) - timeout = endTimeMS - int(time.time() * 1000) - else: - QTest.qWait(int(ms) + cls.TIMEOUT_WAIT) + QTest.qWait(int(ms) + cls.TIMEOUT_WAIT) def qWaitForWindowExposed(self, window, timeout=None): """Waits until the window is shown in the screen. diff --git a/src/silx/io/commonh5.py b/src/silx/io/commonh5.py index f04fe8d494..b0d44400a9 100644 --- a/src/silx/io/commonh5.py +++ b/src/silx/io/commonh5.py @@ -31,6 +31,8 @@ import numpy from . import utils +from .._utils import NP_OPTIONAL_COPY + __authors__ = ["V. Valls", "P. Knobel"] __license__ = "MIT" @@ -347,12 +349,16 @@ def external(self): :rtype: list or None""" return None - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): # Special case for (0,)*-shape datasets if numpy.prod(self.shape) == 0: return self[()] else: - return numpy.array(self[...], dtype=self.dtype if dtype is None else dtype) + return numpy.array( + self[...], + dtype=self.dtype if dtype is None else dtype, + copy=NP_OPTIONAL_COPY if copy is None else copy, + ) def __iter__(self): """Iterate over the first axis. TypeError if scalar.""" diff --git a/src/silx/io/test/test_h5py_utils.py b/src/silx/io/test/test_h5py_utils.py index 0d10a785a3..276814d6bb 100644 --- a/src/silx/io/test/test_h5py_utils.py +++ b/src/silx/io/test/test_h5py_utils.py @@ -1,5 +1,5 @@ # /*########################################################################## -# Copyright (C) 2016-2017 European Synchrotron Radiation Facility +# Copyright (C) 2016-2024 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -76,10 +76,16 @@ def _subprocess_context(contextmgr, *args, **kw): def _open_context(filename, **kw): try: print(os.getpid(), "OPEN", filename, kw) + swmr_writer = kw.get("mode", "r") != "r" and kw.get("swmr", False) + if swmr_writer: + kw.pop("swmr") # Avoid a warning with h5py_utils.File(filename, **kw) as f: if kw.get("mode") == "w": f["check"] = True f.flush() + if swmr_writer: + f.swmr_mode = True # SWMR mode must be enabled afterwards + f.flush() yield f except Exception: print(" ", os.getpid(), "FAILED", filename, kw) diff --git a/src/silx/test/__init__.py b/src/silx/test/__init__.py index 0f3d5ded04..98faae32b2 100644 --- a/src/silx/test/__init__.py +++ b/src/silx/test/__init__.py @@ -24,6 +24,10 @@ """This package provides test of the root modules """ +from __future__ import annotations + +from collections.abc import Sequence +import importlib import logging import subprocess import sys @@ -38,26 +42,43 @@ raise -def run_tests(module: str = "silx", verbosity: int = 0, args=()): +import silx + + +def run_tests( + module: str | None = "silx", + verbosity: int = 0, + args: Sequence[str] = (), +): """Run tests in a subprocess :param module: Name of the silx module to test (default: 'silx') :param verbosity: Requested level of verbosity :param args: List of extra arguments to pass to `pytest` """ - return subprocess.run( - [ - sys.executable, - "-m", - "pytest", - "--pyargs", - module, - "--verbosity", - str(verbosity), - '-o python_files=["test/test*.py","test/Test*.py"]', - '-o python_classes=["Test"]', - '-o python_functions=["test"]', - ] - + list(args), - check=False, - ).returncode + cmd = [ + sys.executable, + "-m", + "pytest", + f"--rootdir={silx.__path__[0]}", + "--verbosity", + str(verbosity), + # Handle warning as errors unless explicitly skipped + "-Werror", + "-Wignore:tostring() is deprecated. Use tobytes() instead.:DeprecationWarning:OpenGL.GL.VERSION.GL_2_0", + "-Wignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", + "-Wignore:Unable to import recommended hash 'siphash24.siphash13', falling back to 'hashlib.sha256'. Run 'python3 -m pip install siphash24' to install the recommended hash.:UserWarning:pytools.persistent_dict", + # Remove __array__ ignore once h5py v3.12 is released + "-Wignore:__array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments.:DeprecationWarning", + ] + list(args) + + if module is not None: + # Retrieve folder for packages and file for modules + imported_module = importlib.import_module(module) + cmd.append( + imported_module.__path__[0] + if hasattr(imported_module, "__path__") + else imported_module.__file__ + ) + + return subprocess.run(cmd, check=False).returncode diff --git a/src/silx/utils/array_like.py b/src/silx/utils/array_like.py index b9b976bca8..cb303bae4e 100644 --- a/src/silx/utils/array_like.py +++ b/src/silx/utils/array_like.py @@ -1,6 +1,6 @@ # /*########################################################################## # -# Copyright (c) 2016-2021 European Synchrotron Radiation Facility +# Copyright (c) 2016-2024 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -49,6 +49,9 @@ import numpy import numbers +from .._utils import NP_OPTIONAL_COPY + + __authors__ = ["P. Knobel"] __license__ = "MIT" __date__ = "26/04/2017" @@ -276,13 +279,17 @@ def __sort_indices(self, indices): ) return sorted_indices - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): """Cast the images into a numpy array, and return it. If a transposition has been done on this images, return a transposed view of a numpy array.""" return numpy.transpose( - numpy.array(self.images, dtype=dtype), self.transposition + numpy.array( + self.images, + dtype=dtype, + copy=NP_OPTIONAL_COPY if copy is None else copy), + self.transposition, ) def __len__(self): @@ -543,13 +550,17 @@ def __getitem__(self, item): return numpy.transpose(output_data_not_transposed, axes=output_dimensions) - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): """Cast the dataset into a numpy array, and return it. If a transposition has been done on this dataset, return a transposed view of a numpy array.""" return numpy.transpose( - numpy.array(self.dataset, dtype=dtype), self.transposition + numpy.array( + self.dataset, + dtype=dtype, + copy=NP_OPTIONAL_COPY if copy is None else copy), + self.transposition, ) def __len__(self):