/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). ***************************************************************************** A test/demo app for doing mass parsing tests on all artifacts in a repository. */ #include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */ #include "fossil-scm/fossil-internal.h" #include static struct App_{ bool failFast; bool crosslink; bool quiet; bool wellFormed; bool randomOrder; bool clearLinks; bool rebuildLeaves; const char * eventTypes; } App = { false/*failFast*/, false/*crosslink*/, false/*quiet*/, true/*wellFormed*/, false/*randomOrder*/, true/*clearLinks*/, true/*rebuildLeaves*/, NULL/*eventTypes*/ }; static int my_xlink_f(fsl_deck * d, void * state){ if(fcli_is_verbose()>1){ FCLI_VN(2,("Crosslinking rid %"FSL_ID_T_PFMT" ...\n", d->rid)); }else if(!App.quiet){ f_out("x"); } return 0; } static int test_parse_all(void){ fsl_buffer content = fsl_buffer_empty; fsl_deck mf = fsl_deck_empty; fsl_cx * const f = fcli_cx(); fsl_buffer q = fsl_buffer_empty; fsl_stmt q1 = fsl_stmt_empty; fsl_db * const db = fsl_cx_db_repo(f); int rc = 0; int counter = 0; int errCount = 0; int counters[FSL_SATYPE_count] = {0,0,0,0,0,0,0,0,0}; bool got[FSL_SATYPE_count] = {0,0,0,0,0,0,0,0,0}; fsl_buffer eventTypesIn = fsl_buffer_empty; fsl_timer_state timer = fsl_timer_state_empty; uint64_t runtimeC = 0/*fsl_content_get()*/, runtimeP = 0/*fsl_deck_parse()*/, runtimeX = 0/*crosslinking*/ /* TODO: break those into separate counters for each artifact type. */; unsigned short const verbose = fcli_is_verbose(); if(!db){ return fsl_cx_err_set(f, FSL_RC_MISUSE, "This app requires a repository db."); } if(App.crosslink){ rc = fsl_xlink_listener( f, "parseparty", my_xlink_f, 0 ); if(!rc) rc = fsl__crosslink_begin(f); if(rc) goto end; } rc = fsl_db_transaction_begin(db); if(rc) goto end; #define RC if(rc) goto end if(App.eventTypes && *App.eventTypes){ const char * c = App.eventTypes; fsl_buffer_append(&q, "SELECT e.objid, b.uuid FROM event e, blob b " "WHERE e.objid=b.rid AND type in ", -1); for(; *c; ++c){ const char * eType = 0; switch(*c){ case 'c': eType = "ci"; got[FSL_SATYPE_CHECKIN] = true; break; case 'g': eType = "g"; got[FSL_SATYPE_CONTROL] = true; break; case 't': eType = "t"; got[FSL_SATYPE_TICKET] = true; break; case 'n': eType = "e"; got[FSL_SATYPE_TECHNOTE] = true; break; case 'w': eType = "w"; got[FSL_SATYPE_WIKI] = true; break; case 'f': eType = "f"; got[FSL_SATYPE_FORUMPOST] = true; break; default: fsl_cx_err_set(f, FSL_RC_MISUSE, "Unknown --types value '%c'.", *c); fsl_buffer_clear(&q); goto end; } fsl_buffer_append(&eventTypesIn, (c==App.eventTypes ? "(" : ","), 1); fsl_buffer_appendf(&eventTypesIn, "%Q", eType); } fsl_buffer_append(&eventTypesIn, ")", 1); fsl_buffer_appendf(&q, "%b ORDER BY ", &eventTypesIn); fsl_buffer_append(&q, App.randomOrder ? "RANDOM()" : "mtime DESC", -1); rc = fsl_db_prepare(db, &q1, fsl_buffer_cstr(&q)); }else{ int i; for(i = 0; i < (int)(sizeof(got)/sizeof(got[0])); ++i){ got[i] = true; } got[FSL_SATYPE_CHECKIN] = true; fsl_buffer_append(&q, "SELECT e.objid, b.uuid FROM event e, blob b " "WHERE e.objid=b.rid ORDER BY ", -1); fsl_buffer_append(&q, App.randomOrder ? "RANDOM()" : "mtime", -1); rc = fsl_db_prepare(db, &q1, fsl_buffer_cstr(&q)); } FCLI_V(("Query = %b\n", &q)); fsl_buffer_clear(&q); if(rc){ rc = fsl_cx_uplift_db_error2(f, db, rc); goto end; } if(App.crosslink){ if(0 && App.crosslink && eventTypesIn.used){ /* If we do this, the for-each-entry loop fails because it has no data, of course. */ f_out("Deleting timeline entries for re-crosslinking types: %b\n", &eventTypesIn); fsl_db_exec(db, "DELETE FROM event WHERE type IN (%b)", &eventTypesIn); } if(got[FSL_SATYPE_CHECKIN]){ FCLI_V(("Deleting mlink/plink entries! If crosslinking " "breaks, use 'fossil rebuild' to recover.\n")); rc = fsl_db_exec_multi(db, "DELETE FROM mlink; " "DELETE FROM plink;"); if(!rc && App.rebuildLeaves){ FCLI_V(("Deleting leaf entries (will be rebuilt).\n")); rc = fsl_db_exec_multi(db, "DELETE FROM leaf;"); } if(!rc && got[FSL_SATYPE_CONTROL]){ FCLI_V(("Deleting most tags!")); rc = fsl_db_exec_multi(db, "CREATE TEMP TABLE X AS " "SELECT tagid FROM TAG WHERE tagid>%d " "AND tagname NOT LIKE 'wiki-%%' " "AND tagname NOT LIKE 'tkt-%%' " "AND tagname NOT LIKE 'event-%%'; " "DELETE FROM tagxref WHERE tagid IN X; " "DELETE FROM tag WHERE tagid IN X; " "DROP TABLE X;" "", FSL_TAGID_NOTE); } /*fsl_db_each( db, fsl_stmt_each_f_dump, NULL, "SELECT tagname from tag" );*/ } if(!rc && got[FSL_SATYPE_WIKI]){ FCLI_V(("Deleting wiki-* tags entries! If crosslinking " "breaks, use 'fossil rebuild' to recover.\n")); rc = fsl_db_exec_multi(db, "DELETE FROM tagxref WHERE tagid IN(" "SELECT tagid FROM tag " "WHERE tagname LIKE 'wiki-%%'" "); " "DELETE FROM tag " "WHERE tagname LIKE 'wiki-%%';" ); } if(!rc && got[FSL_SATYPE_EVENT]){ FCLI_V(("Deleting event-* tags entries! If crosslinking " "breaks, use 'fossil rebuild' to recover.\n")); rc = fsl_db_exec_multi(db, "DELETE FROM tagxref WHERE tagid IN(" "SELECT tagid FROM tag " "WHERE tagname LIKE 'event-%%'" ");" "DELETE FROM tag " "WHERE tagname LIKE 'event-%%';" ); } if(rc){ rc = fsl_cx_uplift_db_error2(f, db, rc); goto end; } }/*App.crosslink*/ f_out("Here we go...\n"); mf.f = f; while(FSL_RC_STEP_ROW==fsl_stmt_step(&q1)){ fsl_id_t const rid = fsl_stmt_g_id(&q1, 0); const char * zUuid = fsl_stmt_g_text(&q1, 1, 0); const char * lineBreak = verbose ? "" : "\n"; bool wellFormedCheck = true; assert(rid>0); if(1) fsl_deck_clean2(&mf,&content) /* Saves only a tiny amount of allocation, largely because fsl_content_get() has to create many temporary buffers for de-deltification and we don't currently have a mechanism for reusing those. */; else fsl_deck_clean(&mf); fsl_timer_start(&timer); rc = fsl_content_get(f, rid, &content); runtimeC += fsl_timer_stop(&timer); RC; if(App.wellFormed && !fsl_might_be_artifact(&content)){ wellFormedCheck = 0; } assert(mf.f); fsl_timer_start(&timer); rc = fsl_deck_parse2(&mf, &content, rid); runtimeP += fsl_timer_stop(&timer); if(fcli_is_verbose()<2 && !App.quiet){ f_out("."); fflush(stdout); } if(rc){ ++errCount; f_out("%sparse-offending artifact: %d / %s\n", lineBreak, (int)rid, zUuid); if(App.failFast){ goto end; } fcli_err_report(1); continue; }else{ assert(!content.mem && "API says that deck takes over memory on success."); } assert(mf.rid); assert(mf.type>=0 && mf.type