Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2015, 2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 :
17 : /**
18 : * @file util/test_amount.c
19 : * @brief Tests for amount logic
20 : * @author Christian Grothoff <christian@grothoff.org>
21 : */
22 : #include "taler/taler_util.h"
23 :
24 :
25 : /**
26 : * Test the price list (`struct TALER_AmountList`) API.
27 : *
28 : * @return 0 on success
29 : */
30 : static int
31 1 : test_amount_list (void)
32 : {
33 : struct TALER_AmountList al;
34 : struct TALER_AmountList cp;
35 : struct TALER_Amount a;
36 : const struct TALER_Amount *f;
37 :
38 : /* parsing, order preservation, round-trip */
39 1 : GNUNET_assert (GNUNET_OK ==
40 : TALER_string_to_amount_list ("EUR:1.1;CHF:1;USD:2;JPY:200",
41 : &al));
42 1 : GNUNET_assert (4 == al.tal_len);
43 1 : GNUNET_assert (0 == strcmp ("EUR",
44 : al.tal[0].currency));
45 1 : GNUNET_assert (0 == strcmp ("JPY",
46 : al.tal[3].currency));
47 1 : GNUNET_assert (0 == strcmp ("EUR:1.1;CHF:1;USD:2;JPY:200",
48 : TALER_amount_list2s (&al)));
49 :
50 : /* lookup is case-insensitive and, crucially, returns NULL for a
51 : currency that is not offered instead of a zero amount */
52 1 : f = TALER_amount_list_find (&al,
53 : "chf");
54 1 : GNUNET_assert (NULL != f);
55 1 : GNUNET_assert (GNUNET_OK ==
56 : TALER_string_to_amount ("CHF:1",
57 : &a));
58 1 : GNUNET_assert (0 == TALER_amount_cmp (&a,
59 : f));
60 1 : GNUNET_assert (NULL ==
61 : TALER_amount_list_find (&al,
62 : "GBP"));
63 : /* ... unlike the accumulator, which answers "zero" */
64 : {
65 1 : struct TALER_AmountSet as = {
66 1 : .taa = al.tal,
67 1 : .taa_size = al.tal_len
68 : };
69 :
70 1 : f = TALER_amount_set_find ("GBP",
71 : &as);
72 1 : GNUNET_assert (NULL != f);
73 1 : GNUNET_assert (TALER_amount_is_zero (f));
74 : }
75 :
76 : /* coverage */
77 : {
78 1 : const char *good[] = { "JPY", "EUR", "USD", "CHF" };
79 1 : const char *missing[] = { "EUR", "CHF", "USD" };
80 1 : const char *extra[] = { "EUR", "CHF", "USD", "JPY", "GBP" };
81 1 : const char *other[] = { "EUR", "CHF", "USD", "GBP" };
82 :
83 1 : GNUNET_assert (TALER_amount_list_covers (&al,
84 : good,
85 : 4));
86 1 : GNUNET_assert (! TALER_amount_list_covers (&al,
87 : missing,
88 : 3));
89 1 : GNUNET_assert (! TALER_amount_list_covers (&al,
90 : extra,
91 : 5));
92 1 : GNUNET_assert (! TALER_amount_list_covers (&al,
93 : other,
94 : 4));
95 : }
96 :
97 : /* copy is deep */
98 1 : TALER_amount_list_copy (&cp,
99 : &al);
100 1 : GNUNET_assert (cp.tal != al.tal);
101 1 : GNUNET_assert (cp.tal_len == al.tal_len);
102 1 : GNUNET_assert (0 == strcmp (TALER_amount_list2s (&cp),
103 : "EUR:1.1;CHF:1;USD:2;JPY:200"));
104 :
105 : /* multiplication applies to every currency */
106 1 : GNUNET_assert (GNUNET_OK ==
107 : TALER_amount_list_multiply (&cp,
108 : 3));
109 1 : GNUNET_assert (0 == strcmp ("EUR:3.3;CHF:3;USD:6;JPY:600",
110 : TALER_amount_list2s (&cp)));
111 : /* a factor of one is a no-op, a factor of zero is refused */
112 1 : GNUNET_assert (GNUNET_OK ==
113 : TALER_amount_list_multiply (&cp,
114 : 1));
115 1 : GNUNET_assert (0 == strcmp ("EUR:3.3;CHF:3;USD:6;JPY:600",
116 : TALER_amount_list2s (&cp)));
117 1 : GNUNET_assert (GNUNET_SYSERR ==
118 : TALER_amount_list_multiply (&cp,
119 : 0));
120 1 : TALER_amount_list_free (&cp);
121 :
122 : /* overflow in one currency must not multiply the others either */
123 1 : GNUNET_assert (GNUNET_OK ==
124 : TALER_string_to_amount_list ("EUR:1;CHF:1000000000000",
125 : &cp));
126 1 : GNUNET_assert (GNUNET_SYSERR ==
127 : TALER_amount_list_multiply (&cp,
128 : 1000000));
129 1 : GNUNET_assert (0 == strcmp ("EUR:1;CHF:1000000000000",
130 : TALER_amount_list2s (&cp)));
131 1 : TALER_amount_list_free (&cp);
132 :
133 : /* uniformity: all priced */
134 1 : GNUNET_assert (GNUNET_OK ==
135 : TALER_amount_list_check_uniform (&al));
136 1 : TALER_amount_list_free (&al);
137 :
138 : /* uniformity: all free */
139 1 : GNUNET_assert (GNUNET_OK ==
140 : TALER_string_to_amount_list ("EUR:0;CHF:0",
141 : &al));
142 1 : GNUNET_assert (GNUNET_NO ==
143 : TALER_amount_list_check_uniform (&al));
144 1 : TALER_amount_list_free (&al);
145 :
146 : /* uniformity: mixed, the case the callers must refuse */
147 1 : GNUNET_assert (GNUNET_OK ==
148 : TALER_string_to_amount_list ("EUR:1;CHF:0",
149 : &al));
150 1 : GNUNET_assert (GNUNET_SYSERR ==
151 : TALER_amount_list_check_uniform (&al));
152 1 : TALER_amount_list_free (&al);
153 :
154 : /* the empty list is the canonical spelling of "free" */
155 1 : GNUNET_assert (GNUNET_OK ==
156 : TALER_string_to_amount_list ("",
157 : &al));
158 1 : GNUNET_assert (0 == al.tal_len);
159 1 : GNUNET_assert (GNUNET_NO ==
160 : TALER_amount_list_check_uniform (&al));
161 1 : GNUNET_assert (NULL ==
162 : TALER_amount_list_find (&al,
163 : "EUR"));
164 1 : GNUNET_assert (0 == strcmp ("",
165 : TALER_amount_list2s (&al)));
166 1 : GNUNET_assert (GNUNET_OK ==
167 : TALER_amount_list_multiply (&al,
168 : 7));
169 1 : TALER_amount_list_free (&al);
170 :
171 : /* a single amount is a valid one-element list, which is what
172 : keeps existing single-currency configurations working */
173 1 : GNUNET_assert (GNUNET_OK ==
174 : TALER_string_to_amount_list ("EUR:1.1",
175 : &al));
176 1 : GNUNET_assert (1 == al.tal_len);
177 1 : TALER_amount_list_free (&al);
178 :
179 : /* rejected: a repeated currency ... */
180 1 : GNUNET_assert (GNUNET_SYSERR ==
181 : TALER_string_to_amount_list ("EUR:1;CHF:1;EUR:2",
182 : &al));
183 : /* ... an empty component ... */
184 1 : GNUNET_assert (GNUNET_SYSERR ==
185 : TALER_string_to_amount_list ("EUR:1;;CHF:1",
186 : &al));
187 1 : GNUNET_assert (GNUNET_SYSERR ==
188 : TALER_string_to_amount_list ("EUR:1;",
189 : &al));
190 : /* ... and a malformed component */
191 1 : GNUNET_assert (GNUNET_SYSERR ==
192 : TALER_string_to_amount_list ("EUR:1;CHF",
193 : &al));
194 1 : GNUNET_assert (GNUNET_SYSERR ==
195 : TALER_string_to_amount_list ("EUR:1;CHF:x",
196 : &al));
197 1 : return 0;
198 : }
199 :
200 :
201 : int
202 1 : main (int argc,
203 : const char *const argv[])
204 : {
205 : struct TALER_Amount a1;
206 : struct TALER_Amount a2;
207 : struct TALER_Amount a3;
208 : struct TALER_Amount r;
209 : char *c;
210 :
211 : (void) argc;
212 : (void) argv;
213 1 : GNUNET_log_setup ("test-amout",
214 : "WARNING",
215 : NULL);
216 : /* test invalid conversions */
217 1 : GNUNET_log_skip (6, GNUNET_NO);
218 : /* non-numeric */
219 1 : GNUNET_assert (GNUNET_SYSERR ==
220 : TALER_string_to_amount ("EUR:4a",
221 : &a1));
222 : /* non-numeric */
223 1 : GNUNET_assert (GNUNET_SYSERR ==
224 : TALER_string_to_amount ("EUR:4.4a",
225 : &a1));
226 : /* non-numeric */
227 1 : GNUNET_assert (GNUNET_SYSERR ==
228 : TALER_string_to_amount ("EUR:4.a4",
229 : &a1));
230 : /* no currency */
231 1 : GNUNET_assert (GNUNET_SYSERR ==
232 : TALER_string_to_amount (":4.a4",
233 : &a1));
234 : /* precision too high */
235 1 : GNUNET_assert (GNUNET_SYSERR ==
236 : TALER_string_to_amount ("EUR:4.123456789",
237 : &a1));
238 : /* value too big */
239 1 : GNUNET_assert (GNUNET_SYSERR ==
240 : TALER_string_to_amount (
241 : "EUR:1234567890123456789012345678901234567890123456789012345678901234567890",
242 : &a1));
243 1 : GNUNET_log_skip (0, GNUNET_YES);
244 :
245 : /* test conversion without fraction */
246 1 : GNUNET_assert (GNUNET_OK ==
247 : TALER_string_to_amount ("EUR:4",
248 : &a1));
249 1 : GNUNET_assert (0 == strcasecmp ("EUR",
250 : a1.currency));
251 1 : GNUNET_assert (4 == a1.value);
252 1 : GNUNET_assert (0 == a1.fraction);
253 :
254 : /* test conversion with leading zero in fraction */
255 1 : GNUNET_assert (GNUNET_OK ==
256 : TALER_string_to_amount ("EUR:0.02",
257 : &a2));
258 1 : GNUNET_assert (0 == strcasecmp ("EUR",
259 : a2.currency));
260 1 : GNUNET_assert (0 == a2.value);
261 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 2 == a2.fraction);
262 1 : c = TALER_amount_to_string (&a2);
263 1 : GNUNET_assert (0 == strcasecmp ("EUR:0.02",
264 : c));
265 1 : GNUNET_free (c);
266 :
267 : /* test conversion with leading space and with fraction */
268 1 : GNUNET_assert (GNUNET_OK ==
269 : TALER_string_to_amount (" EUR:4.12",
270 : &a2));
271 1 : GNUNET_assert (0 == strcasecmp ("EUR",
272 : a2.currency));
273 1 : GNUNET_assert (4 == a2.value);
274 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 12 == a2.fraction);
275 :
276 : /* test use of local currency */
277 1 : GNUNET_assert (GNUNET_OK ==
278 : TALER_string_to_amount (" LOCAL:4444.1000",
279 : &a3));
280 1 : GNUNET_assert (0 == strcasecmp ("LOCAL",
281 : a3.currency));
282 1 : GNUNET_assert (4444 == a3.value);
283 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 10 == a3.fraction);
284 :
285 : /* test CMP with equal and unequal currencies */
286 1 : GNUNET_assert (GNUNET_NO ==
287 : TALER_amount_cmp_currency (&a1,
288 : &a3));
289 1 : GNUNET_assert (GNUNET_YES ==
290 : TALER_amount_cmp_currency (&a1,
291 : &a2));
292 :
293 : /* test subtraction failure (currency mismatch) */
294 1 : GNUNET_assert (TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE ==
295 : TALER_amount_subtract (&a3,
296 : &a3,
297 : &a2));
298 1 : GNUNET_assert (GNUNET_SYSERR ==
299 : TALER_amount_normalize (&a3));
300 :
301 : /* test subtraction failure (negative result) */
302 1 : GNUNET_assert (TALER_AAR_INVALID_NEGATIVE_RESULT ==
303 : TALER_amount_subtract (&a3,
304 : &a1,
305 : &a2));
306 1 : GNUNET_assert (GNUNET_SYSERR ==
307 : TALER_amount_normalize (&a3));
308 :
309 : /* test subtraction success cases */
310 1 : GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
311 : TALER_amount_subtract (&a3,
312 : &a2,
313 : &a1));
314 1 : GNUNET_assert (TALER_AAR_RESULT_ZERO ==
315 : TALER_amount_subtract (&a3,
316 : &a1,
317 : &a1));
318 1 : GNUNET_assert (0 == a3.value);
319 1 : GNUNET_assert (0 == a3.fraction);
320 1 : GNUNET_assert (GNUNET_NO ==
321 : TALER_amount_normalize (&a3));
322 :
323 : /* test addition success */
324 1 : GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
325 : TALER_amount_add (&a3,
326 : &a3,
327 : &a2));
328 1 : GNUNET_assert (GNUNET_NO ==
329 : TALER_amount_normalize (&a3));
330 :
331 : /* test normalization */
332 1 : a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE;
333 1 : a3.value = 4;
334 1 : GNUNET_assert (GNUNET_YES ==
335 : TALER_amount_normalize (&a3));
336 :
337 : /* test conversion to string */
338 1 : c = TALER_amount_to_string (&a3);
339 1 : GNUNET_assert (0 == strcmp ("EUR:6",
340 : c));
341 1 : GNUNET_free (c);
342 :
343 : /* test normalization with fraction overflow */
344 1 : a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE + 1;
345 1 : a3.value = 4;
346 1 : GNUNET_assert (GNUNET_YES ==
347 : TALER_amount_normalize (&a3));
348 1 : c = TALER_amount_to_string (&a3);
349 1 : GNUNET_assert (0 == strcmp ("EUR:6.00000001",
350 : c));
351 1 : GNUNET_free (c);
352 :
353 : /* test normalization with overflow */
354 1 : a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE + 1;
355 1 : a3.value = UINT64_MAX - 1;
356 1 : GNUNET_assert (GNUNET_SYSERR ==
357 : TALER_amount_normalize (&a3));
358 1 : c = TALER_amount_to_string (&a3);
359 1 : GNUNET_assert (NULL == c);
360 :
361 : /* test addition with overflow */
362 1 : a1.fraction = TALER_AMOUNT_FRAC_BASE - 1;
363 1 : a1.value = TALER_AMOUNT_MAX_VALUE - 5;
364 1 : a2.fraction = 2;
365 1 : a2.value = 5;
366 1 : GNUNET_assert (TALER_AAR_INVALID_RESULT_OVERFLOW ==
367 : TALER_amount_add (&a3,
368 : &a1,
369 : &a2));
370 :
371 : /* test addition with underflow on fraction */
372 1 : a1.fraction = 1;
373 1 : a1.value = TALER_AMOUNT_MAX_VALUE;
374 1 : a2.fraction = 2;
375 1 : a2.value = 0;
376 1 : GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
377 : TALER_amount_subtract (&a3,
378 : &a1,
379 : &a2));
380 1 : GNUNET_assert (TALER_AMOUNT_MAX_VALUE - 1 ==
381 : a3.value);
382 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 ==
383 : a3.fraction);
384 :
385 : /* test division */
386 1 : GNUNET_assert (GNUNET_OK ==
387 : TALER_string_to_amount ("EUR:3.33",
388 : &a1));
389 1 : TALER_amount_divide (&a2,
390 : &a1,
391 : 1);
392 1 : GNUNET_assert (0 == strcasecmp ("EUR",
393 : a2.currency));
394 1 : GNUNET_assert (3 == a2.value);
395 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 33 == a2.fraction);
396 :
397 1 : TALER_amount_divide (&a2,
398 : &a1,
399 : 3);
400 1 : GNUNET_assert (0 == strcasecmp ("EUR",
401 : a2.currency));
402 1 : GNUNET_assert (1 == a2.value);
403 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 11 == a2.fraction);
404 :
405 1 : TALER_amount_divide (&a2,
406 : &a1,
407 : 2);
408 1 : GNUNET_assert (0 == strcasecmp ("EUR",
409 : a2.currency));
410 1 : GNUNET_assert (1 == a2.value);
411 1 : GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 1000 * 665 == a2.fraction);
412 1 : TALER_amount_divide (&a2,
413 : &a1,
414 : TALER_AMOUNT_FRAC_BASE * 2);
415 1 : GNUNET_assert (0 == strcasecmp ("EUR",
416 : a2.currency));
417 1 : GNUNET_assert (0 == a2.value);
418 1 : GNUNET_assert (1 == a2.fraction);
419 :
420 : /* test rounding #1 */
421 1 : GNUNET_assert (GNUNET_OK ==
422 : TALER_string_to_amount ("EUR:0.01",
423 : &r));
424 1 : GNUNET_assert (GNUNET_OK ==
425 : TALER_string_to_amount ("EUR:4.001",
426 : &a1));
427 1 : GNUNET_assert (GNUNET_OK ==
428 : TALER_string_to_amount ("EUR:4",
429 : &a2));
430 1 : GNUNET_assert (GNUNET_OK ==
431 : TALER_amount_round_down (&a1,
432 : &r));
433 1 : GNUNET_assert (GNUNET_NO ==
434 : TALER_amount_round_down (&a1,
435 : &r));
436 1 : GNUNET_assert (0 == TALER_amount_cmp (&a1,
437 : &a2));
438 :
439 : /* test rounding #2 */
440 1 : GNUNET_assert (GNUNET_OK ==
441 : TALER_string_to_amount ("EUR:0.001",
442 : &r));
443 :
444 1 : GNUNET_assert (GNUNET_OK ==
445 : TALER_string_to_amount ("EUR:4.001",
446 : &a1));
447 1 : GNUNET_assert (GNUNET_OK ==
448 : TALER_string_to_amount ("EUR:4.001",
449 : &a2));
450 1 : GNUNET_assert (GNUNET_NO ==
451 : TALER_amount_round_down (&a1,
452 : &r));
453 1 : GNUNET_assert (0 == TALER_amount_cmp (&a1,
454 : &a2));
455 :
456 : /* test rounding #3 */
457 1 : GNUNET_assert (GNUNET_OK ==
458 : TALER_string_to_amount ("BTC:5",
459 : &r));
460 1 : GNUNET_assert (GNUNET_OK ==
461 : TALER_string_to_amount ("BTC:12.3",
462 : &a1));
463 1 : GNUNET_assert (GNUNET_OK ==
464 : TALER_string_to_amount ("BTC:10",
465 : &a2));
466 1 : GNUNET_assert (GNUNET_OK ==
467 : TALER_amount_round_down (&a1,
468 : &r));
469 1 : GNUNET_assert (0 == TALER_amount_cmp (&a1,
470 : &a2));
471 :
472 : /* test multiplication */
473 1 : GNUNET_assert (GNUNET_OK ==
474 : TALER_string_to_amount ("BTC:0",
475 : &a1));
476 1 : GNUNET_assert (TALER_AAR_RESULT_ZERO ==
477 : TALER_amount_multiply (&a2,
478 : &a1,
479 : 42));
480 1 : GNUNET_assert (0 == TALER_amount_cmp (&a1,
481 : &a2));
482 1 : GNUNET_assert (GNUNET_OK ==
483 : TALER_string_to_amount ("BTC:5.001",
484 : &a1));
485 1 : GNUNET_assert (GNUNET_OK ==
486 : TALER_string_to_amount ("BTC:5001",
487 : &r));
488 1 : GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
489 : TALER_amount_multiply (&a2,
490 : &a1,
491 : 1000));
492 1 : GNUNET_assert (0 == TALER_amount_cmp (&r,
493 : &a2));
494 1 : GNUNET_assert (1000 ==
495 : TALER_amount_divide2 (&a2,
496 : &a1));
497 1 : GNUNET_assert (GNUNET_OK ==
498 : TALER_string_to_amount ("BTC:5006.00099999",
499 : &r));
500 1 : GNUNET_assert (1000 ==
501 : TALER_amount_divide2 (&r,
502 : &a1));
503 1 : GNUNET_assert (GNUNET_OK ==
504 : TALER_string_to_amount ("BTC:5000.99999999",
505 : &r));
506 1 : GNUNET_assert (999 ==
507 : TALER_amount_divide2 (&r,
508 : &a1));
509 1 : GNUNET_assert (GNUNET_OK ==
510 : TALER_string_to_amount ("BTC:0",
511 : &a1));
512 1 : GNUNET_assert (INT_MAX ==
513 : TALER_amount_divide2 (&a2,
514 : &a1));
515 1 : GNUNET_assert (0 ==
516 : TALER_amount_divide2 (&a1,
517 : &a2));
518 1 : return test_amount_list ();
519 : }
520 :
521 :
522 : /* end of test_amount.c */
|