/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** Copyright (c) 2013 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** This file holds some basic sanity test code. */ #include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */ #include "fossil-scm/fossil-internal.h" #include #include static void fcli_local_help(){ printf("Usage:\n\t%s \n\n", fcli.appName); puts("Runs some basic sanity tests. Requires a checkout."); } static int test_sanity_0(){ int rc = 0; /* Testing a bug/fix: Build on a 32-bit environment (untested on 64-bit) Enable 'long long' typedef fsl_int32_t fsl_id_t #define FSL_ID_T_PFMT FSL_INT32_T_PFMT And then... */ char buf[80]; int i = 0; f_out("Checking for (not asserting) sizeof()/va_list " "weirdness... (use -V for more info)\n"); f_out("On 64-bit platforms these examples probably all look" "right (output=='1 2 3'), " "but mixing in a 64-bit type on a 32-bit platform can lead " "to weirdness (bugs) in conjunction with va_list arguments.\n"); f_out("#%d: %"FSL_ID_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_ID_T_PFMT"\n", ++i, 1, 2, 3); f_out("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n", ++i, (fsl_size_t)1, (fsl_id_t)2, (fsl_size_t)3); VERBOSE(("This next one (might) fail badly because the numeric arguments " "are passed on as integers and the va_arg() handling extracts " "a larger type, effectively skipping over arguments and potentially " "overrunning memory (i.e. corruption):\n")); f_out("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n", ++i, 1, 2, 3); #if 0 /* Interesting... we get warnings for printf()... Unfortunately, we can't recycle gcc's printf warnings here (via __attribute__), because it does not like our custom string formatting options. */ puts("printf(3) says:"); printf("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n", ++i, 1, 2, 3); #endif VERBOSE(("The trigger is the 'll' format specifier in conjunction with " "integer arguments which are not strongly typed and don't match " "their format specifier's expected sizeof() exactly. They are " "passed on as sizeof==4 and extracted as sizeof==8.\n")); VERBOSE(("It can be worked around by explicity casting the arguments " "to fsl_size_t resp. fsl_id_t, and this is sometimes needed " "EVEN IF the arguments are strongly typed (not sure why!).\n")); { fsl_size_t sz1 = 1, sz3 = 3; f_out("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n", ++i, sz1, 2, sz3); } /* Problem has something to do with the sizeof(int) resp. sizeof(fsl_id_t) and the va_arg() handling of the etRADIX bits in fsl_appendf(). The fix? Find the right combination of formatting string for integers in that combination. %d is apparently not correct. And it only appears to happen when we cross multiple levels of va_list handling? Namely, it happens in fsl_snprintf() but not fsl_output(): */ fsl_snprintf(buf, sizeof(buf), "#%d: ?? %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT" ??", (int)++i, (fsl_size_t)1, (fsl_id_t)2, (fsl_size_t)3); f_out("#%d fsl_snprintf() says: %s\n", i, buf); fsl_snprintf(buf, sizeof(buf), "#%d: ?? %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT" ??", ++i, 1, 2, 3); f_out("#%d fsl_snprintf() says: %s\n", i, buf); VERBOSE(("That one is (or was, at one point) failing differently in tcc/gcc " "in some build combinations.\n")); ++i; f_out("#%d: f_out() says: #%d: ?? %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT" ??\n", i, i, 1, 2, 3); /** Those fsl_snprintf() and f_out() calls SHOULD fail in the same way but they're not on my 32-bit box (in gcc - they do in tcc!). They go through the same code, differing only by how many times the va_list is passed on, thus my suspicion regarding the number of va_list indirections having something to do with this. */ return rc; } static int test_sanity_repo(){ fsl_cx * f = fcli.f; int rc = 0; fsl_buffer buf = fsl_buffer_empty; fsl_deck d = fsl_deck_empty; fsl_id_t rid; fsl_uuid_str uuid = NULL; char * str; fsl_db * db = fsl_cx_db_repo(f); fsl_size_t slen, slen2; f_out("Running basic uuid/rid tests against this repo...\n"); assert( 1==fsl_uuid_to_rid(f, "99237c3636730f20ed07b227c5092c087aea8b0c") ); assert( 1==fsl_uuid_to_rid(f, "99237c363673")); assert( 0>fsl_uuid_to_rid(f, "992") ); assert(f->error.code == FSL_RC_AMBIGUOUS); assert( strstr(fsl_buffer_cstr(&f->error.msg), "ambig") ); fcli_err_clear(); rc = fsl_sym_to_rid(f, "prev", FSL_ATYPE_CHECKIN, &rid); assert(!rc); assert(rid>0); rc = fsl_content_get(f, rid, &buf); assert(!rc); /* assert('B' == *fsl_buffer_cstr(&buf)); */ d.f = f; rc = fsl_deck_parse(&d, &buf); assert(0==rc); fsl_buffer_clear(&buf); assert(FSL_CATYPE_MANIFEST==d.type); assert(rid==d.rid); uuid = fsl_rid_to_uuid(f, d.rid); assert(uuid); assert(0==fsl_uuidcmp(d.uuid, uuid)); fsl_free(uuid); assert(d.D>0 && d.Derror.code); assert(db->error.msg.used); f_out("Caught expected SQL-triggered error: %b\n", &db->error.msg); fcli_err_clear(); return rc; } static int test_sanity_delta(){ fsl_buffer f1 = fsl_buffer_empty, f2 = fsl_buffer_empty, d12 = fsl_buffer_empty, d21 = fsl_buffer_empty, a1 = fsl_buffer_empty, a2 = fsl_buffer_empty; int rc; char const * F1 = __FILE__; char const * F2 = "f-sizeof.c"; fsl_size_t len1 = 0, len2 = 0; f_out("Running core delta tests...\n"); rc = fsl_buffer_fill_from_filename(&f1, F1); assert(!rc); rc = fsl_buffer_fill_from_filename(&f2, F2); assert(!rc); f_out("Input file sizes: f1: %"FSL_SIZE_T_PFMT", f2: %"FSL_SIZE_T_PFMT"\n", f1.used, f2.used); rc = fsl_buffer_delta_create(&f1, &f2, &d12); assert(!rc); rc = fsl_buffer_delta_create(&f2, &f1, &d21); assert(!rc); f_out("Delta sizes: f1=>f2: %"FSL_SIZE_T_PFMT", f2=>f1: %"FSL_SIZE_T_PFMT"\n", d12.used, d21.used); /* f_out("%b\n", &d12); */ rc = fsl_delta_applied_size(d12.mem, d12.used, &len1); assert(!rc); assert(len1==f2.used); rc = fsl_delta_applied_size(d21.mem, d21.used, &len2); assert(!rc); assert(len2==f1.used); rc = fsl_buffer_delta_apply(&f1, &d12, &a2); assert(!rc); rc = fsl_buffer_delta_apply(&f2, &d21, &a1); assert(!rc); if( fsl_buffer_compare(&f1,&a1) || fsl_buffer_compare(&f2, &a2) ){ fsl_fatal(FSL_RC_CONSISTENCY, "delta test failed"); } fsl_buffer_clear(&f1); fsl_buffer_clear(&f2); fsl_buffer_clear(&a1); fsl_buffer_clear(&a2); fsl_buffer_clear(&d12); fsl_buffer_clear(&d21); return rc; } static void test_sanity_tkt_01(){ fsl_buffer sql = fsl_buffer_empty; fsl_cx * f = fcli.f; char const * orig = fsl_schema_ticket(); int rc = fsl_cx_schema_ticket(f, &sql); assert(!rc); assert(sql.used>200); if(0) f_out("Ticket schema size=%"FSL_SIZE_T_PFMT ", default schema size=%"FSL_SIZE_T_PFMT"\n", sql.used, fsl_strlen(orig)); fsl_buffer_clear(&sql); } static void test_sanity_tkt_fields(){ fsl_cx * f = fcli.f; int rc; f_out("Loading custom ticket fields...\n"); rc = fsl_cx_ticket_load_fields(f, 0); assert(!rc); f_out("Ticket field count=%"FSL_SIZE_T_PFMT": ", f->ticket.customFields.used); for(rc = 0; rc < (int)f->ticket.customFields.used; ++rc){ fsl_card_J const * jc = f->ticket.customFields.list[rc]; f_out( "%s%s", rc ? ", " : "", jc->field); } f_out("\n"); } static void test_fs_mkdir(){ int rc; char const * path = "_sanity/foo/bar/baz/"; fsl_buffer b = fsl_buffer_empty; fsl_fstat fst = fsl_fstat_empty; f_out("fsl_mkdir2(%s,...)...\n", path); rc = fsl_mkdir2(path, 1); assert(0==rc); rc = fsl_stat( path, &fst, 1); assert(0==rc); assert(FSL_FSTAT_TYPE_DIR==fst.type); fsl_buffer_appendf(&b, "%//", __FILE__); rc = fsl_mkdir2(fsl_buffer_cstr(&b), 0); assert(FSL_RC_TYPE==rc); path = "f-sanity.c"; assert(fsl_is_file(path)); rc = fsl_mkdir2(path, 0); assert(0==rc) /* b/c no path component, nothing to do */; b.used = 0; fsl_buffer_appendf(&b, "%s/", path); rc = fsl_mkdir2(fsl_buffer_cstr(&b), 0); assert(FSL_RC_TYPE==rc); fsl_buffer_clear(&b); } static void test_fs_cx_stat(){ fsl_cx * f = fcli.f; int rc; fsl_fstat fst = fsl_fstat_empty; char const * fname = "src/fsl_cli.c"; fsl_time_t const now = time(0); f_out("fsl_cx_stat()...\n"); rc = fsl_cx_stat( f, "no-such-file", &fst ); assert(FSL_RC_NOT_FOUND==rc); rc = fsl_cx_stat( f, fname, &fst ); assert(0==rc); assert(FSL_FSTAT_TYPE_FILE==fst.type); assert(fst.ctime>0 && fst.ctime0 && fst.mtime0); } static void test_sanity_fs(){ char * s = NULL; fsl_buffer b = fsl_buffer_empty; int rc; fsl_fstat fst = fsl_fstat_empty; rc = fsl_find_home_dir(&b, 1); f_out("Home dir: %b\n", &b); assert(0==rc); b.used = 0; rc = fsl_stat(__FILE__, &fst, 1); assert(0==rc); assert(FSL_FSTAT_TYPE_FILE == fst.type); assert(fst.mtime>0); assert(fst.mtimeflags & FSL_CX_F_LOCALTIME_GMT)); t1 = fsl_cx_time_adj(f, now); ts1 = fsl_db_unix_to_iso8601(db, t1, 0); fsl_cx_flag_set(f, FSL_CX_F_LOCALTIME_GMT, 1 ); assert(f->flags & FSL_CX_F_LOCALTIME_GMT); t2 = fsl_cx_time_adj(f, now); ts2 = fsl_db_unix_to_iso8601(db, t2, 0); fsl_cx_flag_set(f, FSL_CX_F_LOCALTIME_GMT, 0 ); f_out("now=%s\nts1=%s\nts2=%s\n", tsNow, ts1, ts2); fsl_free(ts1); } #endif fsl_free(tsNow); fsl_free(ts2); } static void test_julian(){ char const * ts; double tolerance = 0 /* Tolerance, in Julian Days, for round-trip rounding errors. In my tests most time conversions are either exact or +/-1ms, but the exact range of error is probably at least partially platform-dependent. The differences between the reference JD values (taken from sqlite3) and the results are consistently (0, 0.000000001, and 0.000000011), which is quite close enough for what we do with these. */ ? 1 : 0.000000012; double eB; double eE; char buf[24]; double j = 0; char rc; char vbose = fcli.verbose; struct tester { /* ISO8601 time string. */ char const * ts; /* Expected JD result. This value is normally taken from sqlite3's strftime('%J', this->ts). */ double expect; } list[] = { {"2013-12-13T01:02:03.000", 2456639.543090278}, {"2013-09-10T23:35:53.471", 2456546.483257755}, {"2013-09-10T22:35:53.471", 2456546.441591088}, {"2013-09-10T21:35:53.471", 2456546.399924421}, {"2013-09-10T20:35:53.471", 2456546.358257755}, {"2013-09-10T19:35:53.471", 2456546.316591088}, {"2013-09-10T18:35:53.471", 2456546.274924421}, {"2013-09-10T17:35:53.471", 2456546.233257755}, {"2013-09-10T16:35:53.471", 2456546.191591088}, {"2013-09-10T15:35:53.471", 2456546.149924421}, {"2013-09-10T14:35:53.471", 2456546.108257755}, {"2013-09-10T13:14:15.167", 2456546.051564422}, {"2013-09-10T01:02:03.004", 2456545.543090324}, {"2013-09-09T12:11:10.987", 2456545.007766053}, {"2013-09-09T11:10:09.876", 2456544.965392072}, {"2013-02-02T12:13:14.000", 2456326.009189815}, {"2013-01-02T12:13:14.000", 2456295.009189815}, {"2011-12-31T12:51:46.686", 2455927.035957014}, {"2011-01-01T00:15:54.000", 2455562.511041666}, {"2008-12-20T02:23:18.000", 2454820.599513888}, {"2008-01-02T14:53:47.000", 2454468.12068287}, {"2007-07-21T14:09:59.000", 2454303.090266198}, {"2013-12-31T23:59:59.997", 2456658.499999954}, {0,0} }; struct tester * t; f_out("iso8601 <==> julian tests...\n"); for( t = list; t->ts; ++t ){ ts = t->ts; j = 0; if(vbose) f_out("\ninput : %s\n", ts); rc = fsl_iso8601_to_julian(ts, &j); assert(rc); if(vbose){ f_out("julian: %"FSL_JULIAN_T_PFMT"\n", j); f_out("expect: %"FSL_JULIAN_T_PFMT"\n", t->expect); } buf[0] = 0; rc = fsl_julian_to_iso8601(j, buf, 1); assert(rc); if(vbose) f_out("j=>iso: %s\n", buf); if(0 != t->expect){ eB = t->expect - tolerance; eE = t->expect + tolerance; assert( (j>=eB && j<=eE) ); } if(0!=fsl_strncmp(ts, buf, 22/*not the final digit*/)){ f_out("WARNING: round-trip mismatch at >ms level " "for:" "\n\t%"FSL_JULIAN_T_PFMT"\t%s" "\n\t%"FSL_JULIAN_T_PFMT"\t%s\n", t->expect, ts, j, buf); }else{ rc = fsl_julian_to_iso8601(j, buf, 0); if(vbose) f_out("j=>iso: %s\n", buf); assert(rc); if(0!=fsl_strncmp(ts, buf, 19)){ /* This is caused by: SS.000 ==> (SS-1).999 in julian-to-iso8601 */ f_out("WARNING: mismatch:\n\t%s\n\t%s\n", ts, buf); } } } } static void test_julian2(){ fsl_cx * f = fcli.f; fsl_db * db = fsl_cx_db_repo(f); double tolerance = 0 /* Tolerance, in Julian Days, for round-trip rounding errors. In my tests most time conversions are either exact or +/-1ms, but the exact range of error is probably at least partially platform-dependent. The differences between the reference JD values (taken from sqlite3) and the results are consistently (0, 0.000000001, and 0.000000011), which is quite close enough for what we do with these. */ ? 1 : 0.000000012; double eB; double eE; char buf[24]; int rc; char vbose = fcli.verbose>1; fsl_stmt q = fsl_stmt_empty; int counter = 0, warnings = 0, diffBy1Ms = 0; f_out("Running all event.mtimes through the julian<==>iso converters...\n"); rc = fsl_db_prepare(db, &q, "SELECT mtime, strftime('%%Y-%%m-%%dT%%H:%%M:%%f',mtime) " "FROM event ORDER BY mtime DESC " /*"LIMIT 100"*/ ); while( FSL_RC_STEP_ROW == fsl_stmt_step(&q) ){ char const * ts = fsl_stmt_g_text(&q, 1, NULL); double const jexp = fsl_stmt_g_double(&q, 0); double j = 0; int const oldWarn = warnings; ++counter; if(vbose) f_out("\ninput : %s\n", ts); rc = fsl_iso8601_to_julian(ts, &j); assert(rc); if(vbose){ f_out("julian: %"FSL_JULIAN_T_PFMT"\n", j); f_out("expect: %"FSL_JULIAN_T_PFMT"\n", jexp); } assert(j>0.0); buf[0] = 0; rc = fsl_julian_to_iso8601(j, buf, 1); assert(rc); if(vbose) f_out("j=>iso: %s\n", buf); eB = jexp - tolerance; eE = jexp + tolerance; assert( (j>=eB && j<=eE) ); if(0!=fsl_strncmp(ts, buf, 22/*not the final digit!*/)){ /* See if only the last three digits differ by 1 integer point (1ms), and don't warn if that's the case. There's a corner case there when N.x99 rolls over, and another for N.999, etc., but we'll punt on that problem. */ int const f1 = atoi(ts+20); int const f2 = atoi(buf+20); int const d = f1 - f2; /* assert(f1 || f2); */ if(d<-1 || d>1){ f_out("WARNING: possible round-trip fidelity mismatch: " "\n\twant: %"FSL_JULIAN_T_PFMT"\t%s" "\n\tgot : %"FSL_JULIAN_T_PFMT"\t%s\n", jexp, ts, j, buf); if(1==++warnings){ f_out("These are normally caused by, e.g., N.789 ==> N.790 " "or vice versa, which is still well within tolerance " "but is more difficult to test against in string form.\n" ); } }else{ if(0!=fsl_strncmp(ts, buf, 19)){ f_out("Mismatch at YMDHmS level:\n\t%s\n\t%s\n", ts, buf); } ++diffBy1Ms; } } /* Try without fractional part. These should never mismatch. */ rc = fsl_julian_to_iso8601(j, buf, 0); if(vbose) f_out("j=>iso: %s\n", buf); assert(rc); if(0!=fsl_strncmp(ts, buf, 19)){ /* f_out("VERY UNPEXECTED MISMATCH:\n\t%.*s\n\t%.*s\n", 19, ts, 19, buf); */ /* assert(!"These should never mismatch. " "Unless, of course, the SS.999 problem hits."); */ if(oldWarn == warnings) ++warnings /* avoid double counting */; /* This is caused by: SS.000 ==> (SS-1).999 in julian-to-iso8601. But that's "fixed" now - we shouldn't see this anymore. Still seeing it in the main fossil repo for about 0.27% of the records :/. */ } } fsl_stmt_finalize(&q); f_out("%d Julian timestamps tested from event.mtime.\n", counter); if(diffBy1Ms>0){ f_out("%d record(s) (%3.2f%%) differed round-trip by 1ms " "(this is \"normal\"/not unexpected).\n", diffBy1Ms, ((diffBy1Ms+0.0)/(counter+0.0)*100)); assert((1.0 > ((diffBy1Ms+0.0)/(counter+0.0)*100)) /*^^^ 1% was arbitrarily chosen! Increase if needed! */ && "Suspiciously many Julian/ISO8601 off-by-1ms conversions."); } if(warnings>0){ f_out("ACHTUNG: conversion warning count: %d (%3.2f%% of total)\n", warnings, ((warnings+0.0)/(counter+0.0)*100)); assert((1.0 > ((warnings+0.0)/(counter+0.0)*100)) /*^^^ 1% based on current rate of 0.51% of 779 records. */ && "Suspiciously many Julian/ISO8601 conversion warnings."); } } static void test_pathfinder(){ fsl_pathfinder PF = fsl_pathfinder_empty; fsl_pathfinder * pf = &PF; char const * found = NULL; int rc; f_out("fsl_pathfinder sanity checks...\n"); /* add() cannot fail here b/c of custom allocator, resp. it will abort() if it fails. */ fsl_pathfinder_dir_add(pf, "src"); fsl_pathfinder_ext_add(pf, ".c"); rc = fsl_pathfinder_search(pf, "fsl_fs", &found, NULL); assert(0==rc); assert(strstr(found, "fsl_fs.c")); f_out("found %s\n", found); rc = fsl_pathfinder_search(pf, "nono", &found, NULL); assert(FSL_RC_NOT_FOUND==rc); fsl_pathfinder_clear(pf); } static void test_fs_dirpart(){ fsl_buffer B = fsl_buffer_empty; fsl_buffer * b = &B; char const * cwd = "/foo/bar/baz"; fsl_size_t n = fsl_strlen(cwd); int rc; f_out("fsl_file_dirpart() tests...\n"); rc = fsl_file_dirpart(cwd, (fsl_int_t)n, b, 0); assert(0==rc); assert(0==fsl_strcmp("/foo/bar", fsl_buffer_cstr(b))); assert('/'!=b->mem[b->used-1]); b->used = 0; rc = fsl_file_dirpart(cwd, (fsl_int_t)n, b, 1); assert(0==rc); assert(0==fsl_strcmp("/foo/bar/", fsl_buffer_cstr(b))); assert('/'==b->mem[b->used-1]); b->used = 0; rc = fsl_file_dirpart("/", 1, b, 0); assert(0==rc); assert(0==b->used); rc = fsl_file_dirpart("/", 1, b, 1); assert(1==b->used); assert('/'==b->mem[0]); fsl_buffer_clear(b); } static void test_dir_names(){ fsl_list li = fsl_list_empty; int rc; f_out("fsl_dirpart() UDF tests...\n"); rc = fsl_repo_dir_names( fcli.f, &li, 0 ); assert(!rc); assert(li.used>0); fsl_list_visit_free(&li, 1); } int main(int argc, char * const * argv ){ int rc = 0; fsl_cx * f; fcli.appHelp = fcli_local_help; rc = fcli_setup(argc, argv); if(FSL_RC_BREAK==rc) /* --help */return 0; else if(rc) goto end; f = fcli.f; f_out("Checkout dir=%s\n", fsl_cx_dir_name_checkout(f, NULL)); f_out("Checkout db=%s\n", fsl_cx_db_file_checkout(f, NULL)); f_out("Repo db=%s\n", fsl_cx_db_file_repo(f, NULL)); if(1){ rc = test_sanity_0(); if(!rc) rc = test_sanity_repo(); if(!rc) rc = test_sanity_delta(); if(!rc){ test_pathfinder(); test_fs_dirpart(); test_sanity_tkt_01(); test_sanity_tkt_fields(); test_sanity_fs(); test_sanity_localtime(); test_julian(); test_julian2(); test_dir_names(); } }else{ /* placeholder for use while developing tests. */ } end: f_out("If you made it this far, no assertions were triggered. " "Now try again with valgrind.\n"); return (fcli_err_report(0)||rc) ? EXIT_FAILURE : EXIT_SUCCESS; }