"""
Helper classes for comparing the equality of two HoloViews objects.
These classes are designed to integrate with unittest.TestCase (see
the tests directory) while making equality testing easily accessible
to the user.
For instance, to test if two Matrix objects are equal you can use:
Comparison.assertEqual(matrix1, matrix2)
This will raise an AssertionError if the two matrix objects are not
equal, including information regarding what exactly failed to match.
Note that this functionality could not be provided using comparison
methods on all objects as comparison operators only return Booleans and
thus would not supply any information regarding *why* two elements are
considered different.
"""
import contextlib
from functools import partial
from unittest import TestCase
from unittest.util import safe_repr
import numpy as np
import pandas as pd
from numpy.testing import assert_array_almost_equal, assert_array_equal
from ..core import (
AdjointLayout,
Dimension,
Dimensioned,
DynamicMap,
Element,
Empty,
GridMatrix,
GridSpace,
HoloMap,
Layout,
NdLayout,
NdOverlay,
Overlay,
)
from ..core.options import Cycle, Options
from ..core.util import cast_array_to_int64, datetime_types, dt_to_int, is_float
from . import * # noqa (All Elements need to support comparison)
[docs]class ComparisonInterface:
"""
This class is designed to allow equality testing to work
seamlessly with unittest.TestCase as a mix-in by implementing a
compatible interface (namely the assertEqual method).
The assertEqual class method is to be overridden by an instance
method of the same name when used as a mix-in with TestCase. The
contents of the equality_type_funcs dictionary is suitable for use
with TestCase.addTypeEqualityFunc.
"""
equality_type_funcs = {}
failureException = AssertionError
[docs] @classmethod
def simple_equality(cls, first, second, msg=None):
"""
Classmethod equivalent to unittest.TestCase method (longMessage = False.)
"""
check = first==second
if not isinstance(check, bool) and hasattr(check, "all"):
check = check.all()
if not check:
standardMsg = f'{safe_repr(first)} != {safe_repr(second)}'
raise cls.failureException(msg or standardMsg)
[docs] @classmethod
def assertEqual(cls, first, second, msg=None):
"""
Classmethod equivalent to unittest.TestCase method
"""
asserter = None
if type(first) is type(second) or (is_float(first) and is_float(second)):
asserter = cls.equality_type_funcs.get(type(first))
if asserter is not None:
if isinstance(asserter, str):
asserter = getattr(cls, asserter)
if asserter is None:
asserter = cls.simple_equality
if msg is None:
asserter(first, second)
else:
asserter(first, second, msg=msg)
[docs]class Comparison(ComparisonInterface):
"""
Class used for comparing two HoloViews objects, including complex
composite objects. Comparisons are available as classmethods, the
most general being the assertEqual method that is intended to work
with any input.
For instance, to test if two Image objects are equal you can use:
Comparison.assertEqual(matrix1, matrix2)
"""
# someone might prefer to use a different function, e.g. assert_all_close
assert_array_almost_equal_fn = partial(assert_array_almost_equal, decimal=6)
@classmethod
def register(cls):
# Float comparisons
cls.equality_type_funcs[float] = cls.compare_floats
cls.equality_type_funcs[np.float32] = cls.compare_floats
cls.equality_type_funcs[np.float64] = cls.compare_floats
# List and tuple comparisons
cls.equality_type_funcs[list] = cls.compare_lists
cls.equality_type_funcs[tuple] = cls.compare_tuples
# Dictionary comparisons
cls.equality_type_funcs[dict] = cls.compare_dictionaries
# Numpy array comparison
cls.equality_type_funcs[np.ndarray] = cls.compare_arrays
cls.equality_type_funcs[np.ma.masked_array] = cls.compare_arrays
# Pandas dataframe comparison
cls.equality_type_funcs[pd.DataFrame] = cls.compare_dataframe
# Dimension objects
cls.equality_type_funcs[Dimension] = cls.compare_dimensions
cls.equality_type_funcs[Dimensioned] = cls.compare_dimensioned # Used in unit tests
cls.equality_type_funcs[Element] = cls.compare_elements # Used in unit tests
# Composition (+ and *)
cls.equality_type_funcs[Overlay] = cls.compare_overlays
cls.equality_type_funcs[Layout] = cls.compare_layouttrees
cls.equality_type_funcs[Empty] = cls.compare_empties
# Annotations
cls.equality_type_funcs[VLine] = cls.compare_vline
cls.equality_type_funcs[HLine] = cls.compare_hline
cls.equality_type_funcs[VSpan] = cls.compare_vspan
cls.equality_type_funcs[HSpan] = cls.compare_hspan
cls.equality_type_funcs[Spline] = cls.compare_spline
cls.equality_type_funcs[Arrow] = cls.compare_arrow
cls.equality_type_funcs[Text] = cls.compare_text
cls.equality_type_funcs[Div] = cls.compare_div
# Path comparisons
cls.equality_type_funcs[Path] = cls.compare_paths
cls.equality_type_funcs[Contours] = cls.compare_contours
cls.equality_type_funcs[Polygons] = cls.compare_polygons
cls.equality_type_funcs[Box] = cls.compare_box
cls.equality_type_funcs[Ellipse] = cls.compare_ellipse
cls.equality_type_funcs[Bounds] = cls.compare_bounds
# Rasters
cls.equality_type_funcs[Image] = cls.compare_image
cls.equality_type_funcs[RGB] = cls.compare_rgb
cls.equality_type_funcs[HSV] = cls.compare_hsv
cls.equality_type_funcs[Raster] = cls.compare_raster
cls.equality_type_funcs[QuadMesh] = cls.compare_quadmesh
cls.equality_type_funcs[Surface] = cls.compare_surface
cls.equality_type_funcs[HeatMap] = cls.compare_dataset
# Geometries
cls.equality_type_funcs[Segments] = cls.compare_segments
cls.equality_type_funcs[Rectangles] = cls.compare_boxes
# Charts
cls.equality_type_funcs[Dataset] = cls.compare_dataset
cls.equality_type_funcs[Curve] = cls.compare_curve
cls.equality_type_funcs[ErrorBars] = cls.compare_errorbars
cls.equality_type_funcs[Spread] = cls.compare_spread
cls.equality_type_funcs[Area] = cls.compare_area
cls.equality_type_funcs[Scatter] = cls.compare_scatter
cls.equality_type_funcs[Scatter3D] = cls.compare_scatter3d
cls.equality_type_funcs[TriSurface] = cls.compare_trisurface
cls.equality_type_funcs[Histogram] = cls.compare_histogram
cls.equality_type_funcs[Bars] = cls.compare_bars
cls.equality_type_funcs[Spikes] = cls.compare_spikes
cls.equality_type_funcs[BoxWhisker] = cls.compare_boxwhisker
cls.equality_type_funcs[VectorField] = cls.compare_vectorfield
# Graphs
cls.equality_type_funcs[Graph] = cls.compare_graph
cls.equality_type_funcs[Nodes] = cls.compare_nodes
cls.equality_type_funcs[EdgePaths] = cls.compare_edgepaths
cls.equality_type_funcs[TriMesh] = cls.compare_trimesh
# Tables
cls.equality_type_funcs[ItemTable] = cls.compare_itemtables
cls.equality_type_funcs[Table] = cls.compare_tables
cls.equality_type_funcs[Points] = cls.compare_points
# Statistical
cls.equality_type_funcs[Bivariate] = cls.compare_bivariate
cls.equality_type_funcs[Distribution] = cls.compare_distribution
cls.equality_type_funcs[HexTiles] = cls.compare_hextiles
# NdMappings
cls.equality_type_funcs[NdLayout] = cls.compare_gridlayout
cls.equality_type_funcs[AdjointLayout] = cls.compare_adjointlayouts
cls.equality_type_funcs[NdOverlay] = cls.compare_ndoverlays
cls.equality_type_funcs[GridSpace] = cls.compare_grids
cls.equality_type_funcs[GridMatrix] = cls.compare_grids
cls.equality_type_funcs[HoloMap] = cls.compare_holomap
cls.equality_type_funcs[DynamicMap] = cls.compare_dynamicmap
# Option objects
cls.equality_type_funcs[Options] = cls.compare_options
cls.equality_type_funcs[Cycle] = cls.compare_cycles
return cls.equality_type_funcs
@classmethod
def compare_dictionaries(cls, d1, d2, msg='Dictionaries'):
keys= set(d1.keys())
keys2 = set(d2.keys())
symmetric_diff = keys ^ keys2
if symmetric_diff:
msg = f"Dictionaries have different sets of keys: {symmetric_diff!r}\n\n"
msg += f"Dictionary 1: {d1}\n"
msg += f"Dictionary 2: {d2}"
raise cls.failureException(msg)
for k in keys:
cls.assertEqual(d1[k], d2[k])
@classmethod
def compare_lists(cls, l1, l2, msg=None):
try:
cls.assertEqual(len(l1), len(l2))
for v1, v2 in zip(l1, l2):
cls.assertEqual(v1, v2)
except AssertionError as e:
raise AssertionError(msg or f'{l1!r} != {l2!r}') from e
@classmethod
def compare_tuples(cls, t1, t2, msg=None):
try:
cls.assertEqual(len(t1), len(t2))
for i1, i2 in zip(t1, t2):
cls.assertEqual(i1, i2)
except AssertionError as e:
raise AssertionError(msg or f'{t1!r} != {t2!r}') from e
#=====================#
# Literal comparisons #
#=====================#
@classmethod
def compare_floats(cls, arr1, arr2, msg='Floats'):
cls.compare_arrays(arr1, arr2, msg)
@classmethod
def compare_arrays(cls, arr1, arr2, msg='Arrays'):
try:
if arr1.dtype.kind == 'M':
arr1 = cast_array_to_int64(arr1.astype('datetime64[ns]'))
if arr2.dtype.kind == 'M':
arr2 = cast_array_to_int64(arr2.astype('datetime64[ns]'))
assert_array_equal(arr1, arr2)
except Exception:
try:
cls.assert_array_almost_equal_fn(arr1, arr2)
except AssertionError as e:
raise cls.failureException(msg + str(e)[11:]) from e
@classmethod
def bounds_check(cls, el1, el2, msg=None):
lbrt1 = el1.bounds.lbrt()
lbrt2 = el2.bounds.lbrt()
try:
for v1, v2 in zip(lbrt1, lbrt2):
if isinstance(v1, datetime_types):
v1 = dt_to_int(v1)
if isinstance(v2, datetime_types):
v2 = dt_to_int(v2)
cls.assert_array_almost_equal_fn(v1, v2)
except AssertionError as e:
raise cls.failureException(f"BoundingBoxes are mismatched: {el1.bounds.lbrt()} != {el2.bounds.lbrt()}.") from e
#=======================================#
# Dimension and Dimensioned comparisons #
#=======================================#
@classmethod
def compare_dimensions(cls, dim1, dim2, msg=None):
# 'Weak' equality semantics
if dim1.name != dim2.name:
raise cls.failureException(f"Dimension names mismatched: {dim1.name} != {dim2.name}")
if dim1.label != dim2.label:
raise cls.failureException(f"Dimension labels mismatched: {dim1.label} != {dim2.label}")
# 'Deep' equality of dimension metadata (all parameters)
dim1_params = dim1.param.values()
dim2_params = dim2.param.values()
if set(dim1_params.keys()) != set(dim2_params.keys()):
raise cls.failureException(f"Dimension parameter sets mismatched: {set(dim1_params.keys())} != {set(dim2_params.keys())}")
for k in dim1_params.keys():
if (dim1.param.objects('existing')[k].__class__.__name__ == 'Callable'
and dim2.param.objects('existing')[k].__class__.__name__ == 'Callable'):
continue
try: # This is needed as two lists are not compared by contents using ==
cls.assertEqual(dim1_params[k], dim2_params[k], msg=None)
except AssertionError as e:
msg = f'Dimension parameter {k!r} mismatched: '
raise cls.failureException(f"{msg}{e!s}") from e
@classmethod
def compare_labelled_data(cls, obj1, obj2, msg=None):
cls.assertEqual(obj1.group, obj2.group, "Group labels mismatched.")
cls.assertEqual(obj1.label, obj2.label, "Labels mismatched.")
@classmethod
def compare_dimension_lists(cls, dlist1, dlist2, msg='Dimension lists'):
if len(dlist1) != len(dlist2):
raise cls.failureException(f'{msg} mismatched')
for d1, d2 in zip(dlist1, dlist2):
cls.assertEqual(d1, d2)
@classmethod
def compare_dimensioned(cls, obj1, obj2, msg=None):
cls.compare_labelled_data(obj1, obj2)
cls.compare_dimension_lists(obj1.vdims, obj2.vdims,
'Value dimension list')
cls.compare_dimension_lists(obj1.kdims, obj2.kdims,
'Key dimension list')
@classmethod
def compare_elements(cls, obj1, obj2, msg=None):
cls.compare_labelled_data(obj1, obj2)
cls.assertEqual(obj1.data, obj2.data)
#===============================#
# Compositional trees (+ and *) #
#===============================#
@classmethod
def compare_trees(cls, el1, el2, msg='Trees'):
if len(el1.keys()) != len(el2.keys()):
raise cls.failureException(f"{msg} have mismatched path counts.")
if el1.keys() != el2.keys():
raise cls.failureException(f"{msg} have mismatched paths.")
for element1, element2 in zip(el1.values(), el2.values()):
cls.assertEqual(element1, element2)
@classmethod
def compare_layouttrees(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
cls.compare_trees(el1, el2, msg='Layouts')
@classmethod
def compare_empties(cls, el1, el2, msg=None):
if not all(isinstance(el, Empty) for el in [el1, el2]):
raise cls.failureException("Compared elements are not both Empty()")
@classmethod
def compare_overlays(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
cls.compare_trees(el1, el2, msg='Overlays')
#================================#
# AttrTree and Map based classes #
#================================#
@classmethod
def compare_ndmappings(cls, el1, el2, msg='NdMappings'):
cls.compare_dimensioned(el1, el2)
if len(el1.keys()) != len(el2.keys()):
raise cls.failureException(f"{msg} have different numbers of keys.")
if set(el1.keys()) != set(el2.keys()):
diff1 = [el for el in el1.keys() if el not in el2.keys()]
diff2 = [el for el in el2.keys() if el not in el1.keys()]
raise cls.failureException(f"{msg} have different sets of keys. "
+ f"In first, not second {diff1}. "
+ f"In second, not first: {diff2}.")
for element1, element2 in zip(el1, el2):
cls.assertEqual(element1, element2)
@classmethod
def compare_holomap(cls, el1, el2, msg='HoloMaps'):
cls.compare_dimensioned(el1, el2)
cls.compare_ndmappings(el1, el2, msg)
@classmethod
def compare_dynamicmap(cls, el1, el2, msg='DynamicMap'):
cls.compare_dimensioned(el1, el2)
cls.compare_ndmappings(el1, el2, msg)
@classmethod
def compare_gridlayout(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
if len(el1) != len(el2):
raise cls.failureException("Layouts have different sizes.")
if set(el1.keys()) != set(el2.keys()):
raise cls.failureException("Layouts have different keys.")
for element1, element2 in zip(el1, el2):
cls.assertEqual(element1,element2)
@classmethod
def compare_ndoverlays(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
if len(el1) != len(el2):
raise cls.failureException("NdOverlays have different lengths.")
for (layer1, layer2) in zip(el1, el2):
cls.assertEqual(layer1, layer2)
@classmethod
def compare_adjointlayouts(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
for element1, element2 in zip(el1, el1):
cls.assertEqual(element1, element2)
#=============#
# Annotations #
#=============#
@classmethod
def compare_annotation(cls, el1, el2, msg='Annotation'):
cls.compare_dimensioned(el1, el2)
cls.assertEqual(el1.data, el2.data)
@classmethod
def compare_hline(cls, el1, el2, msg='HLine'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_vline(cls, el1, el2, msg='VLine'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_vspan(cls, el1, el2, msg='VSpan'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_hspan(cls, el1, el2, msg='HSpan'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_spline(cls, el1, el2, msg='Spline'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_arrow(cls, el1, el2, msg='Arrow'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_text(cls, el1, el2, msg='Text'):
cls.compare_annotation(el1, el2, msg=msg)
@classmethod
def compare_div(cls, el1, el2, msg='Div'):
cls.compare_annotation(el1, el2, msg=msg)
#=======#
# Paths #
#=======#
@classmethod
def compare_paths(cls, el1, el2, msg='Path'):
cls.compare_dataset(el1, el2, msg)
paths1 = el1.split()
paths2 = el2.split()
if len(paths1) != len(paths2):
raise cls.failureException(f"{msg} objects do not have a matching number of paths.")
for p1, p2 in zip(paths1, paths2):
cls.compare_dataset(p1, p2, f'{msg} data')
@classmethod
def compare_contours(cls, el1, el2, msg='Contours'):
cls.compare_paths(el1, el2, msg=msg)
@classmethod
def compare_polygons(cls, el1, el2, msg='Polygons'):
cls.compare_paths(el1, el2, msg=msg)
@classmethod
def compare_box(cls, el1, el2, msg='Box'):
cls.compare_paths(el1, el2, msg=msg)
@classmethod
def compare_ellipse(cls, el1, el2, msg='Ellipse'):
cls.compare_paths(el1, el2, msg=msg)
@classmethod
def compare_bounds(cls, el1, el2, msg='Bounds'):
cls.compare_paths(el1, el2, msg=msg)
#========#
# Charts #
#========#
@classmethod
def compare_dataset(cls, el1, el2, msg='Dataset'):
cls.compare_dimensioned(el1, el2)
tabular = not (el1.interface.gridded and el2.interface.gridded)
dimension_data = [(d, el1.dimension_values(d, expanded=tabular),
el2.dimension_values(d, expanded=tabular))
for d in el1.kdims]
dimension_data += [(d, el1.dimension_values(d, flat=tabular),
el2.dimension_values(d, flat=tabular))
for d in el1.vdims]
if el1.shape[0] != el2.shape[0]:
raise AssertionError("%s not of matching length, %d vs. %d."
% (msg, el1.shape[0], el2.shape[0]))
for dim, d1, d2 in dimension_data:
with contextlib.suppress(Exception):
np.testing.assert_equal(d1, d2)
continue # if equal, no need to check further
if d1.dtype != d2.dtype:
failure_msg = (
f"{msg} {dim.pprint_label} columns have different type. "
f"First has type {d1}, and second has type {d2}."
)
raise cls.failureException(failure_msg)
if d1.dtype.kind in 'SUOV':
if list(d1) == list(d2):
failure_msg = f"{msg} along dimension {dim.pprint_label} not equal."
raise cls.failureException(failure_msg)
else:
cls.compare_arrays(d1, d2, msg)
@classmethod
def compare_curve(cls, el1, el2, msg='Curve'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_errorbars(cls, el1, el2, msg='ErrorBars'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_spread(cls, el1, el2, msg='Spread'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_area(cls, el1, el2, msg='Area'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_scatter(cls, el1, el2, msg='Scatter'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_scatter3d(cls, el1, el2, msg='Scatter3D'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_trisurface(cls, el1, el2, msg='TriSurface'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_histogram(cls, el1, el2, msg='Histogram'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_points(cls, el1, el2, msg='Points'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_vectorfield(cls, el1, el2, msg='VectorField'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_bars(cls, el1, el2, msg='Bars'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_spikes(cls, el1, el2, msg='Spikes'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_boxwhisker(cls, el1, el2, msg='BoxWhisker'):
cls.compare_dataset(el1, el2, msg)
#============#
# Geometries #
#============#
@classmethod
def compare_segments(cls, el1, el2, msg='Segments'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_boxes(cls, el1, el2, msg='Rectangles'):
cls.compare_dataset(el1, el2, msg)
#=========#
# Graphs #
#=========#
@classmethod
def compare_graph(cls, el1, el2, msg='Graph'):
cls.compare_dataset(el1, el2, msg)
cls.compare_nodes(el1.nodes, el2.nodes, msg)
if el1._edgepaths or el2._edgepaths:
cls.compare_edgepaths(el1.edgepaths, el2.edgepaths, msg)
@classmethod
def compare_trimesh(cls, el1, el2, msg='TriMesh'):
cls.compare_graph(el1, el2, msg)
@classmethod
def compare_nodes(cls, el1, el2, msg='Nodes'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_edgepaths(cls, el1, el2, msg='Nodes'):
cls.compare_paths(el1, el2, msg)
#=========#
# Rasters #
#=========#
@classmethod
def compare_raster(cls, el1, el2, msg='Raster'):
cls.compare_dimensioned(el1, el2)
cls.compare_arrays(el1.data, el2.data, msg)
@classmethod
def compare_quadmesh(cls, el1, el2, msg='QuadMesh'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_heatmap(cls, el1, el2, msg='HeatMap'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_image(cls, el1, el2, msg='Image'):
cls.bounds_check(el1,el2)
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_rgb(cls, el1, el2, msg='RGB'):
cls.bounds_check(el1,el2)
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_hsv(cls, el1, el2, msg='HSV'):
cls.bounds_check(el1,el2)
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_surface(cls, el1, el2, msg='Surface'):
cls.bounds_check(el1,el2)
cls.compare_dataset(el1, el2, msg)
#========#
# Tables #
#========#
@classmethod
def compare_itemtables(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
if el1.rows != el2.rows:
raise cls.failureException("ItemTables have different numbers of rows.")
if el1.cols != el2.cols:
raise cls.failureException("ItemTables have different numbers of columns.")
if [d.name for d in el1.vdims] != [d.name for d in el2.vdims]:
raise cls.failureException("ItemTables have different Dimensions.")
@classmethod
def compare_tables(cls, el1, el2, msg='Table'):
cls.compare_dataset(el1, el2, msg)
#========#
# Pandas #
#========#
@classmethod
def compare_dataframe(cls, df1, df2, msg='DFrame'):
from pandas.testing import assert_frame_equal
try:
assert_frame_equal(df1, df2)
except AssertionError as e:
raise cls.failureException(f'{msg}: {e}') from e
#============#
# Statistics #
#============#
@classmethod
def compare_distribution(cls, el1, el2, msg='Distribution'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_bivariate(cls, el1, el2, msg='Bivariate'):
cls.compare_dataset(el1, el2, msg)
@classmethod
def compare_hextiles(cls, el1, el2, msg='HexTiles'):
cls.compare_dataset(el1, el2, msg)
#=======#
# Grids #
#=======#
@classmethod
def _compare_grids(cls, el1, el2, name):
if len(el1.keys()) != len(el2.keys()):
raise cls.failureException(f"{name}s have different numbers of items.")
if set(el1.keys()) != set(el2.keys()):
raise cls.failureException(f"{name}s have different keys.")
if len(el1) != len(el2):
raise cls.failureException(f"{name}s have different depths.")
for element1, element2 in zip(el1, el2):
cls.assertEqual(element1, element2)
@classmethod
def compare_grids(cls, el1, el2, msg=None):
cls.compare_dimensioned(el1, el2)
cls._compare_grids(el1, el2, 'GridSpace')
#=========#
# Options #
#=========#
@classmethod
def compare_options(cls, options1, options2, msg=None):
cls.assertEqual(options1.kwargs, options2.kwargs)
@classmethod
def compare_cycles(cls, cycle1, cycle2, msg=None):
cls.assertEqual(cycle1.values, cycle2.values)
[docs]class ComparisonTestCase(Comparison, TestCase):
"""
Class to integrate the Comparison class with unittest.TestCase.
"""
def __init__(self, *args, **kwargs):
TestCase.__init__(self, *args, **kwargs)
registry = Comparison.register()
for k, v in registry.items():
self.addTypeEqualityFunc(k, v)
_assert_element_equal = ComparisonTestCase().assertEqual
def assert_element_equal(element1, element2):
# Filter non-holoviews elements
hv_types = (Element, Layout)
if not isinstance(element1, hv_types):
raise TypeError(f"First argument is not an allowed type but a {type(element1).__name__!r}.")
if not isinstance(element2, hv_types):
raise TypeError(f"Second argument is not an allowed type but a {type(element2).__name__!r}.")
_assert_element_equal(element1, element2)