summaryrefslogtreecommitdiff
path: root/silx/utils
diff options
context:
space:
mode:
Diffstat (limited to 'silx/utils')
-rw-r--r--silx/utils/files.py6
-rwxr-xr-x[-rw-r--r--]silx/utils/number.py6
-rwxr-xr-x[-rw-r--r--]silx/utils/test/__init__.py2
-rw-r--r--silx/utils/test/test_number.py4
-rwxr-xr-xsilx/utils/test/test_testutils.py96
-rwxr-xr-x[-rw-r--r--]silx/utils/testutils.py73
6 files changed, 167 insertions, 20 deletions
diff --git a/silx/utils/files.py b/silx/utils/files.py
index 326d308..1982c0d 100644
--- a/silx/utils/files.py
+++ b/silx/utils/files.py
@@ -46,7 +46,11 @@ def expand_filenames(filenames):
if os.path.exists(filename):
result.append(filename)
elif glob.has_magic(filename):
- result += glob.glob(filename)
+ expanded_filenames = glob.glob(filename)
+ if expanded_filenames:
+ result += expanded_filenames
+ else: # Cannot expand, add as is
+ result.append(filename)
else:
result.append(filename)
return result
diff --git a/silx/utils/number.py b/silx/utils/number.py
index 92e98fe..f852a39 100644..100755
--- a/silx/utils/number.py
+++ b/silx/utils/number.py
@@ -68,8 +68,8 @@ def is_longdouble_64bits():
def min_numerical_convertible_type(string, check_accuracy=True):
"""
- Parse the string and return the smallest numerical type to use for a safe
- conversion.
+ Parse the string and try to return the smallest numerical type to use for
+ a safe conversion. It has some known issues: precission loss.
:param str string: Representation of a float/integer with text
:param bool check_accuracy: If true, a warning is pushed on the logger
@@ -104,7 +104,7 @@ def min_numerical_convertible_type(string, check_accuracy=True):
exponent = "0"
nb_precision_digits = int(exponent) - len(decimal) - 1
- precision = _biggest_float(10) ** nb_precision_digits * 2.5
+ precision = _biggest_float(10) ** nb_precision_digits * 1.2
previous_type = _biggest_float
for numpy_type in _float_types:
if numpy_type == _biggest_float:
diff --git a/silx/utils/test/__init__.py b/silx/utils/test/__init__.py
index 029523c..252bc05 100644..100755
--- a/silx/utils/test/__init__.py
+++ b/silx/utils/test/__init__.py
@@ -38,6 +38,7 @@ from . import test_debug
from . import test_number
from . import test_external_resources
from . import test_enum
+from . import test_testutils
def suite():
@@ -52,4 +53,5 @@ def suite():
test_suite.addTest(test_number.suite())
test_suite.addTest(test_external_resources.suite())
test_suite.addTest(test_enum.suite())
+ test_suite.addTest(test_testutils.suite())
return test_suite
diff --git a/silx/utils/test/test_number.py b/silx/utils/test/test_number.py
index e4f6bd8..4ac9636 100644
--- a/silx/utils/test/test_number.py
+++ b/silx/utils/test/test_number.py
@@ -101,6 +101,10 @@ class TestConversionTypes(testutils.ParametricTestCase):
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)
diff --git a/silx/utils/test/test_testutils.py b/silx/utils/test/test_testutils.py
new file mode 100755
index 0000000..c29a703
--- /dev/null
+++ b/silx/utils/test/test_testutils.py
@@ -0,0 +1,96 @@
+# 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 testutils module"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "18/11/2019"
+
+
+import unittest
+import logging
+from .. import testutils
+
+
+class TestTestLogging(unittest.TestCase):
+ """Tests for TestLogging."""
+
+ def testRight(self):
+ logger = logging.getLogger(__name__ + "testRight")
+ listener = testutils.TestLogging(logger, error=1)
+ with listener:
+ logger.error("expected")
+ logger.info("ignored")
+
+ def testCustomLevel(self):
+ logger = logging.getLogger(__name__ + "testCustomLevel")
+ listener = testutils.TestLogging(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.TestLogging(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.TestLogging(logger, error=1, warning=2)
+ with self.assertRaises(RuntimeError):
+ with listener:
+ pass
+
+ def testCanBeChecked(self):
+ logger = logging.getLogger(__name__ + "testCanBreak")
+ listener = testutils.TestLogging(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.TestLogging(logger) as listener:
+ logger.error("aaa")
+ self.assertIsNotNone(listener)
+
+
+def suite():
+ loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(loadTests(TestTestLogging))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/silx/utils/testutils.py b/silx/utils/testutils.py
index 82c2ce3..1252269 100644..100755
--- a/silx/utils/testutils.py
+++ b/silx/utils/testutils.py
@@ -141,7 +141,7 @@ class TestLogging(logging.Handler):
self.records = []
- self.count_by_level = {
+ self.expected_count_by_level = {
logging.CRITICAL: critical,
logging.ERROR: error,
logging.WARNING: warning,
@@ -150,6 +150,9 @@ class TestLogging(logging.Handler):
logging.NOTSET: notset
}
+ self._expected_count = sum([v for k, v in self.expected_count_by_level.items() if v is not None])
+ """Amount of any logging expected"""
+
super(TestLogging, self).__init__()
def __enter__(self):
@@ -160,27 +163,65 @@ class TestLogging(logging.Handler):
# ensure no log message is ignored
self.entry_level = self.logger.level * 1
self.logger.setLevel(logging.DEBUG)
+ self.entry_disabled = self.logger.disabled
+ self.logger.disabled = False
+ return self
+
+ def can_be_checked(self):
+ """Returns True if this listener have received enough messages to
+ be valid, and then checked.
+
+ This can be useful for asynchronous wait of messages. It allows process
+ an early break, instead of waiting much time in an active loop.
+ """
+ return len(self.records) >= self._expected_count
+
+ def get_count_by_level(self):
+ """Returns the current message count by level.
+ """
+ count = {
+ logging.CRITICAL: 0,
+ logging.ERROR: 0,
+ logging.WARNING: 0,
+ logging.INFO: 0,
+ logging.DEBUG: 0,
+ logging.NOTSET: 0
+ }
+ for record in self.records:
+ level = record.levelno
+ if level in count:
+ count[level] = count[level] + 1
+ return count
def __exit__(self, exc_type, exc_value, traceback):
"""Context (i.e., with) support"""
self.logger.removeHandler(self)
self.logger.propagate = True
self.logger.setLevel(self.entry_level)
-
- for level, expected_count in self.count_by_level.items():
- if expected_count is None:
- continue
-
- # Number of records for the specified level_str
- count = len([r for r in self.records if r.levelno == level])
- if count != expected_count: # That's an error
- # Resend record logs through logger as they where masked
- # to help debug
- for record in self.records:
- self.logger.handle(record)
- raise RuntimeError(
- 'Expected %d %s logging messages, got %d' % (
- expected_count, logging.getLevelName(level), count))
+ self.logger.disabled = self.entry_disabled
+
+ count_by_level = self.get_count_by_level()
+
+ # Remove keys which does not matter
+ ignored = [r for r, v in self.expected_count_by_level.items() if v is None]
+ expected_count_by_level = dict(self.expected_count_by_level)
+ for i in ignored:
+ del count_by_level[i]
+ del expected_count_by_level[i]
+
+ if count_by_level != expected_count_by_level:
+ # Re-send record logs through logger as they where masked
+ # to help debug
+ message = ""
+ for level in count_by_level.keys():
+ if message != "":
+ message += ", "
+ count = count_by_level[level]
+ expected_count = expected_count_by_level[level]
+ message += "%d %s (got %d)" % (expected_count, logging.getLevelName(level), count)
+
+ raise RuntimeError(
+ 'Expected %s' % message)
def emit(self, record):
"""Override :meth:`logging.Handler.emit`"""