Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | # Copyright (c) 2004 Python Software Foundation. |
2 | # All rights reserved. | |
3 | ||
4 | # Written by Eric Price <eprice at tjhsst.edu> | |
5 | # and Facundo Batista <facundo at taniquetil.com.ar> | |
6 | # and Raymond Hettinger <python at rcn.com> | |
7 | # and Aahz (aahz at pobox.com) | |
8 | # and Tim Peters | |
9 | ||
10 | """ | |
11 | These are the test cases for the Decimal module. | |
12 | ||
13 | There are two groups of tests, Arithmetic and Behaviour. The former test | |
14 | the Decimal arithmetic using the tests provided by Mike Cowlishaw. The latter | |
15 | test the pythonic behaviour according to PEP 327. | |
16 | ||
17 | Cowlishaw's tests can be downloaded from: | |
18 | ||
19 | www2.hursley.ibm.com/decimal/dectest.zip | |
20 | ||
21 | This test module can be called from command line with one parameter (Arithmetic | |
22 | or Behaviour) to test each part, or without parameter to test both parts. If | |
23 | you're working through IDLE, you can import this test module and call test_main() | |
24 | with the corresponding argument. | |
25 | """ | |
26 | ||
27 | import unittest | |
28 | import glob | |
29 | import os, sys | |
30 | import pickle, copy | |
31 | from decimal import * | |
32 | from test.test_support import TestSkipped, run_unittest, run_doctest, is_resource_enabled | |
33 | import random | |
34 | try: | |
35 | import threading | |
36 | except ImportError: | |
37 | threading = None | |
38 | ||
39 | # Useful Test Constant | |
40 | Signals = getcontext().flags.keys() | |
41 | ||
42 | # Tests are built around these assumed context defaults | |
43 | DefaultContext.prec=9 | |
44 | DefaultContext.rounding=ROUND_HALF_EVEN | |
45 | DefaultContext.traps=dict.fromkeys(Signals, 0) | |
46 | setcontext(DefaultContext) | |
47 | ||
48 | ||
49 | TESTDATADIR = 'decimaltestdata' | |
50 | if __name__ == '__main__': | |
51 | file = sys.argv[0] | |
52 | else: | |
53 | file = __file__ | |
54 | testdir = os.path.dirname(file) or os.curdir | |
55 | directory = testdir + os.sep + TESTDATADIR + os.sep | |
56 | ||
57 | skip_expected = not os.path.isdir(directory) | |
58 | ||
59 | # Make sure it actually raises errors when not expected and caught in flags | |
60 | # Slower, since it runs some things several times. | |
61 | EXTENDEDERRORTEST = False | |
62 | ||
63 | ||
64 | #Map the test cases' error names to the actual errors | |
65 | ||
66 | ErrorNames = {'clamped' : Clamped, | |
67 | 'conversion_syntax' : InvalidOperation, | |
68 | 'division_by_zero' : DivisionByZero, | |
69 | 'division_impossible' : InvalidOperation, | |
70 | 'division_undefined' : InvalidOperation, | |
71 | 'inexact' : Inexact, | |
72 | 'invalid_context' : InvalidOperation, | |
73 | 'invalid_operation' : InvalidOperation, | |
74 | 'overflow' : Overflow, | |
75 | 'rounded' : Rounded, | |
76 | 'subnormal' : Subnormal, | |
77 | 'underflow' : Underflow} | |
78 | ||
79 | ||
80 | def Nonfunction(*args): | |
81 | """Doesn't do anything.""" | |
82 | return None | |
83 | ||
84 | RoundingDict = {'ceiling' : ROUND_CEILING, #Maps test-case names to roundings. | |
85 | 'down' : ROUND_DOWN, | |
86 | 'floor' : ROUND_FLOOR, | |
87 | 'half_down' : ROUND_HALF_DOWN, | |
88 | 'half_even' : ROUND_HALF_EVEN, | |
89 | 'half_up' : ROUND_HALF_UP, | |
90 | 'up' : ROUND_UP} | |
91 | ||
92 | # Name adapter to be able to change the Decimal and Context | |
93 | # interface without changing the test files from Cowlishaw | |
94 | nameAdapter = {'toeng':'to_eng_string', | |
95 | 'tosci':'to_sci_string', | |
96 | 'samequantum':'same_quantum', | |
97 | 'tointegral':'to_integral', | |
98 | 'remaindernear':'remainder_near', | |
99 | 'divideint':'divide_int', | |
100 | 'squareroot':'sqrt', | |
101 | 'apply':'_apply', | |
102 | } | |
103 | ||
104 | class DecimalTest(unittest.TestCase): | |
105 | """Class which tests the Decimal class against the test cases. | |
106 | ||
107 | Changed for unittest. | |
108 | """ | |
109 | def setUp(self): | |
110 | self.context = Context() | |
111 | for key in DefaultContext.traps.keys(): | |
112 | DefaultContext.traps[key] = 1 | |
113 | self.ignore_list = ['#'] | |
114 | # Basically, a # means return NaN InvalidOperation. | |
115 | # Different from a sNaN in trim | |
116 | ||
117 | self.ChangeDict = {'precision' : self.change_precision, | |
118 | 'rounding' : self.change_rounding_method, | |
119 | 'maxexponent' : self.change_max_exponent, | |
120 | 'minexponent' : self.change_min_exponent, | |
121 | 'clamp' : self.change_clamp} | |
122 | ||
123 | def tearDown(self): | |
124 | """Cleaning up enviroment.""" | |
125 | # leaving context in original state | |
126 | for key in DefaultContext.traps.keys(): | |
127 | DefaultContext.traps[key] = 0 | |
128 | return | |
129 | ||
130 | def eval_file(self, file): | |
131 | global skip_expected | |
132 | if skip_expected: | |
133 | raise TestSkipped | |
134 | return | |
135 | for line in open(file).xreadlines(): | |
136 | line = line.replace('\r\n', '').replace('\n', '') | |
137 | #print line | |
138 | try: | |
139 | t = self.eval_line(line) | |
140 | except InvalidOperation: | |
141 | print 'Error in test cases:' | |
142 | print line | |
143 | continue | |
144 | except DecimalException, exception: | |
145 | #Exception raised where there shoudn't have been one. | |
146 | self.fail('Exception "'+exception.__class__.__name__ + '" raised on line '+line) | |
147 | ||
148 | return | |
149 | ||
150 | def eval_line(self, s): | |
151 | if s.find(' -> ') >= 0 and s[:2] != '--' and not s.startswith(' --'): | |
152 | s = (s.split('->')[0] + '->' + | |
153 | s.split('->')[1].split('--')[0]).strip() | |
154 | else: | |
155 | s = s.split('--')[0].strip() | |
156 | ||
157 | for ignore in self.ignore_list: | |
158 | if s.find(ignore) >= 0: | |
159 | #print s.split()[0], 'NotImplemented--', ignore | |
160 | return | |
161 | if not s: | |
162 | return | |
163 | elif ':' in s: | |
164 | return self.eval_directive(s) | |
165 | else: | |
166 | return self.eval_equation(s) | |
167 | ||
168 | def eval_directive(self, s): | |
169 | funct, value = map(lambda x: x.strip().lower(), s.split(':')) | |
170 | if funct == 'rounding': | |
171 | value = RoundingDict[value] | |
172 | else: | |
173 | try: | |
174 | value = int(value) | |
175 | except ValueError: | |
176 | pass | |
177 | ||
178 | funct = self.ChangeDict.get(funct, Nonfunction) | |
179 | funct(value) | |
180 | ||
181 | def eval_equation(self, s): | |
182 | #global DEFAULT_PRECISION | |
183 | #print DEFAULT_PRECISION | |
184 | ||
185 | if not TEST_ALL and random.random() < 0.90: | |
186 | return | |
187 | ||
188 | try: | |
189 | Sides = s.split('->') | |
190 | L = Sides[0].strip().split() | |
191 | id = L[0] | |
192 | # print id, | |
193 | funct = L[1].lower() | |
194 | valstemp = L[2:] | |
195 | L = Sides[1].strip().split() | |
196 | ans = L[0] | |
197 | exceptions = L[1:] | |
198 | except (TypeError, AttributeError, IndexError): | |
199 | raise InvalidOperation | |
200 | def FixQuotes(val): | |
201 | val = val.replace("''", 'SingleQuote').replace('""', 'DoubleQuote') | |
202 | val = val.replace("'", '').replace('"', '') | |
203 | val = val.replace('SingleQuote', "'").replace('DoubleQuote', '"') | |
204 | return val | |
205 | fname = nameAdapter.get(funct, funct) | |
206 | if fname == 'rescale': | |
207 | return | |
208 | funct = getattr(self.context, fname) | |
209 | vals = [] | |
210 | conglomerate = '' | |
211 | quote = 0 | |
212 | theirexceptions = [ErrorNames[x.lower()] for x in exceptions] | |
213 | ||
214 | for exception in Signals: | |
215 | self.context.traps[exception] = 1 #Catch these bugs... | |
216 | for exception in theirexceptions: | |
217 | self.context.traps[exception] = 0 | |
218 | for i, val in enumerate(valstemp): | |
219 | if val.count("'") % 2 == 1: | |
220 | quote = 1 - quote | |
221 | if quote: | |
222 | conglomerate = conglomerate + ' ' + val | |
223 | continue | |
224 | else: | |
225 | val = conglomerate + val | |
226 | conglomerate = '' | |
227 | v = FixQuotes(val) | |
228 | if fname in ('to_sci_string', 'to_eng_string'): | |
229 | if EXTENDEDERRORTEST: | |
230 | for error in theirexceptions: | |
231 | self.context.traps[error] = 1 | |
232 | try: | |
233 | funct(self.context.create_decimal(v)) | |
234 | except error: | |
235 | pass | |
236 | except Signals, e: | |
237 | self.fail("Raised %s in %s when %s disabled" % \ | |
238 | (e, s, error)) | |
239 | else: | |
240 | self.fail("Did not raise %s in %s" % (error, s)) | |
241 | self.context.traps[error] = 0 | |
242 | v = self.context.create_decimal(v) | |
243 | else: | |
244 | v = Decimal(v) | |
245 | vals.append(v) | |
246 | ||
247 | ans = FixQuotes(ans) | |
248 | ||
249 | if EXTENDEDERRORTEST and fname not in ('to_sci_string', 'to_eng_string'): | |
250 | for error in theirexceptions: | |
251 | self.context.traps[error] = 1 | |
252 | try: | |
253 | funct(*vals) | |
254 | except error: | |
255 | pass | |
256 | except Signals, e: | |
257 | self.fail("Raised %s in %s when %s disabled" % \ | |
258 | (e, s, error)) | |
259 | else: | |
260 | self.fail("Did not raise %s in %s" % (error, s)) | |
261 | self.context.traps[error] = 0 | |
262 | try: | |
263 | result = str(funct(*vals)) | |
264 | if fname == 'same_quantum': | |
265 | result = str(int(eval(result))) # 'True', 'False' -> '1', '0' | |
266 | except Signals, error: | |
267 | self.fail("Raised %s in %s" % (error, s)) | |
268 | except: #Catch any error long enough to state the test case. | |
269 | print "ERROR:", s | |
270 | raise | |
271 | ||
272 | myexceptions = self.getexceptions() | |
273 | self.context.clear_flags() | |
274 | ||
275 | myexceptions.sort() | |
276 | theirexceptions.sort() | |
277 | ||
278 | self.assertEqual(result, ans, | |
279 | 'Incorrect answer for ' + s + ' -- got ' + result) | |
280 | self.assertEqual(myexceptions, theirexceptions, | |
281 | 'Incorrect flags set in ' + s + ' -- got ' \ | |
282 | + str(myexceptions)) | |
283 | return | |
284 | ||
285 | def getexceptions(self): | |
286 | return [e for e in Signals if self.context.flags[e]] | |
287 | ||
288 | def change_precision(self, prec): | |
289 | self.context.prec = prec | |
290 | def change_rounding_method(self, rounding): | |
291 | self.context.rounding = rounding | |
292 | def change_min_exponent(self, exp): | |
293 | self.context.Emin = exp | |
294 | def change_max_exponent(self, exp): | |
295 | self.context.Emax = exp | |
296 | def change_clamp(self, clamp): | |
297 | self.context._clamp = clamp | |
298 | ||
299 | # Dynamically build custom test definition for each file in the test | |
300 | # directory and add the definitions to the DecimalTest class. This | |
301 | # procedure insures that new files do not get skipped. | |
302 | for filename in os.listdir(directory): | |
303 | if '.decTest' not in filename: | |
304 | continue | |
305 | head, tail = filename.split('.') | |
306 | tester = lambda self, f=filename: self.eval_file(directory + f) | |
307 | setattr(DecimalTest, 'test_' + head, tester) | |
308 | del filename, head, tail, tester | |
309 | ||
310 | ||
311 | ||
312 | # The following classes test the behaviour of Decimal according to PEP 327 | |
313 | ||
314 | class DecimalExplicitConstructionTest(unittest.TestCase): | |
315 | '''Unit tests for Explicit Construction cases of Decimal.''' | |
316 | ||
317 | def test_explicit_empty(self): | |
318 | self.assertEqual(Decimal(), Decimal("0")) | |
319 | ||
320 | def test_explicit_from_None(self): | |
321 | self.assertRaises(TypeError, Decimal, None) | |
322 | ||
323 | def test_explicit_from_int(self): | |
324 | ||
325 | #positive | |
326 | d = Decimal(45) | |
327 | self.assertEqual(str(d), '45') | |
328 | ||
329 | #very large positive | |
330 | d = Decimal(500000123) | |
331 | self.assertEqual(str(d), '500000123') | |
332 | ||
333 | #negative | |
334 | d = Decimal(-45) | |
335 | self.assertEqual(str(d), '-45') | |
336 | ||
337 | #zero | |
338 | d = Decimal(0) | |
339 | self.assertEqual(str(d), '0') | |
340 | ||
341 | def test_explicit_from_string(self): | |
342 | ||
343 | #empty | |
344 | self.assertEqual(str(Decimal('')), 'NaN') | |
345 | ||
346 | #int | |
347 | self.assertEqual(str(Decimal('45')), '45') | |
348 | ||
349 | #float | |
350 | self.assertEqual(str(Decimal('45.34')), '45.34') | |
351 | ||
352 | #engineer notation | |
353 | self.assertEqual(str(Decimal('45e2')), '4.5E+3') | |
354 | ||
355 | #just not a number | |
356 | self.assertEqual(str(Decimal('ugly')), 'NaN') | |
357 | ||
358 | def test_explicit_from_tuples(self): | |
359 | ||
360 | #zero | |
361 | d = Decimal( (0, (0,), 0) ) | |
362 | self.assertEqual(str(d), '0') | |
363 | ||
364 | #int | |
365 | d = Decimal( (1, (4, 5), 0) ) | |
366 | self.assertEqual(str(d), '-45') | |
367 | ||
368 | #float | |
369 | d = Decimal( (0, (4, 5, 3, 4), -2) ) | |
370 | self.assertEqual(str(d), '45.34') | |
371 | ||
372 | #weird | |
373 | d = Decimal( (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) ) | |
374 | self.assertEqual(str(d), '-4.34913534E-17') | |
375 | ||
376 | #wrong number of items | |
377 | self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1)) ) | |
378 | ||
379 | #bad sign | |
380 | self.assertRaises(ValueError, Decimal, (8, (4, 3, 4, 9, 1), 2) ) | |
381 | ||
382 | #bad exp | |
383 | self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), 'wrong!') ) | |
384 | ||
385 | #bad coefficients | |
386 | self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, None, 1), 2) ) | |
387 | self.assertRaises(ValueError, Decimal, (1, (4, -3, 4, 9, 1), 2) ) | |
388 | ||
389 | def test_explicit_from_Decimal(self): | |
390 | ||
391 | #positive | |
392 | d = Decimal(45) | |
393 | e = Decimal(d) | |
394 | self.assertEqual(str(e), '45') | |
395 | self.assertNotEqual(id(d), id(e)) | |
396 | ||
397 | #very large positive | |
398 | d = Decimal(500000123) | |
399 | e = Decimal(d) | |
400 | self.assertEqual(str(e), '500000123') | |
401 | self.assertNotEqual(id(d), id(e)) | |
402 | ||
403 | #negative | |
404 | d = Decimal(-45) | |
405 | e = Decimal(d) | |
406 | self.assertEqual(str(e), '-45') | |
407 | self.assertNotEqual(id(d), id(e)) | |
408 | ||
409 | #zero | |
410 | d = Decimal(0) | |
411 | e = Decimal(d) | |
412 | self.assertEqual(str(e), '0') | |
413 | self.assertNotEqual(id(d), id(e)) | |
414 | ||
415 | def test_explicit_context_create_decimal(self): | |
416 | ||
417 | nc = copy.copy(getcontext()) | |
418 | nc.prec = 3 | |
419 | ||
420 | # empty | |
421 | d = Decimal() | |
422 | self.assertEqual(str(d), '0') | |
423 | d = nc.create_decimal() | |
424 | self.assertEqual(str(d), '0') | |
425 | ||
426 | # from None | |
427 | self.assertRaises(TypeError, nc.create_decimal, None) | |
428 | ||
429 | # from int | |
430 | d = nc.create_decimal(456) | |
431 | self.failUnless(isinstance(d, Decimal)) | |
432 | self.assertEqual(nc.create_decimal(45678), | |
433 | nc.create_decimal('457E+2')) | |
434 | ||
435 | # from string | |
436 | d = Decimal('456789') | |
437 | self.assertEqual(str(d), '456789') | |
438 | d = nc.create_decimal('456789') | |
439 | self.assertEqual(str(d), '4.57E+5') | |
440 | ||
441 | # from tuples | |
442 | d = Decimal( (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) ) | |
443 | self.assertEqual(str(d), '-4.34913534E-17') | |
444 | d = nc.create_decimal( (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) ) | |
445 | self.assertEqual(str(d), '-4.35E-17') | |
446 | ||
447 | # from Decimal | |
448 | prevdec = Decimal(500000123) | |
449 | d = Decimal(prevdec) | |
450 | self.assertEqual(str(d), '500000123') | |
451 | d = nc.create_decimal(prevdec) | |
452 | self.assertEqual(str(d), '5.00E+8') | |
453 | ||
454 | ||
455 | class DecimalImplicitConstructionTest(unittest.TestCase): | |
456 | '''Unit tests for Implicit Construction cases of Decimal.''' | |
457 | ||
458 | def test_implicit_from_None(self): | |
459 | self.assertRaises(TypeError, eval, 'Decimal(5) + None', globals()) | |
460 | ||
461 | def test_implicit_from_int(self): | |
462 | #normal | |
463 | self.assertEqual(str(Decimal(5) + 45), '50') | |
464 | #exceeding precision | |
465 | self.assertEqual(Decimal(5) + 123456789000, Decimal(123456789000)) | |
466 | ||
467 | def test_implicit_from_string(self): | |
468 | self.assertRaises(TypeError, eval, 'Decimal(5) + "3"', globals()) | |
469 | ||
470 | def test_implicit_from_float(self): | |
471 | self.assertRaises(TypeError, eval, 'Decimal(5) + 2.2', globals()) | |
472 | ||
473 | def test_implicit_from_Decimal(self): | |
474 | self.assertEqual(Decimal(5) + Decimal(45), Decimal(50)) | |
475 | ||
476 | def test_rop(self): | |
477 | # Allow other classes to be trained to interact with Decimals | |
478 | class E: | |
479 | def __divmod__(self, other): | |
480 | return 'divmod ' + str(other) | |
481 | def __rdivmod__(self, other): | |
482 | return str(other) + ' rdivmod' | |
483 | def __lt__(self, other): | |
484 | return 'lt ' + str(other) | |
485 | def __gt__(self, other): | |
486 | return 'gt ' + str(other) | |
487 | def __le__(self, other): | |
488 | return 'le ' + str(other) | |
489 | def __ge__(self, other): | |
490 | return 'ge ' + str(other) | |
491 | def __eq__(self, other): | |
492 | return 'eq ' + str(other) | |
493 | def __ne__(self, other): | |
494 | return 'ne ' + str(other) | |
495 | ||
496 | self.assertEqual(divmod(E(), Decimal(10)), 'divmod 10') | |
497 | self.assertEqual(divmod(Decimal(10), E()), '10 rdivmod') | |
498 | self.assertEqual(eval('Decimal(10) < E()'), 'gt 10') | |
499 | self.assertEqual(eval('Decimal(10) > E()'), 'lt 10') | |
500 | self.assertEqual(eval('Decimal(10) <= E()'), 'ge 10') | |
501 | self.assertEqual(eval('Decimal(10) >= E()'), 'le 10') | |
502 | self.assertEqual(eval('Decimal(10) == E()'), 'eq 10') | |
503 | self.assertEqual(eval('Decimal(10) != E()'), 'ne 10') | |
504 | ||
505 | # insert operator methods and then exercise them | |
506 | for sym, lop, rop in ( | |
507 | ('+', '__add__', '__radd__'), | |
508 | ('-', '__sub__', '__rsub__'), | |
509 | ('*', '__mul__', '__rmul__'), | |
510 | ('/', '__div__', '__rdiv__'), | |
511 | ('%', '__mod__', '__rmod__'), | |
512 | ('//', '__floordiv__', '__rfloordiv__'), | |
513 | ('**', '__pow__', '__rpow__'), | |
514 | ): | |
515 | ||
516 | setattr(E, lop, lambda self, other: 'str' + lop + str(other)) | |
517 | setattr(E, rop, lambda self, other: str(other) + rop + 'str') | |
518 | self.assertEqual(eval('E()' + sym + 'Decimal(10)'), | |
519 | 'str' + lop + '10') | |
520 | self.assertEqual(eval('Decimal(10)' + sym + 'E()'), | |
521 | '10' + rop + 'str') | |
522 | ||
523 | class DecimalArithmeticOperatorsTest(unittest.TestCase): | |
524 | '''Unit tests for all arithmetic operators, binary and unary.''' | |
525 | ||
526 | def test_addition(self): | |
527 | ||
528 | d1 = Decimal('-11.1') | |
529 | d2 = Decimal('22.2') | |
530 | ||
531 | #two Decimals | |
532 | self.assertEqual(d1+d2, Decimal('11.1')) | |
533 | self.assertEqual(d2+d1, Decimal('11.1')) | |
534 | ||
535 | #with other type, left | |
536 | c = d1 + 5 | |
537 | self.assertEqual(c, Decimal('-6.1')) | |
538 | self.assertEqual(type(c), type(d1)) | |
539 | ||
540 | #with other type, right | |
541 | c = 5 + d1 | |
542 | self.assertEqual(c, Decimal('-6.1')) | |
543 | self.assertEqual(type(c), type(d1)) | |
544 | ||
545 | #inline with decimal | |
546 | d1 += d2 | |
547 | self.assertEqual(d1, Decimal('11.1')) | |
548 | ||
549 | #inline with other type | |
550 | d1 += 5 | |
551 | self.assertEqual(d1, Decimal('16.1')) | |
552 | ||
553 | def test_subtraction(self): | |
554 | ||
555 | d1 = Decimal('-11.1') | |
556 | d2 = Decimal('22.2') | |
557 | ||
558 | #two Decimals | |
559 | self.assertEqual(d1-d2, Decimal('-33.3')) | |
560 | self.assertEqual(d2-d1, Decimal('33.3')) | |
561 | ||
562 | #with other type, left | |
563 | c = d1 - 5 | |
564 | self.assertEqual(c, Decimal('-16.1')) | |
565 | self.assertEqual(type(c), type(d1)) | |
566 | ||
567 | #with other type, right | |
568 | c = 5 - d1 | |
569 | self.assertEqual(c, Decimal('16.1')) | |
570 | self.assertEqual(type(c), type(d1)) | |
571 | ||
572 | #inline with decimal | |
573 | d1 -= d2 | |
574 | self.assertEqual(d1, Decimal('-33.3')) | |
575 | ||
576 | #inline with other type | |
577 | d1 -= 5 | |
578 | self.assertEqual(d1, Decimal('-38.3')) | |
579 | ||
580 | def test_multiplication(self): | |
581 | ||
582 | d1 = Decimal('-5') | |
583 | d2 = Decimal('3') | |
584 | ||
585 | #two Decimals | |
586 | self.assertEqual(d1*d2, Decimal('-15')) | |
587 | self.assertEqual(d2*d1, Decimal('-15')) | |
588 | ||
589 | #with other type, left | |
590 | c = d1 * 5 | |
591 | self.assertEqual(c, Decimal('-25')) | |
592 | self.assertEqual(type(c), type(d1)) | |
593 | ||
594 | #with other type, right | |
595 | c = 5 * d1 | |
596 | self.assertEqual(c, Decimal('-25')) | |
597 | self.assertEqual(type(c), type(d1)) | |
598 | ||
599 | #inline with decimal | |
600 | d1 *= d2 | |
601 | self.assertEqual(d1, Decimal('-15')) | |
602 | ||
603 | #inline with other type | |
604 | d1 *= 5 | |
605 | self.assertEqual(d1, Decimal('-75')) | |
606 | ||
607 | def test_division(self): | |
608 | ||
609 | d1 = Decimal('-5') | |
610 | d2 = Decimal('2') | |
611 | ||
612 | #two Decimals | |
613 | self.assertEqual(d1/d2, Decimal('-2.5')) | |
614 | self.assertEqual(d2/d1, Decimal('-0.4')) | |
615 | ||
616 | #with other type, left | |
617 | c = d1 / 4 | |
618 | self.assertEqual(c, Decimal('-1.25')) | |
619 | self.assertEqual(type(c), type(d1)) | |
620 | ||
621 | #with other type, right | |
622 | c = 4 / d1 | |
623 | self.assertEqual(c, Decimal('-0.8')) | |
624 | self.assertEqual(type(c), type(d1)) | |
625 | ||
626 | #inline with decimal | |
627 | d1 /= d2 | |
628 | self.assertEqual(d1, Decimal('-2.5')) | |
629 | ||
630 | #inline with other type | |
631 | d1 /= 4 | |
632 | self.assertEqual(d1, Decimal('-0.625')) | |
633 | ||
634 | def test_floor_division(self): | |
635 | ||
636 | d1 = Decimal('5') | |
637 | d2 = Decimal('2') | |
638 | ||
639 | #two Decimals | |
640 | self.assertEqual(d1//d2, Decimal('2')) | |
641 | self.assertEqual(d2//d1, Decimal('0')) | |
642 | ||
643 | #with other type, left | |
644 | c = d1 // 4 | |
645 | self.assertEqual(c, Decimal('1')) | |
646 | self.assertEqual(type(c), type(d1)) | |
647 | ||
648 | #with other type, right | |
649 | c = 7 // d1 | |
650 | self.assertEqual(c, Decimal('1')) | |
651 | self.assertEqual(type(c), type(d1)) | |
652 | ||
653 | #inline with decimal | |
654 | d1 //= d2 | |
655 | self.assertEqual(d1, Decimal('2')) | |
656 | ||
657 | #inline with other type | |
658 | d1 //= 2 | |
659 | self.assertEqual(d1, Decimal('1')) | |
660 | ||
661 | def test_powering(self): | |
662 | ||
663 | d1 = Decimal('5') | |
664 | d2 = Decimal('2') | |
665 | ||
666 | #two Decimals | |
667 | self.assertEqual(d1**d2, Decimal('25')) | |
668 | self.assertEqual(d2**d1, Decimal('32')) | |
669 | ||
670 | #with other type, left | |
671 | c = d1 ** 4 | |
672 | self.assertEqual(c, Decimal('625')) | |
673 | self.assertEqual(type(c), type(d1)) | |
674 | ||
675 | #with other type, right | |
676 | c = 7 ** d1 | |
677 | self.assertEqual(c, Decimal('16807')) | |
678 | self.assertEqual(type(c), type(d1)) | |
679 | ||
680 | #inline with decimal | |
681 | d1 **= d2 | |
682 | self.assertEqual(d1, Decimal('25')) | |
683 | ||
684 | #inline with other type | |
685 | d1 **= 4 | |
686 | self.assertEqual(d1, Decimal('390625')) | |
687 | ||
688 | def test_module(self): | |
689 | ||
690 | d1 = Decimal('5') | |
691 | d2 = Decimal('2') | |
692 | ||
693 | #two Decimals | |
694 | self.assertEqual(d1%d2, Decimal('1')) | |
695 | self.assertEqual(d2%d1, Decimal('2')) | |
696 | ||
697 | #with other type, left | |
698 | c = d1 % 4 | |
699 | self.assertEqual(c, Decimal('1')) | |
700 | self.assertEqual(type(c), type(d1)) | |
701 | ||
702 | #with other type, right | |
703 | c = 7 % d1 | |
704 | self.assertEqual(c, Decimal('2')) | |
705 | self.assertEqual(type(c), type(d1)) | |
706 | ||
707 | #inline with decimal | |
708 | d1 %= d2 | |
709 | self.assertEqual(d1, Decimal('1')) | |
710 | ||
711 | #inline with other type | |
712 | d1 %= 4 | |
713 | self.assertEqual(d1, Decimal('1')) | |
714 | ||
715 | def test_floor_div_module(self): | |
716 | ||
717 | d1 = Decimal('5') | |
718 | d2 = Decimal('2') | |
719 | ||
720 | #two Decimals | |
721 | (p, q) = divmod(d1, d2) | |
722 | self.assertEqual(p, Decimal('2')) | |
723 | self.assertEqual(q, Decimal('1')) | |
724 | self.assertEqual(type(p), type(d1)) | |
725 | self.assertEqual(type(q), type(d1)) | |
726 | ||
727 | #with other type, left | |
728 | (p, q) = divmod(d1, 4) | |
729 | self.assertEqual(p, Decimal('1')) | |
730 | self.assertEqual(q, Decimal('1')) | |
731 | self.assertEqual(type(p), type(d1)) | |
732 | self.assertEqual(type(q), type(d1)) | |
733 | ||
734 | #with other type, right | |
735 | (p, q) = divmod(7, d1) | |
736 | self.assertEqual(p, Decimal('1')) | |
737 | self.assertEqual(q, Decimal('2')) | |
738 | self.assertEqual(type(p), type(d1)) | |
739 | self.assertEqual(type(q), type(d1)) | |
740 | ||
741 | def test_unary_operators(self): | |
742 | self.assertEqual(+Decimal(45), Decimal(+45)) # + | |
743 | self.assertEqual(-Decimal(45), Decimal(-45)) # - | |
744 | self.assertEqual(abs(Decimal(45)), abs(Decimal(-45))) # abs | |
745 | ||
746 | ||
747 | # The following are two functions used to test threading in the next class | |
748 | ||
749 | def thfunc1(cls): | |
750 | d1 = Decimal(1) | |
751 | d3 = Decimal(3) | |
752 | cls.assertEqual(d1/d3, Decimal('0.333333333')) | |
753 | cls.synchro.wait() | |
754 | cls.assertEqual(d1/d3, Decimal('0.333333333')) | |
755 | cls.finish1.set() | |
756 | return | |
757 | ||
758 | def thfunc2(cls): | |
759 | d1 = Decimal(1) | |
760 | d3 = Decimal(3) | |
761 | cls.assertEqual(d1/d3, Decimal('0.333333333')) | |
762 | thiscontext = getcontext() | |
763 | thiscontext.prec = 18 | |
764 | cls.assertEqual(d1/d3, Decimal('0.333333333333333333')) | |
765 | cls.synchro.set() | |
766 | cls.finish2.set() | |
767 | return | |
768 | ||
769 | ||
770 | class DecimalUseOfContextTest(unittest.TestCase): | |
771 | '''Unit tests for Use of Context cases in Decimal.''' | |
772 | ||
773 | try: | |
774 | import threading | |
775 | except ImportError: | |
776 | threading = None | |
777 | ||
778 | # Take care executing this test from IDLE, there's an issue in threading | |
779 | # that hangs IDLE and I couldn't find it | |
780 | ||
781 | def test_threading(self): | |
782 | #Test the "threading isolation" of a Context. | |
783 | ||
784 | self.synchro = threading.Event() | |
785 | self.finish1 = threading.Event() | |
786 | self.finish2 = threading.Event() | |
787 | ||
788 | th1 = threading.Thread(target=thfunc1, args=(self,)) | |
789 | th2 = threading.Thread(target=thfunc2, args=(self,)) | |
790 | ||
791 | th1.start() | |
792 | th2.start() | |
793 | ||
794 | self.finish1.wait() | |
795 | self.finish1.wait() | |
796 | return | |
797 | ||
798 | if threading is None: | |
799 | del test_threading | |
800 | ||
801 | ||
802 | class DecimalUsabilityTest(unittest.TestCase): | |
803 | '''Unit tests for Usability cases of Decimal.''' | |
804 | ||
805 | def test_comparison_operators(self): | |
806 | ||
807 | da = Decimal('23.42') | |
808 | db = Decimal('23.42') | |
809 | dc = Decimal('45') | |
810 | ||
811 | #two Decimals | |
812 | self.failUnless(dc > da) | |
813 | self.failUnless(dc >= da) | |
814 | self.failUnless(da < dc) | |
815 | self.failUnless(da <= dc) | |
816 | self.failUnless(da == db) | |
817 | self.failUnless(da != dc) | |
818 | self.failUnless(da <= db) | |
819 | self.failUnless(da >= db) | |
820 | self.assertEqual(cmp(dc,da), 1) | |
821 | self.assertEqual(cmp(da,dc), -1) | |
822 | self.assertEqual(cmp(da,db), 0) | |
823 | ||
824 | #a Decimal and an int | |
825 | self.failUnless(dc > 23) | |
826 | self.failUnless(23 < dc) | |
827 | self.failUnless(dc == 45) | |
828 | self.assertEqual(cmp(dc,23), 1) | |
829 | self.assertEqual(cmp(23,dc), -1) | |
830 | self.assertEqual(cmp(dc,45), 0) | |
831 | ||
832 | #a Decimal and uncomparable | |
833 | self.assertNotEqual(da, 'ugly') | |
834 | self.assertNotEqual(da, 32.7) | |
835 | self.assertNotEqual(da, object()) | |
836 | self.assertNotEqual(da, object) | |
837 | ||
838 | # sortable | |
839 | a = map(Decimal, xrange(100)) | |
840 | b = a[:] | |
841 | random.shuffle(a) | |
842 | a.sort() | |
843 | self.assertEqual(a, b) | |
844 | ||
845 | def test_copy_and_deepcopy_methods(self): | |
846 | d = Decimal('43.24') | |
847 | c = copy.copy(d) | |
848 | self.assertEqual(id(c), id(d)) | |
849 | dc = copy.deepcopy(d) | |
850 | self.assertEqual(id(dc), id(d)) | |
851 | ||
852 | def test_hash_method(self): | |
853 | #just that it's hashable | |
854 | hash(Decimal(23)) | |
855 | #the same hash that to an int | |
856 | self.assertEqual(hash(Decimal(23)), hash(23)) | |
857 | self.assertRaises(TypeError, hash, Decimal('NaN')) | |
858 | self.assert_(hash(Decimal('Inf'))) | |
859 | self.assert_(hash(Decimal('-Inf'))) | |
860 | ||
861 | def test_min_and_max_methods(self): | |
862 | ||
863 | d1 = Decimal('15.32') | |
864 | d2 = Decimal('28.5') | |
865 | l1 = 15 | |
866 | l2 = 28 | |
867 | ||
868 | #between Decimals | |
869 | self.failUnless(min(d1,d2) is d1) | |
870 | self.failUnless(min(d2,d1) is d1) | |
871 | self.failUnless(max(d1,d2) is d2) | |
872 | self.failUnless(max(d2,d1) is d2) | |
873 | ||
874 | #between Decimal and long | |
875 | self.failUnless(min(d1,l2) is d1) | |
876 | self.failUnless(min(l2,d1) is d1) | |
877 | self.failUnless(max(l1,d2) is d2) | |
878 | self.failUnless(max(d2,l1) is d2) | |
879 | ||
880 | def test_as_nonzero(self): | |
881 | #as false | |
882 | self.failIf(Decimal(0)) | |
883 | #as true | |
884 | self.failUnless(Decimal('0.372')) | |
885 | ||
886 | def test_tostring_methods(self): | |
887 | #Test str and repr methods. | |
888 | ||
889 | d = Decimal('15.32') | |
890 | self.assertEqual(str(d), '15.32') # str | |
891 | self.assertEqual(repr(d), 'Decimal("15.32")') # repr | |
892 | ||
893 | def test_tonum_methods(self): | |
894 | #Test float, int and long methods. | |
895 | ||
896 | d1 = Decimal('66') | |
897 | d2 = Decimal('15.32') | |
898 | ||
899 | #int | |
900 | self.assertEqual(int(d1), 66) | |
901 | self.assertEqual(int(d2), 15) | |
902 | ||
903 | #long | |
904 | self.assertEqual(long(d1), 66) | |
905 | self.assertEqual(long(d2), 15) | |
906 | ||
907 | #float | |
908 | self.assertEqual(float(d1), 66) | |
909 | self.assertEqual(float(d2), 15.32) | |
910 | ||
911 | def test_eval_round_trip(self): | |
912 | ||
913 | #with zero | |
914 | d = Decimal( (0, (0,), 0) ) | |
915 | self.assertEqual(d, eval(repr(d))) | |
916 | ||
917 | #int | |
918 | d = Decimal( (1, (4, 5), 0) ) | |
919 | self.assertEqual(d, eval(repr(d))) | |
920 | ||
921 | #float | |
922 | d = Decimal( (0, (4, 5, 3, 4), -2) ) | |
923 | self.assertEqual(d, eval(repr(d))) | |
924 | ||
925 | #weird | |
926 | d = Decimal( (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) ) | |
927 | self.assertEqual(d, eval(repr(d))) | |
928 | ||
929 | def test_as_tuple(self): | |
930 | ||
931 | #with zero | |
932 | d = Decimal(0) | |
933 | self.assertEqual(d.as_tuple(), (0, (0,), 0) ) | |
934 | ||
935 | #int | |
936 | d = Decimal(-45) | |
937 | self.assertEqual(d.as_tuple(), (1, (4, 5), 0) ) | |
938 | ||
939 | #complicated string | |
940 | d = Decimal("-4.34913534E-17") | |
941 | self.assertEqual(d.as_tuple(), (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) ) | |
942 | ||
943 | #inf | |
944 | d = Decimal("Infinity") | |
945 | self.assertEqual(d.as_tuple(), (0, (0,), 'F') ) | |
946 | ||
947 | def test_immutability_operations(self): | |
948 | # Do operations and check that it didn't change change internal objects. | |
949 | ||
950 | d1 = Decimal('-25e55') | |
951 | b1 = Decimal('-25e55') | |
952 | d2 = Decimal('33e-33') | |
953 | b2 = Decimal('33e-33') | |
954 | ||
955 | def checkSameDec(operation, useOther=False): | |
956 | if useOther: | |
957 | eval("d1." + operation + "(d2)") | |
958 | self.assertEqual(d1._sign, b1._sign) | |
959 | self.assertEqual(d1._int, b1._int) | |
960 | self.assertEqual(d1._exp, b1._exp) | |
961 | self.assertEqual(d2._sign, b2._sign) | |
962 | self.assertEqual(d2._int, b2._int) | |
963 | self.assertEqual(d2._exp, b2._exp) | |
964 | else: | |
965 | eval("d1." + operation + "()") | |
966 | self.assertEqual(d1._sign, b1._sign) | |
967 | self.assertEqual(d1._int, b1._int) | |
968 | self.assertEqual(d1._exp, b1._exp) | |
969 | return | |
970 | ||
971 | Decimal(d1) | |
972 | self.assertEqual(d1._sign, b1._sign) | |
973 | self.assertEqual(d1._int, b1._int) | |
974 | self.assertEqual(d1._exp, b1._exp) | |
975 | ||
976 | checkSameDec("__abs__") | |
977 | checkSameDec("__add__", True) | |
978 | checkSameDec("__div__", True) | |
979 | checkSameDec("__divmod__", True) | |
980 | checkSameDec("__cmp__", True) | |
981 | checkSameDec("__float__") | |
982 | checkSameDec("__floordiv__", True) | |
983 | checkSameDec("__hash__") | |
984 | checkSameDec("__int__") | |
985 | checkSameDec("__long__") | |
986 | checkSameDec("__mod__", True) | |
987 | checkSameDec("__mul__", True) | |
988 | checkSameDec("__neg__") | |
989 | checkSameDec("__nonzero__") | |
990 | checkSameDec("__pos__") | |
991 | checkSameDec("__pow__", True) | |
992 | checkSameDec("__radd__", True) | |
993 | checkSameDec("__rdiv__", True) | |
994 | checkSameDec("__rdivmod__", True) | |
995 | checkSameDec("__repr__") | |
996 | checkSameDec("__rfloordiv__", True) | |
997 | checkSameDec("__rmod__", True) | |
998 | checkSameDec("__rmul__", True) | |
999 | checkSameDec("__rpow__", True) | |
1000 | checkSameDec("__rsub__", True) | |
1001 | checkSameDec("__str__") | |
1002 | checkSameDec("__sub__", True) | |
1003 | checkSameDec("__truediv__", True) | |
1004 | checkSameDec("adjusted") | |
1005 | checkSameDec("as_tuple") | |
1006 | checkSameDec("compare", True) | |
1007 | checkSameDec("max", True) | |
1008 | checkSameDec("min", True) | |
1009 | checkSameDec("normalize") | |
1010 | checkSameDec("quantize", True) | |
1011 | checkSameDec("remainder_near", True) | |
1012 | checkSameDec("same_quantum", True) | |
1013 | checkSameDec("sqrt") | |
1014 | checkSameDec("to_eng_string") | |
1015 | checkSameDec("to_integral") | |
1016 | ||
1017 | class DecimalPythonAPItests(unittest.TestCase): | |
1018 | ||
1019 | def test_pickle(self): | |
1020 | d = Decimal('-3.141590000') | |
1021 | p = pickle.dumps(d) | |
1022 | e = pickle.loads(p) | |
1023 | self.assertEqual(d, e) | |
1024 | ||
1025 | def test_int(self): | |
1026 | for x in range(-250, 250): | |
1027 | s = '%0.2f' % (x / 100.0) | |
1028 | # should work the same as for floats | |
1029 | self.assertEqual(int(Decimal(s)), int(float(s))) | |
1030 | # should work the same as to_integral in the ROUND_DOWN mode | |
1031 | d = Decimal(s) | |
1032 | r = d.to_integral(ROUND_DOWN) | |
1033 | self.assertEqual(Decimal(int(d)), r) | |
1034 | ||
1035 | class ContextAPItests(unittest.TestCase): | |
1036 | ||
1037 | def test_pickle(self): | |
1038 | c = Context() | |
1039 | e = pickle.loads(pickle.dumps(c)) | |
1040 | for k in vars(c): | |
1041 | v1 = vars(c)[k] | |
1042 | v2 = vars(e)[k] | |
1043 | self.assertEqual(v1, v2) | |
1044 | ||
1045 | def test_equality_with_other_types(self): | |
1046 | self.assert_(Decimal(10) in ['a', 1.0, Decimal(10), (1,2), {}]) | |
1047 | self.assert_(Decimal(10) not in ['a', 1.0, (1,2), {}]) | |
1048 | ||
1049 | def test_copy(self): | |
1050 | # All copies should be deep | |
1051 | c = Context() | |
1052 | d = c.copy() | |
1053 | self.assertNotEqual(id(c), id(d)) | |
1054 | self.assertNotEqual(id(c.flags), id(d.flags)) | |
1055 | self.assertNotEqual(id(c.traps), id(d.traps)) | |
1056 | ||
1057 | def test_main(arith=False, verbose=None): | |
1058 | """ Execute the tests. | |
1059 | ||
1060 | Runs all arithmetic tests if arith is True or if the "decimal" resource | |
1061 | is enabled in regrtest.py | |
1062 | """ | |
1063 | ||
1064 | global TEST_ALL | |
1065 | TEST_ALL = arith or is_resource_enabled('decimal') | |
1066 | ||
1067 | test_classes = [ | |
1068 | DecimalExplicitConstructionTest, | |
1069 | DecimalImplicitConstructionTest, | |
1070 | DecimalArithmeticOperatorsTest, | |
1071 | DecimalUseOfContextTest, | |
1072 | DecimalUsabilityTest, | |
1073 | DecimalPythonAPItests, | |
1074 | ContextAPItests, | |
1075 | DecimalTest, | |
1076 | ] | |
1077 | ||
1078 | run_unittest(*test_classes) | |
1079 | import decimal as DecimalModule | |
1080 | run_doctest(DecimalModule, verbose) | |
1081 | ||
1082 | ||
1083 | if __name__ == '__main__': | |
1084 | # Calling with no arguments runs all tests. | |
1085 | # Calling with "Skip" will skip over 90% of the arithmetic tests. | |
1086 | if len(sys.argv) == 1: | |
1087 | test_main(arith=True, verbose=True) | |
1088 | elif len(sys.argv) == 2: | |
1089 | arith = sys.argv[1].lower() != 'skip' | |
1090 | test_main(arith=arith, verbose=True) | |
1091 | else: | |
1092 | raise ValueError("test called with wrong arguments, use test_Decimal [Skip]") |