test_amount.c (19448B)
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 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 GNUNET_assert (GNUNET_OK == 40 TALER_string_to_amount_list ("EUR:1.1;CHF:1;USD:2;JPY:200", 41 &al)); 42 GNUNET_assert (4 == al.tal_len); 43 GNUNET_assert (0 == strcmp ("EUR", 44 al.tal[0].currency)); 45 GNUNET_assert (0 == strcmp ("JPY", 46 al.tal[3].currency)); 47 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 f = TALER_amount_list_find (&al, 53 "chf"); 54 GNUNET_assert (NULL != f); 55 GNUNET_assert (GNUNET_OK == 56 TALER_string_to_amount ("CHF:1", 57 &a)); 58 GNUNET_assert (0 == TALER_amount_cmp (&a, 59 f)); 60 GNUNET_assert (NULL == 61 TALER_amount_list_find (&al, 62 "GBP")); 63 /* ... unlike the accumulator, which answers "zero" */ 64 { 65 struct TALER_AmountSet as = { 66 .taa = al.tal, 67 .taa_size = al.tal_len 68 }; 69 70 f = TALER_amount_set_find ("GBP", 71 &as); 72 GNUNET_assert (NULL != f); 73 GNUNET_assert (TALER_amount_is_zero (f)); 74 } 75 76 /* coverage */ 77 { 78 const char *good[] = { "JPY", "EUR", "USD", "CHF" }; 79 const char *missing[] = { "EUR", "CHF", "USD" }; 80 const char *extra[] = { "EUR", "CHF", "USD", "JPY", "GBP" }; 81 const char *other[] = { "EUR", "CHF", "USD", "GBP" }; 82 83 GNUNET_assert (TALER_amount_list_covers (&al, 84 good, 85 4)); 86 GNUNET_assert (! TALER_amount_list_covers (&al, 87 missing, 88 3)); 89 GNUNET_assert (! TALER_amount_list_covers (&al, 90 extra, 91 5)); 92 GNUNET_assert (! TALER_amount_list_covers (&al, 93 other, 94 4)); 95 } 96 97 /* copy is deep */ 98 TALER_amount_list_copy (&cp, 99 &al); 100 GNUNET_assert (cp.tal != al.tal); 101 GNUNET_assert (cp.tal_len == al.tal_len); 102 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 GNUNET_assert (GNUNET_OK == 107 TALER_amount_list_multiply (&cp, 108 3)); 109 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 GNUNET_assert (GNUNET_OK == 113 TALER_amount_list_multiply (&cp, 114 1)); 115 GNUNET_assert (0 == strcmp ("EUR:3.3;CHF:3;USD:6;JPY:600", 116 TALER_amount_list2s (&cp))); 117 GNUNET_assert (GNUNET_SYSERR == 118 TALER_amount_list_multiply (&cp, 119 0)); 120 TALER_amount_list_free (&cp); 121 122 /* overflow in one currency must not multiply the others either */ 123 GNUNET_assert (GNUNET_OK == 124 TALER_string_to_amount_list ("EUR:1;CHF:1000000000000", 125 &cp)); 126 GNUNET_assert (GNUNET_SYSERR == 127 TALER_amount_list_multiply (&cp, 128 1000000)); 129 GNUNET_assert (0 == strcmp ("EUR:1;CHF:1000000000000", 130 TALER_amount_list2s (&cp))); 131 TALER_amount_list_free (&cp); 132 133 /* uniformity: all priced */ 134 GNUNET_assert (GNUNET_OK == 135 TALER_amount_list_check_uniform (&al)); 136 TALER_amount_list_free (&al); 137 138 /* uniformity: all free */ 139 GNUNET_assert (GNUNET_OK == 140 TALER_string_to_amount_list ("EUR:0;CHF:0", 141 &al)); 142 GNUNET_assert (GNUNET_NO == 143 TALER_amount_list_check_uniform (&al)); 144 TALER_amount_list_free (&al); 145 146 /* uniformity: mixed, the case the callers must refuse */ 147 GNUNET_assert (GNUNET_OK == 148 TALER_string_to_amount_list ("EUR:1;CHF:0", 149 &al)); 150 GNUNET_assert (GNUNET_SYSERR == 151 TALER_amount_list_check_uniform (&al)); 152 TALER_amount_list_free (&al); 153 154 /* the empty list is the canonical spelling of "free" */ 155 GNUNET_assert (GNUNET_OK == 156 TALER_string_to_amount_list ("", 157 &al)); 158 GNUNET_assert (0 == al.tal_len); 159 GNUNET_assert (GNUNET_NO == 160 TALER_amount_list_check_uniform (&al)); 161 GNUNET_assert (NULL == 162 TALER_amount_list_find (&al, 163 "EUR")); 164 GNUNET_assert (0 == strcmp ("", 165 TALER_amount_list2s (&al))); 166 GNUNET_assert (GNUNET_OK == 167 TALER_amount_list_multiply (&al, 168 7)); 169 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 GNUNET_assert (GNUNET_OK == 174 TALER_string_to_amount_list ("EUR:1.1", 175 &al)); 176 GNUNET_assert (1 == al.tal_len); 177 TALER_amount_list_free (&al); 178 179 /* rejected: a repeated currency ... */ 180 GNUNET_assert (GNUNET_SYSERR == 181 TALER_string_to_amount_list ("EUR:1;CHF:1;EUR:2", 182 &al)); 183 /* ... an empty component ... */ 184 GNUNET_assert (GNUNET_SYSERR == 185 TALER_string_to_amount_list ("EUR:1;;CHF:1", 186 &al)); 187 GNUNET_assert (GNUNET_SYSERR == 188 TALER_string_to_amount_list ("EUR:1;", 189 &al)); 190 /* ... and a malformed component */ 191 GNUNET_assert (GNUNET_SYSERR == 192 TALER_string_to_amount_list ("EUR:1;CHF", 193 &al)); 194 GNUNET_assert (GNUNET_SYSERR == 195 TALER_string_to_amount_list ("EUR:1;CHF:x", 196 &al)); 197 return 0; 198 } 199 200 201 int 202 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 GNUNET_log_setup ("test-amout", 214 "WARNING", 215 NULL); 216 /* test invalid conversions */ 217 GNUNET_log_skip (6, GNUNET_NO); 218 /* non-numeric */ 219 GNUNET_assert (GNUNET_SYSERR == 220 TALER_string_to_amount ("EUR:4a", 221 &a1)); 222 /* non-numeric */ 223 GNUNET_assert (GNUNET_SYSERR == 224 TALER_string_to_amount ("EUR:4.4a", 225 &a1)); 226 /* non-numeric */ 227 GNUNET_assert (GNUNET_SYSERR == 228 TALER_string_to_amount ("EUR:4.a4", 229 &a1)); 230 /* no currency */ 231 GNUNET_assert (GNUNET_SYSERR == 232 TALER_string_to_amount (":4.a4", 233 &a1)); 234 /* precision too high */ 235 GNUNET_assert (GNUNET_SYSERR == 236 TALER_string_to_amount ("EUR:4.123456789", 237 &a1)); 238 /* value too big */ 239 GNUNET_assert (GNUNET_SYSERR == 240 TALER_string_to_amount ( 241 "EUR:1234567890123456789012345678901234567890123456789012345678901234567890", 242 &a1)); 243 GNUNET_log_skip (0, GNUNET_YES); 244 245 /* test conversion without fraction */ 246 GNUNET_assert (GNUNET_OK == 247 TALER_string_to_amount ("EUR:4", 248 &a1)); 249 GNUNET_assert (0 == strcasecmp ("EUR", 250 a1.currency)); 251 GNUNET_assert (4 == a1.value); 252 GNUNET_assert (0 == a1.fraction); 253 254 /* test conversion with leading zero in fraction */ 255 GNUNET_assert (GNUNET_OK == 256 TALER_string_to_amount ("EUR:0.02", 257 &a2)); 258 GNUNET_assert (0 == strcasecmp ("EUR", 259 a2.currency)); 260 GNUNET_assert (0 == a2.value); 261 GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 2 == a2.fraction); 262 c = TALER_amount_to_string (&a2); 263 GNUNET_assert (0 == strcasecmp ("EUR:0.02", 264 c)); 265 GNUNET_free (c); 266 267 /* test conversion with leading space and with fraction */ 268 GNUNET_assert (GNUNET_OK == 269 TALER_string_to_amount (" EUR:4.12", 270 &a2)); 271 GNUNET_assert (0 == strcasecmp ("EUR", 272 a2.currency)); 273 GNUNET_assert (4 == a2.value); 274 GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 12 == a2.fraction); 275 276 /* test use of local currency */ 277 GNUNET_assert (GNUNET_OK == 278 TALER_string_to_amount (" LOCAL:4444.1000", 279 &a3)); 280 GNUNET_assert (0 == strcasecmp ("LOCAL", 281 a3.currency)); 282 GNUNET_assert (4444 == a3.value); 283 GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 10 == a3.fraction); 284 285 /* test CMP with equal and unequal currencies */ 286 GNUNET_assert (GNUNET_NO == 287 TALER_amount_cmp_currency (&a1, 288 &a3)); 289 GNUNET_assert (GNUNET_YES == 290 TALER_amount_cmp_currency (&a1, 291 &a2)); 292 293 /* test subtraction failure (currency mismatch) */ 294 GNUNET_assert (TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE == 295 TALER_amount_subtract (&a3, 296 &a3, 297 &a2)); 298 GNUNET_assert (GNUNET_SYSERR == 299 TALER_amount_normalize (&a3)); 300 301 /* test subtraction failure (negative result) */ 302 GNUNET_assert (TALER_AAR_INVALID_NEGATIVE_RESULT == 303 TALER_amount_subtract (&a3, 304 &a1, 305 &a2)); 306 GNUNET_assert (GNUNET_SYSERR == 307 TALER_amount_normalize (&a3)); 308 309 /* test subtraction success cases */ 310 GNUNET_assert (TALER_AAR_RESULT_POSITIVE == 311 TALER_amount_subtract (&a3, 312 &a2, 313 &a1)); 314 GNUNET_assert (TALER_AAR_RESULT_ZERO == 315 TALER_amount_subtract (&a3, 316 &a1, 317 &a1)); 318 GNUNET_assert (0 == a3.value); 319 GNUNET_assert (0 == a3.fraction); 320 GNUNET_assert (GNUNET_NO == 321 TALER_amount_normalize (&a3)); 322 323 /* test addition success */ 324 GNUNET_assert (TALER_AAR_RESULT_POSITIVE == 325 TALER_amount_add (&a3, 326 &a3, 327 &a2)); 328 GNUNET_assert (GNUNET_NO == 329 TALER_amount_normalize (&a3)); 330 331 /* test normalization */ 332 a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE; 333 a3.value = 4; 334 GNUNET_assert (GNUNET_YES == 335 TALER_amount_normalize (&a3)); 336 337 /* test conversion to string */ 338 c = TALER_amount_to_string (&a3); 339 GNUNET_assert (0 == strcmp ("EUR:6", 340 c)); 341 GNUNET_free (c); 342 343 /* test normalization with fraction overflow */ 344 a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE + 1; 345 a3.value = 4; 346 GNUNET_assert (GNUNET_YES == 347 TALER_amount_normalize (&a3)); 348 c = TALER_amount_to_string (&a3); 349 GNUNET_assert (0 == strcmp ("EUR:6.00000001", 350 c)); 351 GNUNET_free (c); 352 353 /* test normalization with overflow */ 354 a3.fraction = 2 * TALER_AMOUNT_FRAC_BASE + 1; 355 a3.value = UINT64_MAX - 1; 356 GNUNET_assert (GNUNET_SYSERR == 357 TALER_amount_normalize (&a3)); 358 c = TALER_amount_to_string (&a3); 359 GNUNET_assert (NULL == c); 360 361 /* test addition with overflow */ 362 a1.fraction = TALER_AMOUNT_FRAC_BASE - 1; 363 a1.value = TALER_AMOUNT_MAX_VALUE - 5; 364 a2.fraction = 2; 365 a2.value = 5; 366 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 a1.fraction = 1; 373 a1.value = TALER_AMOUNT_MAX_VALUE; 374 a2.fraction = 2; 375 a2.value = 0; 376 GNUNET_assert (TALER_AAR_RESULT_POSITIVE == 377 TALER_amount_subtract (&a3, 378 &a1, 379 &a2)); 380 GNUNET_assert (TALER_AMOUNT_MAX_VALUE - 1 == 381 a3.value); 382 GNUNET_assert (TALER_AMOUNT_FRAC_BASE - 1 == 383 a3.fraction); 384 385 /* test division */ 386 GNUNET_assert (GNUNET_OK == 387 TALER_string_to_amount ("EUR:3.33", 388 &a1)); 389 TALER_amount_divide (&a2, 390 &a1, 391 1); 392 GNUNET_assert (0 == strcasecmp ("EUR", 393 a2.currency)); 394 GNUNET_assert (3 == a2.value); 395 GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 33 == a2.fraction); 396 397 TALER_amount_divide (&a2, 398 &a1, 399 3); 400 GNUNET_assert (0 == strcasecmp ("EUR", 401 a2.currency)); 402 GNUNET_assert (1 == a2.value); 403 GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 11 == a2.fraction); 404 405 TALER_amount_divide (&a2, 406 &a1, 407 2); 408 GNUNET_assert (0 == strcasecmp ("EUR", 409 a2.currency)); 410 GNUNET_assert (1 == a2.value); 411 GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 1000 * 665 == a2.fraction); 412 TALER_amount_divide (&a2, 413 &a1, 414 TALER_AMOUNT_FRAC_BASE * 2); 415 GNUNET_assert (0 == strcasecmp ("EUR", 416 a2.currency)); 417 GNUNET_assert (0 == a2.value); 418 GNUNET_assert (1 == a2.fraction); 419 420 /* test rounding #1 */ 421 GNUNET_assert (GNUNET_OK == 422 TALER_string_to_amount ("EUR:0.01", 423 &r)); 424 GNUNET_assert (GNUNET_OK == 425 TALER_string_to_amount ("EUR:4.001", 426 &a1)); 427 GNUNET_assert (GNUNET_OK == 428 TALER_string_to_amount ("EUR:4", 429 &a2)); 430 GNUNET_assert (GNUNET_OK == 431 TALER_amount_round_down (&a1, 432 &r)); 433 GNUNET_assert (GNUNET_NO == 434 TALER_amount_round_down (&a1, 435 &r)); 436 GNUNET_assert (0 == TALER_amount_cmp (&a1, 437 &a2)); 438 439 /* test rounding #2 */ 440 GNUNET_assert (GNUNET_OK == 441 TALER_string_to_amount ("EUR:0.001", 442 &r)); 443 444 GNUNET_assert (GNUNET_OK == 445 TALER_string_to_amount ("EUR:4.001", 446 &a1)); 447 GNUNET_assert (GNUNET_OK == 448 TALER_string_to_amount ("EUR:4.001", 449 &a2)); 450 GNUNET_assert (GNUNET_NO == 451 TALER_amount_round_down (&a1, 452 &r)); 453 GNUNET_assert (0 == TALER_amount_cmp (&a1, 454 &a2)); 455 456 /* test rounding #3 */ 457 GNUNET_assert (GNUNET_OK == 458 TALER_string_to_amount ("BTC:5", 459 &r)); 460 GNUNET_assert (GNUNET_OK == 461 TALER_string_to_amount ("BTC:12.3", 462 &a1)); 463 GNUNET_assert (GNUNET_OK == 464 TALER_string_to_amount ("BTC:10", 465 &a2)); 466 GNUNET_assert (GNUNET_OK == 467 TALER_amount_round_down (&a1, 468 &r)); 469 GNUNET_assert (0 == TALER_amount_cmp (&a1, 470 &a2)); 471 472 /* test multiplication */ 473 GNUNET_assert (GNUNET_OK == 474 TALER_string_to_amount ("BTC:0", 475 &a1)); 476 GNUNET_assert (TALER_AAR_RESULT_ZERO == 477 TALER_amount_multiply (&a2, 478 &a1, 479 42)); 480 GNUNET_assert (0 == TALER_amount_cmp (&a1, 481 &a2)); 482 GNUNET_assert (GNUNET_OK == 483 TALER_string_to_amount ("BTC:5.001", 484 &a1)); 485 GNUNET_assert (GNUNET_OK == 486 TALER_string_to_amount ("BTC:5001", 487 &r)); 488 GNUNET_assert (TALER_AAR_RESULT_POSITIVE == 489 TALER_amount_multiply (&a2, 490 &a1, 491 1000)); 492 GNUNET_assert (0 == TALER_amount_cmp (&r, 493 &a2)); 494 GNUNET_assert (1000 == 495 TALER_amount_divide2 (&a2, 496 &a1)); 497 GNUNET_assert (GNUNET_OK == 498 TALER_string_to_amount ("BTC:5006.00099999", 499 &r)); 500 GNUNET_assert (1000 == 501 TALER_amount_divide2 (&r, 502 &a1)); 503 GNUNET_assert (GNUNET_OK == 504 TALER_string_to_amount ("BTC:5000.99999999", 505 &r)); 506 GNUNET_assert (999 == 507 TALER_amount_divide2 (&r, 508 &a1)); 509 GNUNET_assert (GNUNET_OK == 510 TALER_string_to_amount ("BTC:0", 511 &a1)); 512 GNUNET_assert (INT_MAX == 513 TALER_amount_divide2 (&a2, 514 &a1)); 515 GNUNET_assert (0 == 516 TALER_amount_divide2 (&a1, 517 &a2)); 518 return test_amount_list (); 519 } 520 521 522 /* end of test_amount.c */