/* * time-test.c -- test the time functions * * ==================================================================== * Copyright (c) 2000-2004 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */ #include #include #include #include "svn_time.h" #include "../svn_test.h" /* All these variables should refer to the same point in time. */ apr_time_t test_timestamp = APR_TIME_C(1021316450966679); const char *test_timestring = "2002-05-13T19:00:50.966679Z"; const char *test_old_timestring = "Mon 13 May 2002 22:00:50.966679 (day 133, dst 1, gmt_off 010800)"; static svn_error_t * test_time_to_cstring(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { const char *timestring; *msg = "test svn_time_to_cstring"; if (msg_only) return SVN_NO_ERROR; timestring = svn_time_to_cstring(test_timestamp,pool); if (strcmp(timestring,test_timestring) != 0) { return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "svn_time_to_cstring (%" APR_TIME_T_FMT ") returned date string '%s' instead of '%s'", test_timestamp,timestring,test_timestring); } return SVN_NO_ERROR; } static svn_error_t * test_time_from_cstring(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { apr_time_t timestamp; *msg = "test svn_time_from_cstring"; if (msg_only) return SVN_NO_ERROR; SVN_ERR(svn_time_from_cstring(×tamp, test_timestring, pool)); if (timestamp != test_timestamp) { return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "svn_time_from_cstring (%s) returned time '%" APR_TIME_T_FMT "' instead of '%" APR_TIME_T_FMT "'", test_timestring,timestamp,test_timestamp); } return SVN_NO_ERROR; } /* Before editing these tests cases please see the comment in * test_time_from_cstring_old regarding the requirements to exercise the bug * that they exist to test. */ static const char *failure_old_tests[] = { /* Overflow Day */ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" " 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)", /* Overflow Month */ "Tue 3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" " 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)", NULL }; static svn_error_t * test_time_from_cstring_old(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { apr_time_t timestamp; const char **ft; *msg = "test svn_time_from_cstring (old format)"; if (msg_only) return SVN_NO_ERROR; SVN_ERR(svn_time_from_cstring(×tamp, test_old_timestring, pool)); if (timestamp != test_timestamp) { return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "svn_time_from_cstring (%s) returned time '%" APR_TIME_T_FMT "' instead of '%" APR_TIME_T_FMT "'", test_old_timestring,timestamp,test_timestamp); } /* These tests should fail. They've been added to cover a string overflow * found in our code. However, even if they fail that may not indicate * that there is no problem. The strings being tested need to be * sufficently long to cause a segmentation fault in order to exercise * this bug. Unfortunately due to differences in compilers, architectures, * etc. there is no way to be sure that the bug is being exerercised on * all platforms. */ for (ft = failure_old_tests; *ft; ft++) { svn_error_t *err = svn_time_from_cstring(×tamp, *ft, pool); if (! err) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "svn_time_from_cstring (%s) succeeded when it should have failed", *ft); svn_error_clear(err); } return SVN_NO_ERROR; } static svn_error_t * test_time_invariant(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { apr_time_t current_timestamp = apr_time_now(); const char *timestring; apr_time_t timestamp; *msg = "test svn_time_[to/from]_cstring() invariant"; if (msg_only) return SVN_NO_ERROR; timestring = svn_time_to_cstring(current_timestamp, pool); SVN_ERR(svn_time_from_cstring(×tamp, timestring, pool)); if (timestamp != current_timestamp) { return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "svn_time_from_cstring ( svn_time_to_cstring (n) ) returned time '%" APR_TIME_T_FMT "' instead of '%" APR_TIME_T_FMT "'", timestamp,current_timestamp); } return SVN_NO_ERROR; } struct date_test { const char *str; apr_int32_t year; apr_int32_t mon; apr_int32_t mday; apr_int32_t hour; apr_int32_t min; apr_int32_t sec; apr_int32_t usec; }; /* These date strings do not specify a time zone, so we expand the svn_parse_date result it in the local time zone and verify that against the desired values. */ static struct date_test localtz_tests[] = { /* YYYY-M[M]-D[D] */ { "2013-01-25", 2013, 1, 25, 0, 0, 0, 0 }, { "2013-1-25", 2013, 1, 25, 0, 0, 0, 0 }, { "2013-01-2", 2013, 1, 2, 0, 0, 0, 0 }, /* YYYY-M[M]-D[D][Th[h]:mm[:ss[.u[u[u[u[u[u] */ { "2015-04-26T00:01:59.652655", 2015, 4, 26, 0, 1, 59, 652655 }, { "2034-07-20T17:03:36.11379", 2034, 7, 20, 17, 3, 36, 113790 }, { "1975-10-29T17:23:56.3859", 1975, 10, 29, 17, 23, 56, 385900 }, { "2024-06-08T13:06:14.897", 2024, 6, 8, 13, 6, 14, 897000 }, { "2000-01-10T05:11:11.65", 2000, 1, 10, 5, 11, 11, 650000 }, { "2017-01-28T07:21:13.2", 2017, 1, 28, 7, 21, 13, 200000 }, { "2013-05-18T13:52:49", 2013, 5, 18, 13, 52, 49, 0 }, { "2020-05-14T15:28", 2020, 5, 14, 15, 28, 0, 0 }, { "2032-05-14T7:28", 2032, 5, 14, 7, 28, 0, 0 }, { "2020-5-14T15:28", 2020, 5, 14, 15, 28, 0, 0 }, { "2020-05-1T15:28", 2020, 5, 1, 15, 28, 0, 0 }, /* YYYYMMDD */ { "20100226", 2010, 2, 26, 0, 0, 0, 0 }, /* YYYYMMDD[Thhmm[ss[.u[u[u[u[u[u] */ { "19860214T050745.6", 1986, 2, 14, 5, 7, 45, 600000 }, { "20230219T0045", 2023, 2, 19, 0, 45, 0, 0 }, /* YYYY-M[M]-D[D] [h]h:mm[:ss[.u[u[u[u[u[u] */ { "1975-07-11 06:31:49.749504", 1975, 7, 11, 6, 31, 49, 749504 }, { "2037-05-06 00:08", 2037, 5, 6, 0, 8, 0, 0 }, { "2037-5-6 7:01", 2037, 5, 6, 7, 1, 0, 0 }, /* Make sure we can do leap days. */ { "1976-02-29", 1976, 02, 29, 0, 0, 0, 0 }, { "2000-02-29", 2000, 02, 29, 0, 0, 0, 0 }, { NULL } }; /* These date strings specify an explicit time zone, so we expand the svn_parse_date result in UTC and verify that against the desired values (which have been adjusted for the specified time zone). */ static struct date_test gmt_tests[] = { /* YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u]Z */ { "1979-05-05T15:16:04.39Z", 1979, 5, 5, 15, 16, 4, 390000 }, { "2012-03-25T12:14Z", 2012, 3, 25, 12, 14, 0, 0 }, /* YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u]+OO[:oo] */ { "1991-09-13T20:13:01.12779+02:26", 1991, 9, 13, 17, 47, 1, 127790 }, { "1971-07-20T06:11-10", 1971, 7, 20, 16, 11, 0, 0 }, /* YYYYMMDDThhmm[ss[.u[u[u[u[u[u]Z */ { "20010808T105155.527Z", 2001, 8, 8, 10, 51, 55, 527000 }, { "19781204T1322Z", 1978, 12, 4, 13, 22, 0, 0 }, /* YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo] */ { "20030930T001647.8008-0230", 2003, 9, 30, 2, 46, 47, 800800 }, { "19810526T1705+22", 1981, 5, 25, 19, 5, 0, 0 }, /* YYYY-MM-DD hh:mm[:ss[.u[u[u[u[u[u][ +OO[oo] */ { "2024-06-02 11:30:36 +08", 2024, 6, 2, 3, 30, 36, 0 }, { "1994-10-07 08:08 -1457", 1994, 10, 07, 23, 5, 0, 0 }, { NULL } }; /* These date strings only specify a time of day, so we fill the current date into the desired values before comparing. (Currently we do not allow a time zone with just a time of day.) */ static struct date_test daytime_tests[] = { /* hh:mm[:ss[.u[u[u[u[u[u] */ { "00:54:15.46", 0, 0, 0, 0, 54, 15, 460000 }, { "18:21", 0, 0, 0, 18, 21, 0, 0 }, { NULL } }; /* These date strings should not parse correctly. */ static const char *failure_tests[] = { "2000-00-02", /* Invalid month */ "2000-13-02", /* Invalid month */ "2000-01-32", /* Invalid day */ "2000-01-00", /* Invalid day */ "1999-02-29", /* Invalid leap day */ "2000-01-01 24:00:00", /* Invalid hour */ "2000-01-01 00:60:00", /* Invalid minute */ "2000-01-01 00:00:61", /* Invalid second (60 is okay for leap seconds) */ "2000-01-01+24:00", /* Invalid timezone hours */ "2000-01-01-02:60", /* Invalid timezone minutes */ "2000-01-01Z", /* Date with timezone, but no time */ "2000-01-01+01:00", "20000101Z", "20000101-0100", "2000-01-01T10", /* Time with hours but no minutes */ "20000101T10", "2000-01-01 10", NULL }; static svn_error_t * compare_results(struct date_test *dt, apr_time_exp_t *expt) { if (expt->tm_year + 1900 != dt->year || expt->tm_mon + 1 != dt->mon || expt->tm_mday != dt->mday || expt->tm_hour != dt->hour || expt->tm_min != dt->min || expt->tm_sec != dt->sec || expt->tm_usec != dt->usec) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Comparison failed for '%s'", dt->str); return SVN_NO_ERROR; } static svn_error_t * test_parse_date(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { apr_time_t now, result; apr_time_exp_t nowexp, expt; svn_boolean_t matched; struct date_test *dt; const char **ft; *msg = "test svn_parse_date"; if (msg_only) return SVN_NO_ERROR; now = apr_time_now(); if (apr_time_exp_lt(&nowexp, now) != APR_SUCCESS) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "Can't expand time"); for (dt = localtz_tests; dt->str; dt++) { SVN_ERR(svn_parse_date(&matched, &result, dt->str, now, pool)); if (!matched) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Match failed for '%s'", dt->str); if (apr_time_exp_lt(&expt, result) != APR_SUCCESS) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Expand failed for '%s'", dt->str); SVN_ERR(compare_results(dt, &expt)); } for (dt = gmt_tests; dt->str; dt++) { SVN_ERR(svn_parse_date(&matched, &result, dt->str, now, pool)); if (!matched) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Match failed for '%s'", dt->str); if (apr_time_exp_gmt(&expt, result) != APR_SUCCESS) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Expand failed for '%s'", dt->str); SVN_ERR(compare_results(dt, &expt)); } for (dt = daytime_tests; dt->str; dt++) { SVN_ERR(svn_parse_date(&matched, &result, dt->str, now, pool)); if (!matched) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Match failed for '%s'", dt->str); if (apr_time_exp_lt(&expt, result) != APR_SUCCESS) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Expand failed for '%s'", dt->str); dt->year = nowexp.tm_year + 1900; dt->mon = nowexp.tm_mon + 1; dt->mday = nowexp.tm_mday; SVN_ERR(compare_results(dt, &expt)); } for (ft = failure_tests; *ft; ft++) { SVN_ERR(svn_parse_date(&matched, &result, *ft, now, pool)); if (matched) return svn_error_createf (SVN_ERR_TEST_FAILED, NULL, "Match succeeded for '%s'", *ft); } return SVN_NO_ERROR; } /* The test table. */ struct svn_test_descriptor_t test_funcs[] = { SVN_TEST_NULL, SVN_TEST_PASS(test_time_to_cstring), SVN_TEST_PASS(test_time_from_cstring), SVN_TEST_PASS(test_time_from_cstring_old), SVN_TEST_PASS(test_time_invariant), SVN_TEST_PASS(test_parse_date), SVN_TEST_NULL };