diff --git a/tests/__init__.py b/tests/__init__.py index c191137875..57f40b7072 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,8 @@ import os, sys +import param +import logging + +from holoviews.element.comparison import ComparisonTestCase try: # Standardize backend due to random inconsistencies @@ -9,3 +13,77 @@ cwd = os.path.abspath(os.path.split(__file__)[0]) sys.path.insert(0, os.path.join(cwd, '..')) + + +class MockLoggingHandler(logging.Handler): + """ + Mock logging handler to check for expected logs used by + LoggingComparisonTestCase. + + Messages are available from an instance's ``messages`` dict, in + order, indexed by a lowercase log level string (e.g., 'debug', + 'info', etc.).""" + + def __init__(self, *args, **kwargs): + self.messages = {'DEBUG': [], 'INFO': [], 'WARNING': [], + 'ERROR': [], 'CRITICAL': [], 'VERBOSE':[]} + super(MockLoggingHandler, self).__init__(*args, **kwargs) + + def emit(self, record): + "Store a message to the instance's messages dictionary" + self.acquire() + try: + self.messages[record.levelname].append(record.getMessage()) + finally: + self.release() + + def reset(self): + self.acquire() + try: + for message_list in self.messages.values(): + message_list = [] + finally: + self.release() + + def tail(self, level, n=1): + "Returns the last n lines captured at the given level" + return [str(el) for el in self.messages[level][-n:]] + + def assertEndsWith(self, level, substring): + """ + Assert that the last line captured at the given level ends with + a particular substring. + """ + methods = {'WARNING':'param.warning()', 'INFO':'param.message()', + 'VERBOSE':'param.verbose()', 'DEBUG':'param.debug()'} + msg='\n\n{method}: {last_line}\ndoes not end with:\n{substring}' + last_line = self.tail(level, n=1) + if len(last_line) == 0: + raise AssertionError('Missing {method} output: {repr(substring)}'.format( + method=methods[level], substring=repr(substring))) + if not last_line[0].endswith(substring): + raise AssertionError(msg.format(method=methods[level], + last_line=repr(last_line[0]), + substring=repr(substring))) + + + +class LoggingComparisonTestCase(ComparisonTestCase): + """ + ComparisonTestCase with support for capturing param logging output. + + Subclasses must call super setUp to make the + tests independent. Testing can then be done via the + self.log_handler.tail and self.log_handler.assertEndsWith methods. + """ + + @classmethod + def setUpClass(cls): + super(LoggingComparisonTestCase, cls).setUpClass() + log = param.parameterized.get_logger() + cls.log_handler = MockLoggingHandler(level='DEBUG') + log.addHandler(cls.log_handler) + + def setUp(self): + super(LoggingComparisonTestCase, self).setUp() + self.log_handler.reset() diff --git a/tests/testdimensions.py b/tests/testdimensions.py index 019d7bc646..172a1c2cbf 100644 --- a/tests/testdimensions.py +++ b/tests/testdimensions.py @@ -4,6 +4,7 @@ from unittest import SkipTest from holoviews.core import Dimensioned, Dimension from holoviews.element.comparison import ComparisonTestCase +from . import LoggingComparisonTestCase import numpy as np try: @@ -11,7 +12,10 @@ except: pd = None -class DimensionNameLabelTest(ComparisonTestCase): +class DimensionNameLabelTest(LoggingComparisonTestCase): + + def setUp(self): + super(DimensionNameLabelTest, self).setUp() def test_dimension_name(self): dim = Dimension('test') @@ -35,8 +39,9 @@ def test_dimension_label_kwarg(self): self.assertEqual(dim.label, 'A test') def test_dimension_label_kwarg_and_tuple(self): - # Should work but issue a warning dim = Dimension(('test', 'A test'), label='Another test') + substr = "Using label as supplied by keyword ('Another test'), ignoring tuple value 'A test'" + self.log_handler.assertEndsWith('WARNING', substr) self.assertEqual(dim.label, 'Another test') def test_dimension_invalid_name(self): diff --git a/tests/testdynamic.py b/tests/testdynamic.py index 13c200a872..14705405d4 100644 --- a/tests/testdynamic.py +++ b/tests/testdynamic.py @@ -399,7 +399,7 @@ def callback(): self.assertEqual(list(grid.keys()), [(i, j) for i in range(1, 3) for j in range(1, 3)]) self.assertEqual(grid[(0, 1)][()], Image(np.array([[1, 1], [2, 3]]))) - + def test_dynamic_collate_grid_with_integer_stream_mapping(self): def callback(): return GridSpace({(i, j): Image(np.array([[i, j], [2, 3]]))