Skip to content

Commit 72568d2

Browse files
authored
Create test_fraction.py
1 parent e51508c commit 72568d2

1 file changed

Lines changed: 329 additions & 0 deletions

File tree

tests/test_fraction.py

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
#!/usr/bin/env python
2+
"""Tests for fractions.Fraction support in DeepDiff."""
3+
import pytest
4+
import logging
5+
from fractions import Fraction
6+
from decimal import Decimal
7+
from functools import partial
8+
from deepdiff import DeepDiff, DeepHash
9+
from deepdiff.deephash import prepare_string_for_hashing
10+
from deepdiff.helper import number_to_string
11+
from deepdiff.serialization import json_dumps, json_loads
12+
13+
14+
logging.disable(logging.CRITICAL)
15+
16+
# Only the prep part of DeepHash. We don't need to test the actual hash function.
17+
DeepHashPrep = partial(DeepHash, apply_hash=False)
18+
19+
20+
class TestFractionDiff:
21+
"""Tests for DeepDiff with Fraction objects."""
22+
23+
def test_fraction_value_change(self):
24+
t1 = {1: Fraction(1, 3)}
25+
t2 = {1: Fraction(2, 3)}
26+
ddiff = DeepDiff(t1, t2)
27+
result = {
28+
'values_changed': {
29+
'root[1]': {
30+
'new_value': Fraction(2, 3),
31+
'old_value': Fraction(1, 3)
32+
}
33+
}
34+
}
35+
assert result == ddiff
36+
37+
def test_fraction_no_change(self):
38+
t1 = Fraction(1, 3)
39+
t2 = Fraction(1, 3)
40+
ddiff = DeepDiff(t1, t2)
41+
assert {} == ddiff
42+
43+
def test_fraction_vs_float_type_change(self):
44+
t1 = Fraction(1, 2)
45+
t2 = 0.5
46+
ddiff = DeepDiff(t1, t2)
47+
assert 'type_changes' in ddiff
48+
assert ddiff['type_changes']['root']['old_type'] == Fraction
49+
assert ddiff['type_changes']['root']['new_type'] == float
50+
51+
def test_fraction_vs_int_type_change(self):
52+
t1 = Fraction(2, 1)
53+
t2 = 2
54+
ddiff = DeepDiff(t1, t2)
55+
assert 'type_changes' in ddiff
56+
assert ddiff['type_changes']['root']['old_type'] == Fraction
57+
assert ddiff['type_changes']['root']['new_type'] == int
58+
59+
def test_fraction_vs_decimal_type_change(self):
60+
t1 = Fraction(1, 2)
61+
t2 = Decimal('0.5')
62+
ddiff = DeepDiff(t1, t2)
63+
assert 'type_changes' in ddiff
64+
65+
def test_fraction_in_dict(self):
66+
t1 = {"a": Fraction(1, 3), "b": Fraction(2, 3)}
67+
t2 = {"a": Fraction(1, 3), "b": Fraction(3, 4)}
68+
ddiff = DeepDiff(t1, t2)
69+
assert 'values_changed' in ddiff
70+
assert "root['b']" in ddiff['values_changed']
71+
72+
def test_fraction_in_list(self):
73+
t1 = [Fraction(1, 2), Fraction(1, 3)]
74+
t2 = [Fraction(1, 2), Fraction(1, 4)]
75+
ddiff = DeepDiff(t1, t2)
76+
result = {
77+
'values_changed': {
78+
'root[1]': {
79+
'new_value': Fraction(1, 4),
80+
'old_value': Fraction(1, 3)
81+
}
82+
}
83+
}
84+
assert result == ddiff
85+
86+
def test_fraction_nested(self):
87+
t1 = {"data": [{"val": Fraction(1, 3)}]}
88+
t2 = {"data": [{"val": Fraction(2, 3)}]}
89+
ddiff = DeepDiff(t1, t2)
90+
assert 'values_changed' in ddiff
91+
assert "root['data'][0]['val']" in ddiff['values_changed']
92+
93+
94+
class TestFractionIgnoreNumericTypeChanges:
95+
"""Tests for ignore_numeric_type_changes with Fraction."""
96+
97+
def test_fraction_vs_float_ignored(self):
98+
t1 = Fraction(1, 2)
99+
t2 = 0.5
100+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
101+
assert {} == ddiff
102+
103+
def test_fraction_vs_int_ignored(self):
104+
t1 = Fraction(2, 1)
105+
t2 = 2
106+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
107+
assert {} == ddiff
108+
109+
def test_fraction_vs_decimal_ignored(self):
110+
t1 = Fraction(1, 2)
111+
t2 = Decimal('0.5')
112+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
113+
assert {} == ddiff
114+
115+
def test_fraction_vs_float_different_values(self):
116+
t1 = Fraction(1, 3)
117+
t2 = 0.5
118+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
119+
assert 'values_changed' in ddiff
120+
121+
def test_fraction_vs_float_in_list_ignored(self):
122+
t1 = [Fraction(1, 2), Fraction(3, 4)]
123+
t2 = [0.5, 0.75]
124+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
125+
assert {} == ddiff
126+
127+
def test_fraction_vs_int_in_dict_ignored(self):
128+
t1 = {"a": Fraction(5, 1), "b": Fraction(10, 1)}
129+
t2 = {"a": 5, "b": 10}
130+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
131+
assert {} == ddiff
132+
133+
@pytest.mark.parametrize("t1, t2, significant_digits, result", [
134+
([0.5], [Fraction(1, 2)], 5, {}),
135+
([Fraction(1, 3)], [0.333333], 5, {}),
136+
([Fraction(1, 3)], [Decimal('0.33333')], 5, {}),
137+
([1], [Fraction(1, 1)], 5, {}),
138+
([-Fraction(1, 2)], [-0.5], 5, {}),
139+
([Fraction(22, 7)], [3.14286], 4, {}),
140+
])
141+
def test_ignore_numeric_type_changes_with_fraction(self, t1, t2, significant_digits, result):
142+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True, significant_digits=significant_digits)
143+
assert result == ddiff
144+
145+
146+
class TestFractionSignificantDigits:
147+
"""Tests for significant_digits with Fraction."""
148+
149+
def test_fraction_significant_digits_equal(self):
150+
t1 = Fraction(1, 3) # 0.333...
151+
t2 = Fraction(334, 1000) # 0.334
152+
ddiff = DeepDiff(t1, t2, significant_digits=2)
153+
assert {} == ddiff
154+
155+
def test_fraction_significant_digits_different(self):
156+
t1 = Fraction(1, 3) # 0.333...
157+
t2 = Fraction(1, 2) # 0.5
158+
ddiff = DeepDiff(t1, t2, significant_digits=1)
159+
assert 'values_changed' in ddiff
160+
161+
@pytest.mark.parametrize("test_num, t1, t2, significant_digits, number_format_notation, result", [
162+
(1, Fraction(1, 3), Fraction(334, 1000), 2, "f", {}),
163+
(2, Fraction(1, 2), Fraction(499, 1000), 2, "f", {}),
164+
(3, Fraction(1, 2), Fraction(1, 3), 0, "f", {}),
165+
(4, Fraction(1, 2), Fraction(1, 3), 1, "f",
166+
{'values_changed': {'root': {'new_value': Fraction(1, 3), 'old_value': Fraction(1, 2)}}}),
167+
(5, Fraction(22, 7), Fraction(355, 113), 2, "f", {}), # Two approximations of pi agree to 2 digits
168+
(6, Fraction(22, 7), Fraction(355, 113), 3, "f",
169+
{'values_changed': {'root': {'new_value': Fraction(355, 113), 'old_value': Fraction(22, 7)}}}),
170+
])
171+
def test_fraction_significant_digits_and_notation(self, test_num, t1, t2, significant_digits, number_format_notation, result):
172+
ddiff = DeepDiff(t1, t2, significant_digits=significant_digits,
173+
number_format_notation=number_format_notation)
174+
assert result == ddiff, f"test_fraction_significant_digits_and_notation #{test_num} failed."
175+
176+
177+
class TestFractionMathEpsilon:
178+
"""Tests for math_epsilon with Fraction."""
179+
180+
def test_fraction_math_epsilon_close(self):
181+
d1 = {"a": Fraction(7175, 1000)}
182+
d2 = {"a": Fraction(7174, 1000)}
183+
res = DeepDiff(d1, d2, math_epsilon=0.01)
184+
assert res == {}
185+
186+
def test_fraction_math_epsilon_not_close(self):
187+
d1 = {"a": Fraction(7175, 1000)}
188+
d2 = {"a": Fraction(7174, 1000)}
189+
res = DeepDiff(d1, d2, math_epsilon=0.0001)
190+
assert 'values_changed' in res
191+
192+
def test_fraction_vs_float_math_epsilon(self):
193+
d1 = {"a": Fraction(1, 3)}
194+
d2 = {"a": 0.333}
195+
res = DeepDiff(d1, d2, math_epsilon=0.001, ignore_numeric_type_changes=True)
196+
assert res == {}
197+
198+
199+
class TestFractionIgnoreOrder:
200+
"""Tests for ignore_order with Fraction."""
201+
202+
def test_fraction_ignore_order(self):
203+
t1 = [{1: Fraction(1, 3)}, {2: Fraction(2, 3)}]
204+
t2 = [{2: Fraction(2, 3)}, {1: Fraction(1, 3)}]
205+
ddiff = DeepDiff(t1, t2, ignore_order=True)
206+
assert {} == ddiff
207+
208+
def test_fraction_ignore_order_with_change(self):
209+
t1 = [Fraction(1, 2), Fraction(1, 3)]
210+
t2 = [Fraction(1, 3), Fraction(1, 4)]
211+
ddiff = DeepDiff(t1, t2, ignore_order=True)
212+
assert ddiff != {}
213+
214+
215+
class TestFractionAsKey:
216+
"""Tests for Fraction used as dictionary key."""
217+
218+
def test_fraction_as_dict_key(self):
219+
t1 = {Fraction(1, 2): "half"}
220+
t2 = {Fraction(1, 2): "one half"}
221+
ddiff = DeepDiff(t1, t2)
222+
assert 'values_changed' in ddiff
223+
224+
def test_fraction_vs_float_key(self):
225+
# Fraction(1, 2) == 0.5 and hash(Fraction(1, 2)) == hash(0.5) in Python,
226+
# so they resolve to the same dict key. DeepDiff sees no difference.
227+
t1 = {Fraction(1, 2): "value"}
228+
t2 = {0.5: "value"}
229+
ddiff = DeepDiff(t1, t2)
230+
assert ddiff == {}
231+
232+
def test_fraction_vs_float_key_ignore_numeric(self):
233+
t1 = {Fraction(1, 2): "value"}
234+
t2 = {0.5: "value"}
235+
ddiff = DeepDiff(t1, t2, ignore_numeric_type_changes=True)
236+
assert {} == ddiff
237+
238+
239+
class TestFractionNumberToString:
240+
"""Tests for number_to_string with Fraction."""
241+
242+
@pytest.mark.parametrize("t1, t2, significant_digits, number_format_notation, expected_result", [
243+
(Fraction(1, 3), 0.333333, 5, "f", True),
244+
(Fraction(1, 2), 0.5, 5, "f", True),
245+
(Fraction(1, 2), 0.5, 5, "e", True),
246+
(Fraction(1, 3), Fraction(1, 4), 1, "f", ('0.3', '0.2')),
247+
(Fraction(22, 7), 3.14286, 4, "f", True),
248+
(Fraction(0), 0.0, 5, "f", True),
249+
(Fraction(-1, 2), -0.5, 5, "f", True),
250+
])
251+
def test_number_to_string_fraction(self, t1, t2, significant_digits, number_format_notation, expected_result):
252+
st1 = number_to_string(t1, significant_digits=significant_digits, number_format_notation=number_format_notation)
253+
st2 = number_to_string(t2, significant_digits=significant_digits, number_format_notation=number_format_notation)
254+
if expected_result is True:
255+
assert st1 == st2
256+
else:
257+
assert st1 == expected_result[0]
258+
assert st2 == expected_result[1]
259+
260+
261+
class TestFractionDeepHash:
262+
"""Tests for DeepHash with Fraction."""
263+
264+
def test_fraction_hash(self):
265+
result = DeepHash(Fraction(1, 3))
266+
assert result[Fraction(1, 3)]
267+
268+
def test_fraction_same_value_same_hash(self):
269+
result1 = DeepHash(Fraction(1, 2))
270+
result2 = DeepHash(Fraction(1, 2))
271+
assert result1[Fraction(1, 2)] == result2[Fraction(1, 2)]
272+
273+
def test_fraction_different_value_different_hash(self):
274+
result1 = DeepHash(Fraction(1, 2))
275+
result2 = DeepHash(Fraction(1, 3))
276+
assert result1[Fraction(1, 2)] != result2[Fraction(1, 3)]
277+
278+
def test_fraction_vs_float_hash_different_by_default(self):
279+
result1 = DeepHash(Fraction(1, 2))
280+
result2 = DeepHash(0.5)
281+
assert result1[Fraction(1, 2)] != result2[0.5]
282+
283+
def test_fraction_vs_float_hash_same_with_ignore_numeric_type(self):
284+
result1 = DeepHash(Fraction(1, 2), ignore_numeric_type_changes=True)
285+
result2 = DeepHash(0.5, ignore_numeric_type_changes=True)
286+
assert result1[Fraction(1, 2)] == result2[0.5]
287+
288+
def test_fraction_hash_prep(self):
289+
result = DeepHashPrep(Fraction(1, 3))
290+
assert 'Fraction' in result[Fraction(1, 3)]
291+
292+
def test_fraction_hash_prep_ignore_numeric_type(self):
293+
result = DeepHashPrep(Fraction(1, 2), ignore_numeric_type_changes=True)
294+
assert 'number' in result[Fraction(1, 2)]
295+
296+
def test_fraction_hash_significant_digits(self):
297+
r1 = DeepHashPrep(Fraction(1, 3), significant_digits=2)
298+
r2 = DeepHashPrep(Fraction(334, 1000), significant_digits=2)
299+
assert r1[Fraction(1, 3)] == r2[Fraction(334, 1000)]
300+
301+
302+
class TestFractionSerialization:
303+
"""Tests for JSON serialization of Fraction values."""
304+
305+
def test_fraction_to_json(self):
306+
t1 = Fraction(1, 3)
307+
t2 = Fraction(2, 3)
308+
ddiff = DeepDiff(t1, t2)
309+
json_str = ddiff.to_json()
310+
assert json_str is not None
311+
assert '"new_value"' in json_str
312+
313+
def test_fraction_integer_value_serialization(self):
314+
"""Fraction with denominator 1 should serialize as int."""
315+
result = json_dumps(Fraction(5, 1))
316+
assert result == '5'
317+
318+
def test_fraction_float_value_serialization(self):
319+
"""Fraction with denominator != 1 should serialize as float."""
320+
result = json_dumps(Fraction(1, 2))
321+
assert result == '0.5'
322+
323+
def test_fraction_json_roundtrip(self):
324+
t1 = {"a": Fraction(1, 2), "b": [1, 2]}
325+
t2 = {"a": Fraction(3, 4), "b": [1, 3]}
326+
ddiff = DeepDiff(t1, t2)
327+
json_str = ddiff.to_json()
328+
loaded = json_loads(json_str)
329+
assert loaded is not None

0 commit comments

Comments
 (0)