diff options
Diffstat (limited to 'src/silx/utils/test')
-rwxr-xr-x | src/silx/utils/test/__init__.py | 24 | ||||
-rw-r--r-- | src/silx/utils/test/test_array_like.py | 430 | ||||
-rw-r--r-- | src/silx/utils/test/test_debug.py | 88 | ||||
-rw-r--r-- | src/silx/utils/test/test_deprecation.py | 96 | ||||
-rw-r--r-- | src/silx/utils/test/test_enum.py | 85 | ||||
-rw-r--r-- | src/silx/utils/test/test_external_resources.py | 89 | ||||
-rw-r--r-- | src/silx/utils/test/test_launcher.py | 191 | ||||
-rw-r--r-- | src/silx/utils/test/test_launcher_command.py | 47 | ||||
-rw-r--r-- | src/silx/utils/test/test_number.py | 175 | ||||
-rw-r--r-- | src/silx/utils/test/test_proxy.py | 330 | ||||
-rw-r--r-- | src/silx/utils/test/test_retry.py | 169 | ||||
-rwxr-xr-x | src/silx/utils/test/test_testutils.py | 94 | ||||
-rw-r--r-- | src/silx/utils/test/test_weakref.py | 315 |
13 files changed, 2133 insertions, 0 deletions
diff --git a/src/silx/utils/test/__init__.py b/src/silx/utils/test/__init__.py new file mode 100755 index 0000000..14fd940 --- /dev/null +++ b/src/silx/utils/test/__init__.py @@ -0,0 +1,24 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2021 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ diff --git a/src/silx/utils/test/test_array_like.py b/src/silx/utils/test/test_array_like.py new file mode 100644 index 0000000..a0b4b7b --- /dev/null +++ b/src/silx/utils/test/test_array_like.py @@ -0,0 +1,430 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for array_like module""" + +__authors__ = ["P. Knobel"] +__license__ = "MIT" +__date__ = "09/01/2017" + +import h5py +import numpy +import os +import tempfile +import unittest + +from ..array_like import DatasetView, ListOfImages +from ..array_like import get_dtype, get_concatenated_dtype, get_shape,\ + is_array, is_nested_sequence, is_list_of_arrays + + +class TestTransposedDatasetView(unittest.TestCase): + + def setUp(self): + # dataset attributes + self.ndim = 3 + self.original_shape = (5, 10, 20) + self.size = 1 + for dim in self.original_shape: + self.size *= dim + + self.volume = numpy.arange(self.size).reshape(self.original_shape) + + self.tempdir = tempfile.mkdtemp() + self.h5_fname = os.path.join(self.tempdir, "tempfile.h5") + with h5py.File(self.h5_fname, "w") as f: + f["volume"] = self.volume + + self.h5f = h5py.File(self.h5_fname, "r") + + self.all_permutations = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), + (2, 0, 1), (2, 1, 0)] + + def tearDown(self): + self.h5f.close() + os.unlink(self.h5_fname) + os.rmdir(self.tempdir) + + def _testSize(self, obj): + """These assertions apply to all following test cases""" + self.assertEqual(obj.ndim, self.ndim) + self.assertEqual(obj.size, self.size) + size_from_shape = 1 + for dim in obj.shape: + size_from_shape *= dim + self.assertEqual(size_from_shape, self.size) + + for dim in self.original_shape: + self.assertIn(dim, obj.shape) + + def testNoTransposition(self): + """no transposition (transposition = (0, 1, 2))""" + a = DatasetView(self.h5f["volume"]) + + self.assertEqual(a.shape, self.original_shape) + self._testSize(a) + + # reversing the dimensions twice results in no change + rtrans = list(reversed(range(self.ndim))) + self.assertTrue(numpy.array_equal( + a, + a.transpose(rtrans).transpose(rtrans))) + + for i in range(a.shape[0]): + for j in range(a.shape[1]): + for k in range(a.shape[2]): + self.assertEqual(self.h5f["volume"][i, j, k], + a[i, j, k]) + + def _testTransposition(self, transposition): + """test transposed dataset + + :param tuple transposition: List of dimensions (0... n-1) sorted + in the desired order + """ + a = DatasetView(self.h5f["volume"], + transposition=transposition) + self._testSize(a) + + # sort shape of transposed object, to hopefully find the original shape + sorted_shape = tuple(dim_size for (_, dim_size) in + sorted(zip(transposition, a.shape))) + self.assertEqual(sorted_shape, self.original_shape) + + a_as_array = numpy.array(self.h5f["volume"]).transpose(transposition) + + # test the __array__ method + self.assertTrue(numpy.array_equal( + numpy.array(a), + a_as_array)) + + # test slicing + for selection in [(2, slice(None), slice(None)), + (slice(None), 1, slice(0, 8)), + (slice(0, 3), slice(None), 3), + (1, 3, slice(None)), + (slice(None), 2, 1), + (4, slice(1, 9, 2), 2)]: + self.assertIsInstance(a[selection], numpy.ndarray) + self.assertTrue(numpy.array_equal( + a[selection], + a_as_array[selection])) + + # test the DatasetView.__getitem__ for single values + # (step adjusted to test at least 3 indices in each dimension) + for i in range(0, a.shape[0], a.shape[0] // 3): + for j in range(0, a.shape[1], a.shape[1] // 3): + for k in range(0, a.shape[2], a.shape[2] // 3): + sorted_indices = tuple(idx for (_, idx) in + sorted(zip(transposition, [i, j, k]))) + viewed_value = a[i, j, k] + corresponding_original_value = self.h5f["volume"][sorted_indices] + self.assertEqual(viewed_value, + corresponding_original_value) + + # reversing the dimensions twice results in no change + rtrans = list(reversed(range(self.ndim))) + self.assertTrue(numpy.array_equal( + a, + a.transpose(rtrans).transpose(rtrans))) + + # test .T property + self.assertTrue(numpy.array_equal( + a.T, + a.transpose(rtrans))) + + def testTransposition012(self): + """transposition = (0, 1, 2) + (should be the same as testNoTransposition)""" + self._testTransposition((0, 1, 2)) + + def testTransposition021(self): + """transposition = (0, 2, 1)""" + self._testTransposition((0, 2, 1)) + + def testTransposition102(self): + """transposition = (1, 0, 2)""" + self._testTransposition((1, 0, 2)) + + def testTransposition120(self): + """transposition = (1, 2, 0)""" + self._testTransposition((1, 2, 0)) + + def testTransposition201(self): + """transposition = (2, 0, 1)""" + self._testTransposition((2, 0, 1)) + + def testTransposition210(self): + """transposition = (2, 1, 0)""" + self._testTransposition((2, 1, 0)) + + def testAllDoubleTranspositions(self): + for trans1 in self.all_permutations: + for trans2 in self.all_permutations: + self._testDoubleTransposition(trans1, trans2) + + def _testDoubleTransposition(self, transposition1, transposition2): + a = DatasetView(self.h5f["volume"], + transposition=transposition1).transpose(transposition2) + + b = self.volume.transpose(transposition1).transpose(transposition2) + + self.assertTrue(numpy.array_equal(a, b), + "failed with double transposition %s %s" % (transposition1, transposition2)) + + def test1DIndex(self): + a = DatasetView(self.h5f["volume"]) + self.assertTrue(numpy.array_equal(self.volume[1], + a[1])) + + b = DatasetView(self.h5f["volume"], transposition=(1, 0, 2)) + self.assertTrue(numpy.array_equal(self.volume[:, 1, :], + b[1])) + + +class TestTransposedListOfImages(unittest.TestCase): + def setUp(self): + # images attributes + self.ndim = 3 + self.original_shape = (5, 10, 20) + self.size = 1 + for dim in self.original_shape: + self.size *= dim + + volume = numpy.arange(self.size).reshape(self.original_shape) + + self.images = [] + for i in range(self.original_shape[0]): + self.images.append( + volume[i]) + + self.images_as_3D_array = numpy.array(self.images) + + self.all_permutations = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), + (2, 0, 1), (2, 1, 0)] + + def tearDown(self): + pass + + def _testSize(self, obj): + """These assertions apply to all following test cases""" + self.assertEqual(obj.ndim, self.ndim) + self.assertEqual(obj.size, self.size) + size_from_shape = 1 + for dim in obj.shape: + size_from_shape *= dim + self.assertEqual(size_from_shape, self.size) + + for dim in self.original_shape: + self.assertIn(dim, obj.shape) + + def testNoTransposition(self): + """no transposition (transposition = (0, 1, 2))""" + a = ListOfImages(self.images) + + self.assertEqual(a.shape, self.original_shape) + self._testSize(a) + + for i in range(a.shape[0]): + for j in range(a.shape[1]): + for k in range(a.shape[2]): + self.assertEqual(self.images[i][j, k], + a[i, j, k]) + + # reversing the dimensions twice results in no change + rtrans = list(reversed(range(self.ndim))) + self.assertTrue(numpy.array_equal( + a, + a.transpose(rtrans).transpose(rtrans))) + + # test .T property + self.assertTrue(numpy.array_equal( + a.T, + a.transpose(rtrans))) + + def _testTransposition(self, transposition): + """test transposed dataset + + :param tuple transposition: List of dimensions (0... n-1) sorted + in the desired order + """ + a = ListOfImages(self.images, + transposition=transposition) + self._testSize(a) + + # sort shape of transposed object, to hopefully find the original shape + sorted_shape = tuple(dim_size for (_, dim_size) in + sorted(zip(transposition, a.shape))) + self.assertEqual(sorted_shape, self.original_shape) + + a_as_array = numpy.array(self.images).transpose(transposition) + + # test the DatasetView.__array__ method + self.assertTrue(numpy.array_equal( + numpy.array(a), + a_as_array)) + + # test slicing + for selection in [(2, slice(None), slice(None)), + (slice(None), 1, slice(0, 8)), + (slice(0, 3), slice(None), 3), + (1, 3, slice(None)), + (slice(None), 2, 1), + (4, slice(1, 9, 2), 2)]: + self.assertIsInstance(a[selection], numpy.ndarray) + self.assertTrue(numpy.array_equal( + a[selection], + a_as_array[selection])) + + # test the DatasetView.__getitem__ for single values + # (step adjusted to test at least 3 indices in each dimension) + for i in range(0, a.shape[0], a.shape[0] // 3): + for j in range(0, a.shape[1], a.shape[1] // 3): + for k in range(0, a.shape[2], a.shape[2] // 3): + viewed_value = a[i, j, k] + sorted_indices = tuple(idx for (_, idx) in + sorted(zip(transposition, [i, j, k]))) + corresponding_original_value = self.images[sorted_indices[0]][sorted_indices[1:]] + self.assertEqual(viewed_value, + corresponding_original_value) + + # reversing the dimensions twice results in no change + rtrans = list(reversed(range(self.ndim))) + self.assertTrue(numpy.array_equal( + a, + a.transpose(rtrans).transpose(rtrans))) + + # test .T property + self.assertTrue(numpy.array_equal( + a.T, + a.transpose(rtrans))) + + def _testDoubleTransposition(self, transposition1, transposition2): + a = ListOfImages(self.images, + transposition=transposition1).transpose(transposition2) + + b = self.images_as_3D_array.transpose(transposition1).transpose(transposition2) + + self.assertTrue(numpy.array_equal(a, b), + "failed with double transposition %s %s" % (transposition1, transposition2)) + + def testTransposition012(self): + """transposition = (0, 1, 2) + (should be the same as testNoTransposition)""" + self._testTransposition((0, 1, 2)) + + def testTransposition021(self): + """transposition = (0, 2, 1)""" + self._testTransposition((0, 2, 1)) + + def testTransposition102(self): + """transposition = (1, 0, 2)""" + self._testTransposition((1, 0, 2)) + + def testTransposition120(self): + """transposition = (1, 2, 0)""" + self._testTransposition((1, 2, 0)) + + def testTransposition201(self): + """transposition = (2, 0, 1)""" + self._testTransposition((2, 0, 1)) + + def testTransposition210(self): + """transposition = (2, 1, 0)""" + self._testTransposition((2, 1, 0)) + + def testAllDoubleTranspositions(self): + for trans1 in self.all_permutations: + for trans2 in self.all_permutations: + self._testDoubleTransposition(trans1, trans2) + + def test1DIndex(self): + a = ListOfImages(self.images) + self.assertTrue(numpy.array_equal(self.images[1], + a[1])) + + b = ListOfImages(self.images, transposition=(1, 0, 2)) + self.assertTrue(numpy.array_equal(self.images_as_3D_array[:, 1, :], + b[1])) + + +class TestFunctions(unittest.TestCase): + """Test functions to guess the dtype and shape of an array_like + object""" + def testListOfLists(self): + l = [[0, 1, 2], [2, 3, 4]] + self.assertEqual(get_dtype(l), + numpy.dtype(int)) + self.assertEqual(get_shape(l), + (2, 3)) + self.assertTrue(is_nested_sequence(l)) + self.assertFalse(is_array(l)) + self.assertFalse(is_list_of_arrays(l)) + + l = [[0., 1.], [2., 3.]] + self.assertEqual(get_dtype(l), + numpy.dtype(float)) + self.assertEqual(get_shape(l), + (2, 2)) + self.assertTrue(is_nested_sequence(l)) + self.assertFalse(is_array(l)) + self.assertFalse(is_list_of_arrays(l)) + + # concatenated dtype of int and float + l = [numpy.array([[0, 1, 2], [2, 3, 4]]), + numpy.array([[0., 1., 2.], [2., 3., 4.]])] + + self.assertEqual(get_concatenated_dtype(l), + numpy.array(l).dtype) + self.assertEqual(get_shape(l), + (2, 2, 3)) + self.assertFalse(is_nested_sequence(l)) + self.assertFalse(is_array(l)) + self.assertTrue(is_list_of_arrays(l)) + + def testNumpyArray(self): + a = numpy.array([[0, 1], [2, 3]]) + self.assertEqual(get_dtype(a), + a.dtype) + self.assertFalse(is_nested_sequence(a)) + self.assertTrue(is_array(a)) + self.assertFalse(is_list_of_arrays(a)) + + def testH5pyDataset(self): + a = numpy.array([[0, 1], [2, 3]]) + + tempdir = tempfile.mkdtemp() + h5_fname = os.path.join(tempdir, "tempfile.h5") + with h5py.File(h5_fname, "w") as h5f: + h5f["dataset"] = a + d = h5f["dataset"] + + self.assertEqual(get_dtype(d), + numpy.dtype(int)) + self.assertFalse(is_nested_sequence(d)) + self.assertTrue(is_array(d)) + self.assertFalse(is_list_of_arrays(d)) + + os.unlink(h5_fname) + os.rmdir(tempdir) diff --git a/src/silx/utils/test/test_debug.py b/src/silx/utils/test/test_debug.py new file mode 100644 index 0000000..09f4b01 --- /dev/null +++ b/src/silx/utils/test/test_debug.py @@ -0,0 +1,88 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2021 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for debug module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "27/02/2018" + + +import unittest +from silx.utils import debug +from silx.utils import testutils + + +@debug.log_all_methods +class _Foobar(object): + + def a(self): + return None + + def b(self): + return self.a() + + def random_args(self, *args, **kwargs): + return args, kwargs + + def named_args(self, a, b): + return a + 1, b + 1 + + +class TestDebug(unittest.TestCase): + """Tests for debug module.""" + + def logB(self): + """ + Can be used to check the log output using: + `./run_tests.py silx.utils.test.test_debug.TestDebug.logB -v` + """ + print() + test = _Foobar() + test.b() + + @testutils.validate_logging(debug.debug_logger.name, warning=2) + def testMethod(self): + test = _Foobar() + test.a() + + @testutils.validate_logging(debug.debug_logger.name, warning=4) + def testInterleavedMethod(self): + test = _Foobar() + test.b() + + @testutils.validate_logging(debug.debug_logger.name, warning=2) + def testNamedArgument(self): + # Arguments arre still provided to the patched method + test = _Foobar() + result = test.named_args(10, 11) + self.assertEqual(result, (11, 12)) + + @testutils.validate_logging(debug.debug_logger.name, warning=2) + def testRandomArguments(self): + # Arguments arre still provided to the patched method + test = _Foobar() + result = test.random_args("foo", 50, a=10, b=100) + self.assertEqual(result[0], ("foo", 50)) + self.assertEqual(result[1], {"a": 10, "b": 100}) diff --git a/src/silx/utils/test/test_deprecation.py b/src/silx/utils/test/test_deprecation.py new file mode 100644 index 0000000..d52cb26 --- /dev/null +++ b/src/silx/utils/test/test_deprecation.py @@ -0,0 +1,96 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2021 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for html module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "17/01/2018" + + +import unittest +from .. import deprecation +from silx.utils import testutils + + +class TestDeprecation(unittest.TestCase): + """Tests for deprecation module.""" + + @deprecation.deprecated + def deprecatedWithoutParam(self): + pass + + @deprecation.deprecated(reason="r", replacement="r", since_version="v") + def deprecatedWithParams(self): + pass + + @deprecation.deprecated(reason="r", replacement="r", since_version="v", only_once=True) + def deprecatedOnlyOnce(self): + pass + + @deprecation.deprecated(reason="r", replacement="r", since_version="v", only_once=False) + def deprecatedEveryTime(self): + pass + + @testutils.validate_logging(deprecation.depreclog.name, warning=1) + def testAnnotationWithoutParam(self): + self.deprecatedWithoutParam() + + @testutils.validate_logging(deprecation.depreclog.name, warning=1) + def testAnnotationWithParams(self): + self.deprecatedWithParams() + + @testutils.validate_logging(deprecation.depreclog.name, warning=3) + def testLoggedEveryTime(self): + """Logged everytime cause it is 3 different locations""" + self.deprecatedOnlyOnce() + self.deprecatedOnlyOnce() + self.deprecatedOnlyOnce() + + @testutils.validate_logging(deprecation.depreclog.name, warning=1) + def testLoggedSingleTime(self): + def log(): + self.deprecatedOnlyOnce() + log() + log() + log() + + @testutils.validate_logging(deprecation.depreclog.name, warning=3) + def testLoggedEveryTime2(self): + self.deprecatedEveryTime() + self.deprecatedEveryTime() + self.deprecatedEveryTime() + + @testutils.validate_logging(deprecation.depreclog.name, warning=1) + def testWarning(self): + deprecation.deprecated_warning(type_="t", name="n") + + def testBacktrace(self): + loggingValidator = testutils.LoggingValidator(deprecation.depreclog.name) + with loggingValidator: + self.deprecatedEveryTime() + message = loggingValidator.records[0].getMessage() + filename = __file__.replace(".pyc", ".py") + self.assertTrue(filename in message) + self.assertTrue("testBacktrace" in message) diff --git a/src/silx/utils/test/test_enum.py b/src/silx/utils/test/test_enum.py new file mode 100644 index 0000000..808304a --- /dev/null +++ b/src/silx/utils/test/test_enum.py @@ -0,0 +1,85 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2019 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests of Enum class with extra class methods""" + +from __future__ import absolute_import + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "29/04/2019" + + +import sys +import unittest + +import enum +from silx.utils.enum import Enum + + +class TestEnum(unittest.TestCase): + """Tests for enum module.""" + + def test(self): + """Test with Enum""" + class Success(Enum): + A = 1 + B = 'B' + self._check_enum_content(Success) + + @unittest.skipIf(sys.version_info.major <= 2, 'Python3 only') + def test(self): + """Test Enum with member redefinition""" + with self.assertRaises(TypeError): + class Failure(Enum): + A = 1 + A = 'B' + + def test_unique(self): + """Test with enum.unique""" + with self.assertRaises(ValueError): + @enum.unique + class Failure(Enum): + A = 1 + B = 1 + + @enum.unique + class Success(Enum): + A = 1 + B = 'B' + self._check_enum_content(Success) + + def _check_enum_content(self, enum_): + """Check that the content of an enum is: <A: 1, B: 2>. + + :param Enum enum_: + """ + self.assertEqual(enum_.members(), (enum_.A, enum_.B)) + self.assertEqual(enum_.names(), ('A', 'B')) + self.assertEqual(enum_.values(), (1, 'B')) + + self.assertEqual(enum_.from_value(1), enum_.A) + self.assertEqual(enum_.from_value('B'), enum_.B) + with self.assertRaises(ValueError): + enum_.from_value(3) diff --git a/src/silx/utils/test/test_external_resources.py b/src/silx/utils/test/test_external_resources.py new file mode 100644 index 0000000..1fedda3 --- /dev/null +++ b/src/silx/utils/test/test_external_resources.py @@ -0,0 +1,89 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2021 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Test for resource files management.""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "08/03/2019" + + +import os +import unittest +import shutil +import socket +import urllib.request +import urllib.error + +from silx.utils.ExternalResources import ExternalResources + + +def isSilxWebsiteAvailable(): + try: + urllib.request.urlopen('http://www.silx.org', timeout=1) + return True + except urllib.error.URLError: + return False + except socket.timeout: + # This exception is still received in Python 2.7 + return False + + +class TestExternalResources(unittest.TestCase): + """This is a test for the ExternalResources""" + + @classmethod + def setUpClass(cls): + if not isSilxWebsiteAvailable(): + raise unittest.SkipTest("Network or silx website not available") + + def setUp(self): + self.resources = ExternalResources("toto", "http://www.silx.org/pub/silx/") + + def tearDown(self): + if self.resources.data_home: + shutil.rmtree(self.resources.data_home) + self.resources = None + + def test_download(self): + "test the download from silx.org" + f = self.resources.getfile("lena.png") + self.assertTrue(os.path.exists(f)) + di = self.resources.getdir("source.tar.gz") + for fi in di: + self.assertTrue(os.path.exists(fi)) + + def test_download_all(self): + "test the download of all files from silx.org" + filename = self.resources.getfile("lena.png") + directory = "source.tar.gz" + filelist = self.resources.getdir(directory) + # download file and remove it to create a json mapping file + os.remove(filename) + directory_path = os.path.commonprefix(filelist) + # Make sure we will rmtree a dangerous path like "/" + self.assertIn(self.resources.data_home, directory_path) + shutil.rmtree(directory_path) + filelist = self.resources.download_all() + self.assertGreater(len(filelist), 1, "At least 2 items were downloaded") diff --git a/src/silx/utils/test/test_launcher.py b/src/silx/utils/test/test_launcher.py new file mode 100644 index 0000000..9e9024c --- /dev/null +++ b/src/silx/utils/test/test_launcher.py @@ -0,0 +1,191 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for html module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "17/01/2018" + + +import sys +import unittest +from silx.utils.testutils import ParametricTestCase +from .. import launcher + + +class CallbackMock(): + + def __init__(self, result=None): + self._execute_count = 0 + self._execute_argv = None + self._result = result + + def execute(self, argv): + self._execute_count = self._execute_count + 1 + self._execute_argv = argv + return self._result + + def __call__(self, argv): + return self.execute(argv) + + +class TestLauncherCommand(unittest.TestCase): + """Tests for launcher class.""" + + def testEnv(self): + command = launcher.LauncherCommand("foo") + old = sys.argv + params = ["foo", "bar"] + with command.get_env(params): + self.assertEqual(params, sys.argv) + self.assertEqual(sys.argv, old) + + def testEnvWhileException(self): + command = launcher.LauncherCommand("foo") + old = sys.argv + params = ["foo", "bar"] + try: + with command.get_env(params): + raise RuntimeError() + except RuntimeError: + pass + self.assertEqual(sys.argv, old) + + def testExecute(self): + params = ["foo", "bar"] + callback = CallbackMock(result=42) + command = launcher.LauncherCommand("foo", function=callback) + status = command.execute(params) + self.assertEqual(callback._execute_count, 1) + self.assertEqual(callback._execute_argv, params) + self.assertEqual(status, 42) + + +class TestModuleCommand(ParametricTestCase): + + def setUp(self): + module_name = "silx.utils.test.test_launcher_command" + command = launcher.LauncherCommand("foo", module_name=module_name) + self.command = command + + def testHelp(self): + status = self.command.execute(["--help"]) + self.assertEqual(status, 0) + + def testException(self): + try: + self.command.execute(["exception"]) + self.fail() + except RuntimeError: + pass + + def testCall(self): + status = self.command.execute([]) + self.assertEqual(status, 0) + + def testError(self): + status = self.command.execute(["error"]) + self.assertEqual(status, -1) + + +class TestLauncher(ParametricTestCase): + """Tests for launcher class.""" + + def testCallCommand(self): + callback = CallbackMock(result=42) + runner = launcher.Launcher(prog="prog") + command = launcher.LauncherCommand("foo", function=callback) + runner.add_command(command=command) + status = runner.execute(["prog", "foo", "param1", "param2"]) + self.assertEqual(status, 42) + self.assertEqual(callback._execute_argv, ["prog foo", "param1", "param2"]) + self.assertEqual(callback._execute_count, 1) + + def testAddCommand(self): + runner = launcher.Launcher(prog="prog") + module_name = "silx.utils.test.test_launcher_command" + runner.add_command("foo", module_name=module_name) + status = runner.execute(["prog", "foo"]) + self.assertEqual(status, 0) + + def testCallHelpOnCommand(self): + callback = CallbackMock(result=42) + runner = launcher.Launcher(prog="prog") + command = launcher.LauncherCommand("foo", function=callback) + runner.add_command(command=command) + status = runner.execute(["prog", "--help", "foo"]) + self.assertEqual(status, 42) + self.assertEqual(callback._execute_argv, ["prog foo", "--help"]) + self.assertEqual(callback._execute_count, 1) + + def testCallHelpOnCommand2(self): + callback = CallbackMock(result=42) + runner = launcher.Launcher(prog="prog") + command = launcher.LauncherCommand("foo", function=callback) + runner.add_command(command=command) + status = runner.execute(["prog", "help", "foo"]) + self.assertEqual(status, 42) + self.assertEqual(callback._execute_argv, ["prog foo", "--help"]) + self.assertEqual(callback._execute_count, 1) + + def testCallHelpOnUnknownCommand(self): + callback = CallbackMock(result=42) + runner = launcher.Launcher(prog="prog") + command = launcher.LauncherCommand("foo", function=callback) + runner.add_command(command=command) + status = runner.execute(["prog", "help", "foo2"]) + self.assertEqual(status, -1) + + def testNotAvailableCommand(self): + callback = CallbackMock(result=42) + runner = launcher.Launcher(prog="prog") + command = launcher.LauncherCommand("foo", function=callback) + runner.add_command(command=command) + status = runner.execute(["prog", "foo2", "param1", "param2"]) + self.assertEqual(status, -1) + self.assertEqual(callback._execute_count, 0) + + def testCallHelp(self): + callback = CallbackMock(result=42) + runner = launcher.Launcher(prog="prog") + command = launcher.LauncherCommand("foo", function=callback) + runner.add_command(command=command) + status = runner.execute(["prog", "help"]) + self.assertEqual(status, 0) + self.assertEqual(callback._execute_count, 0) + + def testCommonCommands(self): + runner = launcher.Launcher() + tests = [ + ["prog"], + ["prog", "--help"], + ["prog", "--version"], + ["prog", "help", "--help"], + ["prog", "help", "help"], + ] + for arguments in tests: + with self.subTest(args=tests): + status = runner.execute(arguments) + self.assertEqual(status, 0) diff --git a/src/silx/utils/test/test_launcher_command.py b/src/silx/utils/test/test_launcher_command.py new file mode 100644 index 0000000..ccf4601 --- /dev/null +++ b/src/silx/utils/test/test_launcher_command.py @@ -0,0 +1,47 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for html module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "03/04/2017" + + +import sys + + +def main(argv): + + if "--help" in argv: + # Common behaviour of ArgumentParser + sys.exit(0) + + if "exception" in argv: + raise RuntimeError("Simulated exception") + + if "error" in argv: + return -1 + + return 0 diff --git a/src/silx/utils/test/test_number.py b/src/silx/utils/test/test_number.py new file mode 100644 index 0000000..3eb8e91 --- /dev/null +++ b/src/silx/utils/test/test_number.py @@ -0,0 +1,175 @@ +# coding: utf-8 +# /*########################################################################## +# Copyright (C) 2016-2021 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################*/ +"""Tests for silx.uitls.number module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "05/06/2018" + +import logging +import numpy +import unittest +import pkg_resources +from silx.utils import number +from silx.utils import testutils + +_logger = logging.getLogger(__name__) + + +class TestConversionTypes(testutils.ParametricTestCase): + + def testEmptyFail(self): + self.assertRaises(ValueError, number.min_numerical_convertible_type, "") + + def testStringFail(self): + self.assertRaises(ValueError, number.min_numerical_convertible_type, "a") + + def testInteger(self): + dtype = number.min_numerical_convertible_type("1456") + self.assertTrue(numpy.issubdtype(dtype, numpy.unsignedinteger)) + + def testTrailledInteger(self): + dtype = number.min_numerical_convertible_type(" \t\n\r1456\t\n\r") + self.assertTrue(numpy.issubdtype(dtype, numpy.unsignedinteger)) + + def testPositiveInteger(self): + dtype = number.min_numerical_convertible_type("+1456") + self.assertTrue(numpy.issubdtype(dtype, numpy.unsignedinteger)) + + def testNegativeInteger(self): + dtype = number.min_numerical_convertible_type("-1456") + self.assertTrue(numpy.issubdtype(dtype, numpy.signedinteger)) + + def testIntegerExponential(self): + dtype = number.min_numerical_convertible_type("14e10") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testIntegerPositiveExponential(self): + dtype = number.min_numerical_convertible_type("14e+10") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testIntegerNegativeExponential(self): + dtype = number.min_numerical_convertible_type("14e-10") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testNumberDecimal(self): + dtype = number.min_numerical_convertible_type("14.5") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testPositiveNumberDecimal(self): + dtype = number.min_numerical_convertible_type("+14.5") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testNegativeNumberDecimal(self): + dtype = number.min_numerical_convertible_type("-14.5") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testDecimal(self): + dtype = number.min_numerical_convertible_type(".50") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testPositiveDecimal(self): + dtype = number.min_numerical_convertible_type("+.5") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testNegativeDecimal(self): + dtype = number.min_numerical_convertible_type("-.5") + self.assertTrue(numpy.issubdtype(dtype, numpy.floating)) + + def testMantissa16(self): + dtype = number.min_numerical_convertible_type("1.50") + self.assertEqual(dtype, numpy.float16) + + def testFloat32(self): + dtype = number.min_numerical_convertible_type("-23.172") + self.assertEqual(dtype, numpy.float32) + + def testMantissa32(self): + dtype = number.min_numerical_convertible_type("1400.50") + self.assertEqual(dtype, numpy.float32) + + def testMantissa64(self): + dtype = number.min_numerical_convertible_type("10000.000010") + self.assertEqual(dtype, numpy.float64) + + def testMantissa80(self): + self.skipIfFloat80NotSupported() + dtype = number.min_numerical_convertible_type("1000000000.00001013") + + if pkg_resources.parse_version(numpy.version.version) <= pkg_resources.parse_version("1.10.4"): + # numpy 1.8.2 -> Debian 8 + # Checking a float128 precision with numpy 1.8.2 using abs(diff) is not working. + # It looks like the difference is done using float64 (diff == 0.0) + expected = (numpy.longdouble, numpy.float64) + else: + expected = (numpy.longdouble, ) + self.assertIn(dtype, expected) + + def testExponent32(self): + dtype = number.min_numerical_convertible_type("14.0e30") + self.assertEqual(dtype, numpy.float32) + + def testExponent64(self): + dtype = number.min_numerical_convertible_type("14.0e300") + self.assertEqual(dtype, numpy.float64) + + def testExponent80(self): + self.skipIfFloat80NotSupported() + dtype = number.min_numerical_convertible_type("14.0e3000") + self.assertEqual(dtype, numpy.longdouble) + + def testFloat32ToString(self): + value = str(numpy.float32(numpy.pi)) + dtype = number.min_numerical_convertible_type(value) + self.assertIn(dtype, (numpy.float32, numpy.float64)) + + def skipIfFloat80NotSupported(self): + if number.is_longdouble_64bits(): + self.skipTest("float-80bits not supported") + + def testLosePrecisionUsingFloat80(self): + self.skipIfFloat80NotSupported() + if pkg_resources.parse_version(numpy.version.version) <= pkg_resources.parse_version("1.10.4"): + self.skipTest("numpy > 1.10.4 expected") + # value does not fit even in a 128 bits mantissa + value = "1.0340282366920938463463374607431768211456" + func = testutils.validate_logging(number._logger.name, warning=1) + func = func(number.min_numerical_convertible_type) + dtype = func(value) + self.assertIn(dtype, (numpy.longdouble, )) + + def testMillisecondEpochTime(self): + datetimes = ['1465803236.495412', + '1465803236.999362', + '1465803237.504311', + '1465803238.009261', + '1465803238.512211', + '1465803239.016160', + '1465803239.520110', + '1465803240.026059', + '1465803240.529009'] + for datetime in datetimes: + with self.subTest(datetime=datetime): + dtype = number.min_numerical_convertible_type(datetime) + self.assertEqual(dtype, numpy.float64) diff --git a/src/silx/utils/test/test_proxy.py b/src/silx/utils/test/test_proxy.py new file mode 100644 index 0000000..e165267 --- /dev/null +++ b/src/silx/utils/test/test_proxy.py @@ -0,0 +1,330 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2019 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for weakref module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "02/10/2017" + + +import unittest +import pickle +import numpy +from silx.utils.proxy import Proxy, docstring + + +class Thing(object): + + def __init__(self, value): + self.value = value + + def __getitem__(self, selection): + return selection + 1 + + def method(self, value): + return value + 2 + + +class InheritedProxy(Proxy): + """Inheriting the proxy allow to specialisze methods""" + + def __init__(self, obj, value): + Proxy.__init__(self, obj) + self.value = value + 2 + + def __getitem__(self, selection): + return selection + 3 + + def method(self, value): + return value + 4 + + +class TestProxy(unittest.TestCase): + """Test that the proxy behave as expected""" + + def text_init(self): + obj = Thing(10) + p = Proxy(obj) + self.assertTrue(isinstance(p, Thing)) + self.assertTrue(isinstance(p, Proxy)) + + # methods and properties + + def test_has_special_method(self): + obj = Thing(10) + p = Proxy(obj) + self.assertTrue(hasattr(p, "__getitem__")) + + def test_missing_special_method(self): + obj = Thing(10) + p = Proxy(obj) + self.assertFalse(hasattr(p, "__and__")) + + def test_method(self): + obj = Thing(10) + p = Proxy(obj) + self.assertEqual(p.method(10), obj.method(10)) + + def test_property(self): + obj = Thing(10) + p = Proxy(obj) + self.assertEqual(p.value, obj.value) + + # special functions + + def test_getitem(self): + obj = Thing(10) + p = Proxy(obj) + self.assertEqual(p[10], obj[10]) + + def test_setitem(self): + obj = numpy.array([10, 20, 30]) + p = Proxy(obj) + p[0] = 20 + self.assertEqual(obj[0], 20) + + def test_slice(self): + obj = numpy.arange(20) + p = Proxy(obj) + expected = obj[0:10:2] + result = p[0:10:2] + self.assertEqual(list(result), list(expected)) + + # binary comparator methods + + def test_lt(self): + obj = numpy.array([20]) + p = Proxy(obj) + expected = obj < obj + result = p < p + self.assertEqual(result, expected) + + # binary numeric methods + + def test_add(self): + obj = numpy.array([20]) + proxy = Proxy(obj) + expected = obj + obj + result = proxy + proxy + self.assertEqual(result, expected) + + def test_iadd(self): + expected = numpy.array([20]) + expected += 10 + obj = numpy.array([20]) + result = Proxy(obj) + result += 10 + self.assertEqual(result, expected) + + def test_radd(self): + obj = numpy.array([20]) + p = Proxy(obj) + expected = 10 + obj + result = 10 + p + self.assertEqual(result, expected) + + # binary logical methods + + def test_and(self): + obj = numpy.array([20]) + p = Proxy(obj) + expected = obj & obj + result = p & p + self.assertEqual(result, expected) + + def test_iand(self): + expected = numpy.array([20]) + expected &= 10 + obj = numpy.array([20]) + result = Proxy(obj) + result &= 10 + self.assertEqual(result, expected) + + def test_rand(self): + obj = numpy.array([20]) + p = Proxy(obj) + expected = 10 & obj + result = 10 & p + self.assertEqual(result, expected) + + # unary methods + + def test_neg(self): + obj = numpy.array([20]) + p = Proxy(obj) + expected = -obj + result = -p + self.assertEqual(result, expected) + + def test_round(self): + obj = 20.5 + p = Proxy(obj) + expected = round(obj) + result = round(p) + self.assertEqual(result, expected) + + # cast + + def test_bool(self): + obj = True + p = Proxy(obj) + if p: + pass + else: + self.fail() + + def test_str(self): + obj = Thing(10) + p = Proxy(obj) + expected = str(obj) + result = str(p) + self.assertEqual(result, expected) + + def test_repr(self): + obj = Thing(10) + p = Proxy(obj) + expected = repr(obj) + result = repr(p) + self.assertEqual(result, expected) + + def test_text_bool(self): + obj = "" + p = Proxy(obj) + if p: + self.fail() + else: + pass + + def test_text_str(self): + obj = "a" + p = Proxy(obj) + expected = str(obj) + result = str(p) + self.assertEqual(result, expected) + + def test_text_repr(self): + obj = "a" + p = Proxy(obj) + expected = repr(obj) + result = repr(p) + self.assertEqual(result, expected) + + def test_hash(self): + obj = [0, 1, 2] + p = Proxy(obj) + with self.assertRaises(TypeError): + hash(p) + obj = (0, 1, 2) + p = Proxy(obj) + hash(p) + + +class TestInheritedProxy(unittest.TestCase): + """Test that inheriting the Proxy class behave as expected""" + + # methods and properties + + def test_method(self): + obj = Thing(10) + p = InheritedProxy(obj, 11) + self.assertEqual(p.method(10), 11 + 3) + + def test_property(self): + obj = Thing(10) + p = InheritedProxy(obj, 11) + self.assertEqual(p.value, 11 + 2) + + # special functions + + def test_getitem(self): + obj = Thing(10) + p = InheritedProxy(obj, 11) + self.assertEqual(p[12], 12 + 3) + + +class TestPickle(unittest.TestCase): + + def test_dumps(self): + obj = Thing(10) + p = Proxy(obj) + expected = pickle.dumps(obj) + result = pickle.dumps(p) + self.assertEqual(result, expected) + + def test_loads(self): + obj = Thing(10) + p = Proxy(obj) + obj2 = pickle.loads(pickle.dumps(p)) + self.assertTrue(isinstance(obj2, Thing)) + self.assertFalse(isinstance(obj2, Proxy)) + self.assertEqual(obj.value, obj2.value) + + +class TestDocstring(unittest.TestCase): + """Test docstring decorator""" + + class Base(object): + def method(self): + """Docstring""" + pass + + def test_inheritance(self): + class Derived(TestDocstring.Base): + @docstring(TestDocstring.Base) + def method(self): + pass + + self.assertEqual(Derived.method.__doc__, + TestDocstring.Base.method.__doc__) + + def test_composition(self): + class Composed(object): + def __init__(self): + self._base = TestDocstring.Base() + + @docstring(TestDocstring.Base) + def method(self): + return self._base.method() + + @docstring(TestDocstring.Base.method) + def renamed(self): + return self._base.method() + + self.assertEqual(Composed.method.__doc__, + TestDocstring.Base.method.__doc__) + + self.assertEqual(Composed.renamed.__doc__, + TestDocstring.Base.method.__doc__) + + def test_function(self): + def f(): + """Docstring""" + pass + + @docstring(f) + def g(): + pass + + self.assertEqual(f.__doc__, g.__doc__) diff --git a/src/silx/utils/test/test_retry.py b/src/silx/utils/test/test_retry.py new file mode 100644 index 0000000..39bfdcf --- /dev/null +++ b/src/silx/utils/test/test_retry.py @@ -0,0 +1,169 @@ +# coding: utf-8 +# /*########################################################################## +# Copyright (C) 2016-2017 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################*/ +"""Tests for retry utilities""" + +__authors__ = ["W. de Nolf"] +__license__ = "MIT" +__date__ = "05/02/2020" + + +import unittest +import os +import sys +import time +import tempfile + +from .. import retry + + +def _cause_segfault(): + import ctypes + + i = ctypes.c_char(b"a") + j = ctypes.pointer(i) + c = 0 + while True: + j[c] = b"a" + c += 1 + + +def _submain(filename, kwcheck=None, ncausefailure=0, faildelay=0): + assert filename + assert kwcheck + sys.stderr = open(os.devnull, "w") + + with open(filename, mode="r") as f: + failcounter = int(f.readline().strip()) + + if failcounter < ncausefailure: + time.sleep(faildelay) + failcounter += 1 + with open(filename, mode="w") as f: + f.write(str(failcounter)) + if failcounter % 2: + raise retry.RetryError + else: + _cause_segfault() + return True + + +_wsubmain = retry.retry_in_subprocess()(_submain) + + +class TestRetry(unittest.TestCase): + def setUp(self): + self.test_dir = tempfile.mkdtemp() + self.ctr_file = os.path.join(self.test_dir, "failcounter.txt") + + def tearDown(self): + if os.path.exists(self.ctr_file): + os.unlink(self.ctr_file) + os.rmdir(self.test_dir) + + def test_retry(self): + ncausefailure = 3 + faildelay = 0.1 + sufficient_timeout = ncausefailure * (faildelay + 10) + insufficient_timeout = ncausefailure * faildelay * 0.5 + + @retry.retry() + def method(check, kwcheck=None): + assert check + assert kwcheck + nonlocal failcounter + if failcounter < ncausefailure: + time.sleep(faildelay) + failcounter += 1 + raise retry.RetryError + return True + + failcounter = 0 + kw = { + "kwcheck": True, + "retry_timeout": sufficient_timeout, + } + self.assertTrue(method(True, **kw)) + + failcounter = 0 + kw = { + "kwcheck": True, + "retry_timeout": insufficient_timeout, + } + with self.assertRaises(retry.RetryTimeoutError): + method(True, **kw) + + def test_retry_contextmanager(self): + ncausefailure = 3 + faildelay = 0.1 + sufficient_timeout = ncausefailure * (faildelay + 10) + insufficient_timeout = ncausefailure * faildelay * 0.5 + + @retry.retry_contextmanager() + def context(check, kwcheck=None): + assert check + assert kwcheck + nonlocal failcounter + if failcounter < ncausefailure: + time.sleep(faildelay) + failcounter += 1 + raise retry.RetryError + yield True + + failcounter = 0 + kw = {"kwcheck": True, "retry_timeout": sufficient_timeout} + with context(True, **kw) as result: + self.assertTrue(result) + + failcounter = 0 + kw = {"kwcheck": True, "retry_timeout": insufficient_timeout} + with self.assertRaises(retry.RetryTimeoutError): + with context(True, **kw) as result: + pass + + def test_retry_in_subprocess(self): + ncausefailure = 3 + faildelay = 0.1 + sufficient_timeout = ncausefailure * (faildelay + 10) + insufficient_timeout = ncausefailure * faildelay * 0.5 + + kw = { + "ncausefailure": ncausefailure, + "faildelay": faildelay, + "kwcheck": True, + "retry_timeout": sufficient_timeout, + } + with open(self.ctr_file, mode="w") as f: + f.write("0") + self.assertTrue(_wsubmain(self.ctr_file, **kw)) + + kw = { + "ncausefailure": ncausefailure, + "faildelay": faildelay, + "kwcheck": True, + "retry_timeout": insufficient_timeout, + } + with open(self.ctr_file, mode="w") as f: + f.write("0") + with self.assertRaises(retry.RetryTimeoutError): + _wsubmain(self.ctr_file, **kw) diff --git a/src/silx/utils/test/test_testutils.py b/src/silx/utils/test/test_testutils.py new file mode 100755 index 0000000..4f07c4e --- /dev/null +++ b/src/silx/utils/test/test_testutils.py @@ -0,0 +1,94 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2021 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for testutils module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "18/11/2019" + + +import unittest +import logging +from .. import testutils + + +class TestLoggingValidator(unittest.TestCase): + """Tests for LoggingValidator""" + + def testRight(self): + logger = logging.getLogger(__name__ + "testRight") + listener = testutils.LoggingValidator(logger, error=1) + with listener: + logger.error("expected") + logger.info("ignored") + + def testCustomLevel(self): + logger = logging.getLogger(__name__ + "testCustomLevel") + listener = testutils.LoggingValidator(logger, error=1) + with listener: + logger.error("expected") + logger.log(666, "custom level have to be ignored") + + def testWrong(self): + logger = logging.getLogger(__name__ + "testWrong") + listener = testutils.LoggingValidator(logger, error=1) + with self.assertRaises(RuntimeError): + with listener: + logger.error("expected") + logger.error("not expected") + + def testManyErrors(self): + logger = logging.getLogger(__name__ + "testManyErrors") + listener = testutils.LoggingValidator(logger, error=1, warning=2) + with self.assertRaises(RuntimeError): + with listener: + pass + + def testCanBeChecked(self): + logger = logging.getLogger(__name__ + "testCanBreak") + listener = testutils.LoggingValidator(logger, error=1, warning=2) + with self.assertRaises(RuntimeError): + with listener: + logger.error("aaa") + logger.warning("aaa") + self.assertFalse(listener.can_be_checked()) + logger.error("aaa") + # Here we know that it's already wrong without a big cost + self.assertTrue(listener.can_be_checked()) + + def testWithAs(self): + logger = logging.getLogger(__name__ + "testCanBreak") + with testutils.LoggingValidator(logger) as listener: + logger.error("aaa") + self.assertIsNotNone(listener) + + def testErrorMessage(self): + logger = logging.getLogger(__name__ + "testCanBreak") + listener = testutils.LoggingValidator(logger, error=1, warning=2) + with self.assertRaisesRegex(RuntimeError, "aaabbb"): + with listener: + logger.error("aaa") + logger.warning("aaabbb") + logger.error("aaa") diff --git a/src/silx/utils/test/test_weakref.py b/src/silx/utils/test/test_weakref.py new file mode 100644 index 0000000..06f3adf --- /dev/null +++ b/src/silx/utils/test/test_weakref.py @@ -0,0 +1,315 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2019 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 +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Tests for weakref module""" + +__authors__ = ["V. Valls"] +__license__ = "MIT" +__date__ = "15/09/2016" + + +import unittest +from .. import weakref + + +class Dummy(object): + """Dummy class to use it as geanie pig""" + def inc(self, a): + return a + 1 + + def __lt__(self, other): + return True + + +def dummy_inc(a): + """Dummy function to use it as geanie pig""" + return a + 1 + + +class TestWeakMethod(unittest.TestCase): + """Tests for weakref.WeakMethod""" + + def testMethod(self): + dummy = Dummy() + callable_ = weakref.WeakMethod(dummy.inc) + self.assertEqual(callable_()(10), 11) + + def testMethodWithDeadObject(self): + dummy = Dummy() + callable_ = weakref.WeakMethod(dummy.inc) + dummy = None + self.assertIsNone(callable_()) + + def testMethodWithDeadFunction(self): + dummy = Dummy() + dummy.inc2 = lambda self, a: a + 1 + callable_ = weakref.WeakMethod(dummy.inc2) + dummy.inc2 = None + self.assertIsNone(callable_()) + + def testFunction(self): + callable_ = weakref.WeakMethod(dummy_inc) + self.assertEqual(callable_()(10), 11) + + def testDeadFunction(self): + def inc(a): + return a + 1 + callable_ = weakref.WeakMethod(inc) + inc = None + self.assertIsNone(callable_()) + + def testLambda(self): + store = lambda a: a + 1 # noqa: E731 + callable_ = weakref.WeakMethod(store) + self.assertEqual(callable_()(10), 11) + + def testDeadLambda(self): + callable_ = weakref.WeakMethod(lambda a: a + 1) + self.assertIsNone(callable_()) + + def testCallbackOnDeadObject(self): + self.__count = 0 + + def callback(ref): + self.__count += 1 + self.assertIs(callable_, ref) + dummy = Dummy() + callable_ = weakref.WeakMethod(dummy.inc, callback) + dummy = None + self.assertEqual(self.__count, 1) + + def testCallbackOnDeadMethod(self): + self.__count = 0 + + def callback(ref): + self.__count += 1 + self.assertIs(callable_, ref) + dummy = Dummy() + dummy.inc2 = lambda self, a: a + 1 + callable_ = weakref.WeakMethod(dummy.inc2, callback) + dummy.inc2 = None + self.assertEqual(self.__count, 1) + + def testCallbackOnDeadFunction(self): + self.__count = 0 + + def callback(ref): + self.__count += 1 + self.assertIs(callable_, ref) + store = lambda a: a + 1 # noqa: E731 + callable_ = weakref.WeakMethod(store, callback) + store = None + self.assertEqual(self.__count, 1) + + def testEquals(self): + dummy = Dummy() + callable1 = weakref.WeakMethod(dummy.inc) + callable2 = weakref.WeakMethod(dummy.inc) + self.assertEqual(callable1, callable2) + + def testInSet(self): + callable_set = set([]) + dummy = Dummy() + callable_set.add(weakref.WeakMethod(dummy.inc)) + callable_ = weakref.WeakMethod(dummy.inc) + self.assertIn(callable_, callable_set) + + def testInDict(self): + callable_dict = {} + dummy = Dummy() + callable_dict[weakref.WeakMethod(dummy.inc)] = 10 + callable_ = weakref.WeakMethod(dummy.inc) + self.assertEqual(callable_dict.get(callable_), 10) + + +class TestWeakMethodProxy(unittest.TestCase): + + def testMethod(self): + dummy = Dummy() + callable_ = weakref.WeakMethodProxy(dummy.inc) + self.assertEqual(callable_(10), 11) + + def testMethodWithDeadObject(self): + dummy = Dummy() + method = weakref.WeakMethodProxy(dummy.inc) + dummy = None + self.assertRaises(ReferenceError, method, 9) + + +class TestWeakList(unittest.TestCase): + """Tests for weakref.WeakList""" + + def setUp(self): + self.list = weakref.WeakList() + self.object1 = Dummy() + self.object2 = Dummy() + self.list.append(self.object1) + self.list.append(self.object2) + + def testAppend(self): + obj = Dummy() + self.list.append(obj) + self.assertEqual(len(self.list), 3) + obj = None + self.assertEqual(len(self.list), 2) + + def testRemove(self): + self.list.remove(self.object1) + self.assertEqual(len(self.list), 1) + + def testPop(self): + obj = self.list.pop(0) + self.assertIs(obj, self.object1) + self.assertEqual(len(self.list), 1) + + def testGetItem(self): + self.assertIs(self.object1, self.list[0]) + + def testGetItemSlice(self): + objects = self.list[:] + self.assertEqual(len(objects), 2) + self.assertIs(self.object1, objects[0]) + self.assertIs(self.object2, objects[1]) + + def testIter(self): + obj_list = list(self.list) + self.assertEqual(len(obj_list), 2) + self.assertIs(self.object1, obj_list[0]) + + def testLen(self): + self.assertEqual(len(self.list), 2) + + def testSetItem(self): + obj = Dummy() + self.list[0] = obj + self.assertIsNot(self.object1, self.list[0]) + obj = None + self.assertEqual(len(self.list), 1) + + def testSetItemSlice(self): + obj = Dummy() + self.list[:] = [obj, obj] + self.assertEqual(len(self.list), 2) + self.assertIs(obj, self.list[0]) + self.assertIs(obj, self.list[1]) + obj = None + self.assertEqual(len(self.list), 0) + + def testDelItem(self): + del self.list[0] + self.assertEqual(len(self.list), 1) + self.assertIs(self.object2, self.list[0]) + + def testDelItemSlice(self): + del self.list[:] + self.assertEqual(len(self.list), 0) + + def testContains(self): + self.assertIn(self.object1, self.list) + + def testAdd(self): + others = [Dummy()] + l = self.list + others + self.assertIs(l[0], self.object1) + self.assertEqual(len(l), 3) + others = None + self.assertEqual(len(l), 2) + + def testExtend(self): + others = [Dummy()] + self.list.extend(others) + self.assertIs(self.list[0], self.object1) + self.assertEqual(len(self.list), 3) + others = None + self.assertEqual(len(self.list), 2) + + def testIadd(self): + others = [Dummy()] + self.list += others + self.assertIs(self.list[0], self.object1) + self.assertEqual(len(self.list), 3) + others = None + self.assertEqual(len(self.list), 2) + + def testMul(self): + l = self.list * 2 + self.assertIs(l[0], self.object1) + self.assertEqual(len(l), 4) + self.object1 = None + self.assertEqual(len(l), 2) + self.assertIs(l[0], self.object2) + self.assertIs(l[1], self.object2) + + def testImul(self): + self.list *= 2 + self.assertIs(self.list[0], self.object1) + self.assertEqual(len(self.list), 4) + self.object1 = None + self.assertEqual(len(self.list), 2) + self.assertIs(self.list[0], self.object2) + self.assertIs(self.list[1], self.object2) + + def testCount(self): + self.list.append(self.object2) + self.assertEqual(self.list.count(self.object1), 1) + self.assertEqual(self.list.count(self.object2), 2) + + def testIndex(self): + self.assertEqual(self.list.index(self.object1), 0) + self.assertEqual(self.list.index(self.object2), 1) + + def testInsert(self): + obj = Dummy() + self.list.insert(1, obj) + self.assertEqual(len(self.list), 3) + self.assertIs(self.list[1], obj) + obj = None + self.assertEqual(len(self.list), 2) + + def testReverse(self): + self.list.reverse() + self.assertEqual(len(self.list), 2) + self.assertIs(self.list[0], self.object2) + self.assertIs(self.list[1], self.object1) + + def testReverted(self): + new_list = reversed(self.list) + self.assertEqual(len(new_list), 2) + self.assertIs(self.list[1], self.object2) + self.assertIs(self.list[0], self.object1) + self.assertIs(new_list[0], self.object2) + self.assertIs(new_list[1], self.object1) + self.object1 = None + self.assertEqual(len(new_list), 1) + + def testStr(self): + self.assertNotEqual(self.list.__str__(), "[]") + + def testRepr(self): + self.assertNotEqual(self.list.__repr__(), "[]") + + def testSort(self): + # only a coverage + self.list.sort() + self.assertEqual(len(self.list), 2) |