Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ca08777

Browse filesBrowse files
committed
Add a NumpyTestUtilsMixin to help dict comps.
The difficulty of comparing dicts with nested either (a) ndarrays or (b) number arrays that equal one another up-to-a-tolerance was preventing us from writing stronger tests. This is far from perfect, but it allows you to just throw a set of dict objects in without getting false negative results.
1 parent 806a6e0 commit ca08777
Copy full SHA for ca08777

File tree

Expand file treeCollapse file tree

1 file changed

+82
-0
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+82
-0
lines changed

‎plotly/tests/test_optional/optional_utils.py

Copy file name to clipboardExpand all lines: plotly/tests/test_optional/optional_utils.py
+82Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,93 @@
33
import matplotlib
44
# Force matplotlib to not use any Xwindows backend.
55
matplotlib.use('Agg')
6+
7+
import numpy as np
8+
69
from plotly.matplotlylib import Exporter, PlotlyRenderer
10+
from plotly.tests.utils import is_num_list
11+
from plotly.utils import get_by_path, node_generator
712

813

914
def run_fig(fig):
1015
renderer = PlotlyRenderer()
1116
exporter = Exporter(renderer)
1217
exporter.run(fig)
1318
return renderer
19+
20+
21+
class NumpyTestUtilsMixin(object):
22+
"""Provides some helper functions to make testing easier."""
23+
24+
def _format_path(self, path):
25+
str_path = [repr(p) for p in path]
26+
return '[' + ']['.join(sp for sp in str_path) + ']'
27+
28+
def assert_dict_equal(self, d1, d2, msg=None):
29+
"""
30+
Uses `np.allclose()` on number arrays.
31+
32+
:raises: (AssertionError) Using TestCase's self.failureException
33+
34+
"""
35+
self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
36+
self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
37+
38+
for node, path in node_generator(d1):
39+
40+
# first check that this sub-dict is contained in both dicts
41+
try:
42+
comp_node = get_by_path(d2, path)
43+
except (KeyError, IndexError):
44+
standard_msg = (
45+
'Path {} exists in dict 1, but not dict 2.'
46+
.format(path)
47+
)
48+
self.fail(self._formatMessage(msg, standard_msg))
49+
self.assertIsInstance(
50+
comp_node, dict, 'Value at path {} is not a dict.'.format(path)
51+
)
52+
53+
# check that each key in the first is contained in the second
54+
for key, val in node.items():
55+
if isinstance(val, dict):
56+
continue # this gets tested as its own node
57+
58+
# check that the values at this key are equal
59+
val_path = path + (key, )
60+
try:
61+
comp_val = comp_node[key]
62+
except KeyError:
63+
standard_msg = (
64+
'Path {} exists in dict 1, but not dict 2.'
65+
.format(self._format_path(val_path))
66+
)
67+
self.fail(self._formatMessage(msg, standard_msg))
68+
69+
if (isinstance(val, np.ndarray) or
70+
isinstance(comp_val, np.ndarray)):
71+
if np.array_equal(val, comp_val):
72+
continue
73+
elif val == comp_val:
74+
continue
75+
76+
if is_num_list(val) and is_num_list(comp_val):
77+
if np.allclose(val, comp_val):
78+
continue
79+
80+
standard_msg = (
81+
'Value comparison failed at path {}.\n'
82+
'{} != {}'
83+
.format(self._format_path(val_path), val, comp_val)
84+
)
85+
self.fail(self._formatMessage(msg, standard_msg))
86+
87+
# finally, check that keys in the second are in the first
88+
for key in comp_node:
89+
val_path = path + (key, )
90+
if key not in node:
91+
standard_msg = (
92+
'Path {} exists in dict 2, but not dict 1.'
93+
.format(self._format_path(val_path))
94+
)
95+
self.fail(self._formatMessage(msg, standard_msg))

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.