/* -*- 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 <time.h>
#ifndef _WIN32
#include <unistd.h> /*isatty()*/
#endif
static struct App_{
bool failFast;
bool crosslink;
bool quiet;
const char * eventTypes;
} App = {
0/*failFast*/,
0/*crosslink*/,
0/*quiet*/,
0/*eventTypes*/
};
static int my_xlink_f(fsl_deck * d, void * state){
if(fcli.verbose){
FCLI_VN(1,("Crosslinking rid %"FSL_ID_T_PFMT", uuid %s ...\n",
d->rid, d->uuid));
}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.f;
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};
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;
}
#define RC if(rc) goto end
if(App.eventTypes && *App.eventTypes){
fsl_buffer q = fsl_buffer_empty;
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"; break;
case 'g': eType = "g"; break;
case 't': eType = "t"; break;
case 'n': eType = "e"; break;
case 'w': eType = "w"; break;
case 'f': eType = "f"; break;
default:
fsl_cx_err_set(f, FSL_RC_MISUSE, "Unknown --types value '%c'.", *c);
fsl_buffer_clear(&q);
goto end;
}
fsl_buffer_append(&q, (c==App.eventTypes ? "(" : ","), 1);
fsl_buffer_appendf(&q, "%Q", eType);
}
fsl_buffer_append(&q, ") ORDER BY mtime", -1);
FCLI_V(("Query=%b\n", &q));
rc = fsl_db_prepare(db, &q1, fsl_buffer_cstr(&q));
fsl_buffer_clear(&q);
}else{
rc = fsl_db_prepare(db, &q1, "SELECT e.objid, b.uuid FROM event e, blob b "
"WHERE e.objid=b.rid ORDER BY RANDOM()");
}
if(rc){
fsl_cx_uplift_db_error(f, db);
goto end;
}
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 = !fcli.verbose ? "\n" : "";
assert(rid>0);
fsl_buffer_reuse(&content);
rc = fsl_content_get(f, rid, &content);
RC;
fsl_deck_clean(&mf);
assert(mf.f);
rc = fsl_deck_parse(&mf, &content);
if(!fcli.verbose && !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;
}
assert(mf.rid);
assert(mf.uuid);
assert(mf.type>=0 && mf.type<FSL_SATYPE_count);
assert(!content.mem && "Was handed off to mf.");
#if 0
/* These assertions are wrong for phantom artifact cases. The
libfossil tree contains some artifacts, for testing purposes,
from the main fossil tree, which results in phantoms (the
hashes those artifacts reference but which we don't have). */
assert(mf.rid);
assert(mf.uuid);
#endif
FCLI_VN(1,("Parsed rid %d, uuid %s\n", (int)mf.rid, mf.uuid));
if(App.crosslink){
rc = fsl_deck_crosslink(&mf);
if(rc){
if(FSL_RC_NOT_FOUND==rc){
/* Assume this is an artifact, like 4b05c2c59fa61f1240d41949b305173c76d1395d,
which exists as an artifact file but is not part of this project. */
f_out("%sFAILED NON-FATALLY crosslinking rid %d, uuid %s\n",
lineBreak, (int)mf.rid, mf.uuid, fsl_rc_cstr(rc));
fcli_err_report(1);
rc = 0;
}else{
f_out("%sFAILED crosslinking rid %d, uuid %s w/ rc=%s\n",
lineBreak, (int)mf.rid, mf.uuid, fsl_rc_cstr(rc));
fcli_err_report(1);
if(App.failFast){
break;
}
}
}
}
++counter;
++counters[mf.type];
/* TODO? Optionally check R-card calculation if
mf.type==FSL_SATYPE_CHECKIN and mf.R is not NULL. */
}
f_out("\nSuccessfully processed %d artifact(s):\n", counter);
assert(0==counters[FSL_SATYPE_ANY]);
#define CAT(T) if(counters[T]){f_out("%-22s = %d\n", #T, counters[T]);} (void)0
CAT(FSL_SATYPE_CHECKIN);
CAT(FSL_SATYPE_CLUSTER);
CAT(FSL_SATYPE_CONTROL);
CAT(FSL_SATYPE_WIKI);
CAT(FSL_SATYPE_TICKET);
CAT(FSL_SATYPE_ATTACHMENT);
CAT(FSL_SATYPE_EVENT);
CAT(FSL_SATYPE_FORUMPOST);
#undef CAT
if(errCount){
f_out("ERROR count: %d\n", errCount);
}
end:
if(App.crosslink){
if(rc){
f_out("Something failed. Rolling back crosslink-started transaction.\n");
fsl_db_transaction_rollback(db);
}else{
fsl_cx_err_reset(f);
f_out("Ending crosslinking process (might take a few ms)...\n");
rc = fsl_crosslink_end(f);
}
}
#undef RC
fsl_stmt_finalize(&q1);
fsl_buffer_clear(&content);
fsl_deck_finalize(&mf);
return rc;
}
int main(int argc, char const * const * argv ){
int rc = 0;
bool skipUnknown = false;
fcli_cliflag FCliFlags[] = {
FCLI_FLAG("t","types","list",&App.eventTypes,
"List of letters of artifact types to parse: "
"c=checkin, g=control (tags), w=wiki, "
"t=ticket, n=technote, f=forum"),
FCLI_FLAG_BOOL("F","fail-fast",&App.failFast,
"Stop processing at the first error."),
FCLI_FLAG_BOOL("c","crosslink",&App.crosslink,
"Crosslink all parsed artifacts."),
FCLI_FLAG_BOOL("q","quiet",&App.quiet,
"Disables certain output."),
FCLI_FLAG_BOOL("s","skip-unknown",&skipUnknown,
"Treates failures due to crosslinking "
"as-yet-unsupported/unknown artifact types as "
"non-errors."),
fcli_cliflag_empty_m
};
fcli_help_info FCliHelp = {
"Tests the libfossil artifact parser by parsing in-repo artifacts.",
NULL, NULL
};
fcli.cliFlags = FCliFlags;
fcli.appHelp = &FCliHelp;
rc = fcli_setup(argc, argv);
if(FCLI_RC_HELP==rc) /* --help */ return EXIT_SUCCESS;
else if(rc) goto end;
if(skipUnknown){
fsl_cx_flag_set(fcli_cx(), FSL_CX_SKIP_UNKNOWN_CROSSLINKS, 1);
}
if(fcli_has_unused_flags(0)) goto end;
rc = test_parse_all();
end:
return (fcli_err_report(1) || rc) ? EXIT_FAILURE : EXIT_SUCCESS;
}