1#include "config.h" 2 3#include "ntp_stdlib.h" /* test fail without this include, for some reason */ 4#include "ntp_calendar.h" 5#include "ntp_unixtime.h" 6#include "unity.h" 7 8#include <string.h> 9 10static int leapdays(int year); 11 12void setUp(void); 13int isGT(int first, int second); 14int leapdays(int year); 15char * CalendarFromCalToString(const struct calendar *cal); 16char * CalendarFromIsoToString(const struct isodate *iso); 17int IsEqualCal(const struct calendar *expected, const struct calendar *actual); 18int IsEqualIso(const struct isodate *expected, const struct isodate *actual); 19char * DateFromCalToString(const struct calendar *cal); 20char * DateFromIsoToString(const struct isodate *iso); 21int IsEqualDateCal(const struct calendar *expected, const struct calendar *actual); 22int IsEqualDateIso(const struct isodate *expected, const struct isodate *actual); 23 24void test_DaySplitMerge(void); 25void test_SplitYearDays1(void); 26void test_SplitYearDays2(void); 27void test_RataDie1(void); 28void test_LeapYears1(void); 29void test_LeapYears2(void); 30void test_RoundTripDate(void); 31void test_RoundTripYearStart(void); 32void test_RoundTripMonthStart(void); 33void test_RoundTripWeekStart(void); 34void test_RoundTripDayStart(void); 35void test_IsoCalYearsToWeeks(void); 36void test_IsoCalWeeksToYearStart(void); 37void test_IsoCalWeeksToYearEnd(void); 38void test_DaySecToDate(void); 39 40void test_NtpToNtp(void); 41void test_NtpToTime(void); 42 43void 44setUp(void) 45{ 46 init_lib(); 47 48 return; 49} 50 51 52/* 53 * --------------------------------------------------------------------- 54 * test support stuff 55 * --------------------------------------------------------------------- 56 */ 57int 58isGT(int first, int second) 59{ 60 if(first > second) { 61 return TRUE; 62 } else { 63 return FALSE; 64 } 65} 66 67int 68leapdays(int year) 69{ 70 if (year % 400 == 0) 71 return 1; 72 if (year % 100 == 0) 73 return 0; 74 if (year % 4 == 0) 75 return 1; 76 return 0; 77} 78 79char * 80CalendarFromCalToString( 81 const struct calendar *cal) 82{ 83 char * str = malloc(sizeof (char) * 100); 84 snprintf(str, 100, "%u-%02u-%02u (%u) %02u:%02u:%02u", 85 cal->year, (u_int)cal->month, (u_int)cal->monthday, 86 cal->yearday, 87 (u_int)cal->hour, (u_int)cal->minute, (u_int)cal->second); 88 str[99] = '\0'; /* paranoia rulez! */ 89 return str; 90} 91 92char * 93CalendarFromIsoToString( 94 const struct isodate *iso) 95{ 96 char * str = emalloc (sizeof (char) * 100); 97 snprintf(str, 100, "%u-W%02u-%02u %02u:%02u:%02u", 98 iso->year, (u_int)iso->week, (u_int)iso->weekday, 99 (u_int)iso->hour, (u_int)iso->minute, (u_int)iso->second); 100 str[99] = '\0'; /* paranoia rulez! */ 101 return str; 102} 103 104int 105IsEqualCal( 106 const struct calendar *expected, 107 const struct calendar *actual) 108{ 109 if (expected->year == actual->year && 110 (!expected->yearday || expected->yearday == actual->yearday) && 111 expected->month == actual->month && 112 expected->monthday == actual->monthday && 113 expected->hour == actual->hour && 114 expected->minute == actual->minute && 115 expected->second == actual->second) { 116 return TRUE; 117 } else { 118 char *p_exp = CalendarFromCalToString(expected); 119 char *p_act = CalendarFromCalToString(actual); 120 121 printf("expected: %s but was %s", p_exp, p_act); 122 123 free(p_exp); 124 free(p_act); 125 126 return FALSE; 127 } 128} 129 130int 131IsEqualIso( 132 const struct isodate *expected, 133 const struct isodate *actual) 134{ 135 if (expected->year == actual->year && 136 expected->week == actual->week && 137 expected->weekday == actual->weekday && 138 expected->hour == actual->hour && 139 expected->minute == actual->minute && 140 expected->second == actual->second) { 141 return TRUE; 142 } else { 143 printf("expected: %s but was %s", 144 CalendarFromIsoToString(expected), 145 CalendarFromIsoToString(actual)); 146 return FALSE; 147 } 148} 149 150char * 151DateFromCalToString( 152 const struct calendar *cal) 153{ 154 155 char * str = emalloc (sizeof (char) * 100); 156 snprintf(str, 100, "%u-%02u-%02u (%u)", 157 cal->year, (u_int)cal->month, (u_int)cal->monthday, 158 cal->yearday); 159 str[99] = '\0'; /* paranoia rulez! */ 160 return str; 161} 162 163char * 164DateFromIsoToString( 165 const struct isodate *iso) 166{ 167 168 char * str = emalloc (sizeof (char) * 100); 169 snprintf(str, 100, "%u-W%02u-%02u", 170 iso->year, (u_int)iso->week, (u_int)iso->weekday); 171 str[99] = '\0'; /* paranoia rulez! */ 172 return str; 173} 174 175int/*BOOL*/ 176IsEqualDateCal( 177 const struct calendar *expected, 178 const struct calendar *actual) 179{ 180 if (expected->year == actual->year && 181 (!expected->yearday || expected->yearday == actual->yearday) && 182 expected->month == actual->month && 183 expected->monthday == actual->monthday) { 184 return TRUE; 185 } else { 186 printf("expected: %s but was %s", 187 DateFromCalToString(expected), 188 DateFromCalToString(actual)); 189 return FALSE; 190 } 191} 192 193int/*BOOL*/ 194IsEqualDateIso( 195 const struct isodate *expected, 196 const struct isodate *actual) 197{ 198 if (expected->year == actual->year && 199 expected->week == actual->week && 200 expected->weekday == actual->weekday) { 201 return TRUE; 202 } else { 203 printf("expected: %s but was %s", 204 DateFromIsoToString(expected), 205 DateFromIsoToString(actual)); 206 return FALSE; 207 } 208} 209 210 211/* 212 * --------------------------------------------------------------------- 213 * test cases 214 * --------------------------------------------------------------------- 215 */ 216 217/* days before month, with a full-year pad at the upper end */ 218static const u_short real_month_table[2][13] = { 219 /* -*- table for regular years -*- */ 220 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, 221 /* -*- table for leap years -*- */ 222 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } 223}; 224 225/* days in month, with one month wrap-around at both ends */ 226static const u_short real_month_days[2][14] = { 227 /* -*- table for regular years -*- */ 228 { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 }, 229 /* -*- table for leap years -*- */ 230 { 31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 } 231}; 232 233/* test the day/sec join & split ops, making sure that 32bit 234 * intermediate results would definitely overflow and the hi DWORD of 235 * the 'vint64' is definitely needed. 236 */ 237void 238test_DaySplitMerge(void) 239{ 240 int32 day,sec; 241 242 for (day = -1000000; day <= 1000000; day += 100) { 243 for (sec = -100000; sec <= 186400; sec += 10000) { 244 vint64 merge; 245 ntpcal_split split; 246 int32 eday; 247 int32 esec; 248 249 merge = ntpcal_dayjoin(day, sec); 250 split = ntpcal_daysplit(&merge); 251 eday = day; 252 esec = sec; 253 254 while (esec >= 86400) { 255 eday += 1; 256 esec -= 86400; 257 } 258 while (esec < 0) { 259 eday -= 1; 260 esec += 86400; 261 } 262 263 TEST_ASSERT_EQUAL(eday, split.hi); 264 TEST_ASSERT_EQUAL(esec, split.lo); 265 } 266 } 267 268 return; 269} 270 271void 272test_SplitYearDays1(void) 273{ 274 int32 eyd; 275 276 for (eyd = -1; eyd <= 365; eyd++) { 277 ntpcal_split split = ntpcal_split_yeardays(eyd, 0); 278 if (split.lo >= 0 && split.hi >= 0) { 279 TEST_ASSERT_TRUE(isGT(12,split.hi)); 280 TEST_ASSERT_TRUE(isGT(real_month_days[0][split.hi+1], split.lo)); 281 int32 tyd = real_month_table[0][split.hi] + split.lo; 282 TEST_ASSERT_EQUAL(eyd, tyd); 283 } else 284 TEST_ASSERT_TRUE(eyd < 0 || eyd > 364); 285 } 286 287 return; 288} 289 290void 291test_SplitYearDays2(void) 292{ 293 int32 eyd; 294 295 for (eyd = -1; eyd <= 366; eyd++) { 296 ntpcal_split split = ntpcal_split_yeardays(eyd, 1); 297 if (split.lo >= 0 && split.hi >= 0) { 298 /* basic checks do not work on compunds :( */ 299 /* would like: TEST_ASSERT_TRUE(12 > split.hi); */ 300 TEST_ASSERT_TRUE(isGT(12,split.hi)); 301 TEST_ASSERT_TRUE(isGT(real_month_days[1][split.hi+1], split.lo)); 302 int32 tyd = real_month_table[1][split.hi] + split.lo; 303 TEST_ASSERT_EQUAL(eyd, tyd); 304 } else 305 TEST_ASSERT_TRUE(eyd < 0 || eyd > 365); 306 } 307 308 return; 309} 310 311void 312test_RataDie1(void) 313{ 314 int32 testDate = 1; /* 0001-01-01 (proleptic date) */ 315 struct calendar expected = { 1, 1, 1, 1 }; 316 struct calendar actual; 317 318 ntpcal_rd_to_date(&actual, testDate); 319 TEST_ASSERT_TRUE(IsEqualDateCal(&expected, &actual)); 320 321 return; 322} 323 324/* check last day of february for first 10000 years */ 325void 326test_LeapYears1(void) 327{ 328 struct calendar dateIn, dateOut; 329 330 for (dateIn.year = 1; dateIn.year < 10000; ++dateIn.year) { 331 dateIn.month = 2; 332 dateIn.monthday = 28 + leapdays(dateIn.year); 333 dateIn.yearday = 31 + dateIn.monthday; 334 335 ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn)); 336 337 TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut)); 338 } 339 340 return; 341} 342 343/* check first day of march for first 10000 years */ 344void 345test_LeapYears2(void) 346{ 347 struct calendar dateIn, dateOut; 348 349 for (dateIn.year = 1; dateIn.year < 10000; ++dateIn.year) { 350 dateIn.month = 3; 351 dateIn.monthday = 1; 352 dateIn.yearday = 60 + leapdays(dateIn.year); 353 354 ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn)); 355 TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut)); 356 } 357 358 return; 359} 360 361/* Full roundtrip from 1601-01-01 to 2400-12-31 362 * checks sequence of rata die numbers and validates date output 363 * (since the input is all nominal days of the calendar in that range 364 * and the result of the inverse calculation must match the input no 365 * invalid output can occur.) 366 */ 367void 368test_RoundTripDate(void) 369{ 370 struct calendar truDate, expDate = { 1600, 0, 12, 31 };; 371 int leaps; 372 int32 truRdn, expRdn = ntpcal_date_to_rd(&expDate); 373 374 while (expDate.year < 2400) { 375 expDate.year++; 376 expDate.month = 0; 377 expDate.yearday = 0; 378 leaps = leapdays(expDate.year); 379 while (expDate.month < 12) { 380 expDate.month++; 381 expDate.monthday = 0; 382 while (expDate.monthday < real_month_days[leaps][expDate.month]) { 383 expDate.monthday++; 384 expDate.yearday++; 385 expRdn++; 386 387 truRdn = ntpcal_date_to_rd(&expDate); 388 TEST_ASSERT_EQUAL(expRdn, truRdn); 389 390 ntpcal_rd_to_date(&truDate, truRdn); 391 TEST_ASSERT_TRUE(IsEqualDateCal(&expDate, &truDate)); 392 } 393 } 394 } 395 396 return; 397} 398 399/* Roundtrip testing on calyearstart */ 400void 401test_RoundTripYearStart(void) 402{ 403 static const time_t pivot = 0; 404 u_int32 ntp, expys, truys; 405 struct calendar date; 406 407 for (ntp = 0; ntp < 0xFFFFFFFFu - 30000000u; ntp += 30000000u) { 408 truys = calyearstart(ntp, &pivot); 409 ntpcal_ntp_to_date(&date, ntp, &pivot); 410 date.month = date.monthday = 1; 411 date.hour = date.minute = date.second = 0; 412 expys = ntpcal_date_to_ntp(&date); 413 TEST_ASSERT_EQUAL(expys, truys); 414 } 415 416 return; 417} 418 419/* Roundtrip testing on calmonthstart */ 420void 421test_RoundTripMonthStart(void) 422{ 423 static const time_t pivot = 0; 424 u_int32 ntp, expms, trums; 425 struct calendar date; 426 427 for (ntp = 0; ntp < 0xFFFFFFFFu - 2000000u; ntp += 2000000u) { 428 trums = calmonthstart(ntp, &pivot); 429 ntpcal_ntp_to_date(&date, ntp, &pivot); 430 date.monthday = 1; 431 date.hour = date.minute = date.second = 0; 432 expms = ntpcal_date_to_ntp(&date); 433 TEST_ASSERT_EQUAL(expms, trums); 434 } 435 436 return; 437} 438 439/* Roundtrip testing on calweekstart */ 440void 441test_RoundTripWeekStart(void) 442{ 443 static const time_t pivot = 0; 444 u_int32 ntp, expws, truws; 445 struct isodate date; 446 447 for (ntp = 0; ntp < 0xFFFFFFFFu - 600000u; ntp += 600000u) { 448 truws = calweekstart(ntp, &pivot); 449 isocal_ntp_to_date(&date, ntp, &pivot); 450 date.hour = date.minute = date.second = 0; 451 date.weekday = 1; 452 expws = isocal_date_to_ntp(&date); 453 TEST_ASSERT_EQUAL(expws, truws); 454 } 455 456 return; 457} 458 459/* Roundtrip testing on caldaystart */ 460void 461test_RoundTripDayStart(void) 462{ 463 static const time_t pivot = 0; 464 u_int32 ntp, expds, truds; 465 struct calendar date; 466 467 for (ntp = 0; ntp < 0xFFFFFFFFu - 80000u; ntp += 80000u) { 468 truds = caldaystart(ntp, &pivot); 469 ntpcal_ntp_to_date(&date, ntp, &pivot); 470 date.hour = date.minute = date.second = 0; 471 expds = ntpcal_date_to_ntp(&date); 472 TEST_ASSERT_EQUAL(expds, truds); 473 } 474 475 return; 476} 477 478/* --------------------------------------------------------------------- 479 * ISO8601 week calendar internals 480 * 481 * The ISO8601 week calendar implementation is simple in the terms of 482 * the math involved, but the implementation of the calculations must 483 * take care of a few things like overflow, floor division, and sign 484 * corrections. 485 * 486 * Most of the functions are straight forward, but converting from years 487 * to weeks and from weeks to years warrants some extra tests. These use 488 * an independent reference implementation of the conversion from years 489 * to weeks. 490 * --------------------------------------------------------------------- 491 */ 492 493/* helper / reference implementation for the first week of year in the 494 * ISO8601 week calendar. This is based on the reference definition of 495 * the ISO week calendar start: The Monday closest to January,1st of the 496 * corresponding year in the Gregorian calendar. 497 */ 498static int32_t 499refimpl_WeeksInIsoYears( 500 int32_t years) 501{ 502 int32_t days, weeks; 503 504 days = ntpcal_weekday_close( 505 ntpcal_days_in_years(years) + 1, 506 CAL_MONDAY) - 1; 507 /* the weekday functions operate on RDN, while we want elapsed 508 * units here -- we have to add / sub 1 in the midlle / at the 509 * end of the operation that gets us the first day of the ISO 510 * week calendar day. 511 */ 512 weeks = days / 7; 513 days = days % 7; 514 TEST_ASSERT_EQUAL(0, days); /* paranoia check... */ 515 516 return weeks; 517} 518 519/* The next tests loop over 5000yrs, but should still be very fast. If 520 * they are not, the calendar needs a better implementation... 521 */ 522void 523test_IsoCalYearsToWeeks(void) 524{ 525 int32_t years; 526 int32_t wref, wcal; 527 528 for (years = -1000; years < 4000; ++years) { 529 /* get number of weeks before years (reference) */ 530 wref = refimpl_WeeksInIsoYears(years); 531 /* get number of weeks before years (object-under-test) */ 532 wcal = isocal_weeks_in_years(years); 533 TEST_ASSERT_EQUAL(wref, wcal); 534 } 535 536 return; 537} 538 539void 540test_IsoCalWeeksToYearStart(void) 541{ 542 int32_t years; 543 int32_t wref; 544 ntpcal_split ysplit; 545 546 for (years = -1000; years < 4000; ++years) { 547 /* get number of weeks before years (reference) */ 548 wref = refimpl_WeeksInIsoYears(years); 549 /* reverse split */ 550 ysplit = isocal_split_eraweeks(wref); 551 /* check invariants: same year, week 0 */ 552 TEST_ASSERT_EQUAL(years, ysplit.hi); 553 TEST_ASSERT_EQUAL(0, ysplit.lo); 554 } 555 556 return; 557} 558 559void 560test_IsoCalWeeksToYearEnd(void) 561{ 562 int32_t years; 563 int32_t wref; 564 ntpcal_split ysplit; 565 566 for (years = -1000; years < 4000; ++years) { 567 /* get last week of previous year */ 568 wref = refimpl_WeeksInIsoYears(years) - 1; 569 /* reverse split */ 570 ysplit = isocal_split_eraweeks(wref); 571 /* check invariants: previous year, week 51 or 52 */ 572 TEST_ASSERT_EQUAL(years-1, ysplit.hi); 573 TEST_ASSERT(ysplit.lo == 51 || ysplit.lo == 52); 574 } 575 576 return; 577} 578 579void 580test_DaySecToDate(void) 581{ 582 struct calendar cal; 583 int32_t days; 584 585 days = ntpcal_daysec_to_date(&cal, -86400); 586 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==0), 587 "failed for -86400"); 588 589 days = ntpcal_daysec_to_date(&cal, -86399); 590 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==1), 591 "failed for -86399"); 592 593 days = ntpcal_daysec_to_date(&cal, -1); 594 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==23 && cal.minute==59 && cal.second==59), 595 "failed for -1"); 596 597 days = ntpcal_daysec_to_date(&cal, 0); 598 TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==0), 599 "failed for 0"); 600 601 days = ntpcal_daysec_to_date(&cal, 1); 602 TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==1), 603 "failed for 1"); 604 605 days = ntpcal_daysec_to_date(&cal, 86399); 606 TEST_ASSERT_MESSAGE((days==0 && cal.hour==23 && cal.minute==59 && cal.second==59), 607 "failed for 86399"); 608 609 days = ntpcal_daysec_to_date(&cal, 86400); 610 TEST_ASSERT_MESSAGE((days==1 && cal.hour==0 && cal.minute==0 && cal.second==0), 611 "failed for 86400"); 612 613 return; 614} 615 616/* -------------------------------------------------------------------- 617 * unfolding of (truncated) NTP time stamps to full 64bit values. 618 * 619 * Note: These tests need a 64bit time_t to be useful. 620 */ 621 622void 623test_NtpToNtp(void) 624{ 625# if SIZEOF_TIME_T <= 4 626 627 TEST_IGNORE_MESSAGE("test only useful for sizeof(time_t) > 4, skipped"); 628 629# else 630 631 static const uint32_t ntp_vals[6] = { 632 UINT32_C(0x00000000), 633 UINT32_C(0x00000001), 634 UINT32_C(0x7FFFFFFF), 635 UINT32_C(0x80000000), 636 UINT32_C(0x80000001), 637 UINT32_C(0xFFFFFFFF) 638 }; 639 640 static char lbuf[128]; 641 vint64 hold; 642 time_t pivot, texp, diff; 643 int loops, iloop; 644 645 pivot = 0; 646 for (loops = 0; loops < 16; ++loops) { 647 for (iloop = 0; iloop < 6; ++iloop) { 648 hold = ntpcal_ntp_to_ntp( 649 ntp_vals[iloop], &pivot); 650 texp = vint64_to_time(&hold); 651 652 /* constraint 1: texp must be in the 653 * (right-open) intervall [p-(2^31), p+(2^31)[, 654 * but the pivot 'p' must be taken in full NTP 655 * time scale! 656 */ 657 diff = texp - (pivot + JAN_1970); 658 snprintf(lbuf, sizeof(lbuf), 659 "bounds check: piv=%lld exp=%lld dif=%lld", 660 (long long)pivot, 661 (long long)texp, 662 (long long)diff); 663 TEST_ASSERT_MESSAGE((diff >= INT32_MIN) && (diff <= INT32_MAX), 664 lbuf); 665 666 /* constraint 2: low word must be equal to 667 * input 668 */ 669 snprintf(lbuf, sizeof(lbuf), 670 "low check: ntp(in)=$%08lu ntp(out[0:31])=$%08lu", 671 (unsigned long)ntp_vals[iloop], 672 (unsigned long)hold.D_s.lo); 673 TEST_ASSERT_EQUAL_MESSAGE(ntp_vals[iloop], hold.D_s.lo, lbuf); 674 } 675 pivot += 0x20000000; 676 } 677# endif 678} 679 680void 681test_NtpToTime(void) 682{ 683# if SIZEOF_TIME_T <= 4 684 685 TEST_IGNORE_MESSAGE("test only useful for sizeof(time_t) > 4, skipped"); 686 687# else 688 689 static const uint32_t ntp_vals[6] = { 690 UINT32_C(0x00000000), 691 UINT32_C(0x00000001), 692 UINT32_C(0x7FFFFFFF), 693 UINT32_C(0x80000000), 694 UINT32_C(0x80000001), 695 UINT32_C(0xFFFFFFFF) 696 }; 697 698 static char lbuf[128]; 699 vint64 hold; 700 time_t pivot, texp, diff; 701 uint32_t back; 702 int loops, iloop; 703 704 pivot = 0; 705 for (loops = 0; loops < 16; ++loops) { 706 for (iloop = 0; iloop < 6; ++iloop) { 707 hold = ntpcal_ntp_to_time( 708 ntp_vals[iloop], &pivot); 709 texp = vint64_to_time(&hold); 710 711 /* constraint 1: texp must be in the 712 * (right-open) intervall [p-(2^31), p+(2^31)[ 713 */ 714 diff = texp - pivot; 715 snprintf(lbuf, sizeof(lbuf), 716 "bounds check: piv=%lld exp=%lld dif=%lld", 717 (long long)pivot, 718 (long long)texp, 719 (long long)diff); 720 TEST_ASSERT_MESSAGE((diff >= INT32_MIN) && (diff <= INT32_MAX), 721 lbuf); 722 723 /* constraint 2: conversion from full time back 724 * to truncated NTP time must yield same result 725 * as input. 726 */ 727 back = (uint32_t)texp + JAN_1970; 728 snprintf(lbuf, sizeof(lbuf), 729 "modulo check: ntp(in)=$%08lu ntp(out)=$%08lu", 730 (unsigned long)ntp_vals[iloop], 731 (unsigned long)back); 732 TEST_ASSERT_EQUAL_MESSAGE(ntp_vals[iloop], back, lbuf); 733 } 734 pivot += 0x20000000; 735 } 736# endif 737} 738