From: drh <> Date: Tue, 2 Dec 2025 11:40:45 +0000 (+0000) Subject: Initial code for a test program specifically for QRF. Maybe it would be X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Fqrftest;p=thirdparty%2Fsqlite.git Initial code for a test program specifically for QRF. Maybe it would be better just to use the TCL interface for this. Worked saved on this branch in case I ever decide to come back to it or reuse part of it. FossilOrigin-Name: ad6a56a2d2e23fb5abd9f42f8bf54044b20a987b62a91e6a9d6c70f374bcb15d --- diff --git a/ext/qrf/test/qrftest.c b/ext/qrf/test/qrftest.c new file mode 100644 index 0000000000..8905900060 --- /dev/null +++ b/ext/qrf/test/qrftest.c @@ -0,0 +1,360 @@ +/* +** 2025-12-01 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** A test harness for QRF. +*/ +#include +#include +#include +#include +#include +#include "sqlite3.h" +#include "qrf.h" + +/* Integer types */ +typedef sqlite3_int64 i64; +typedef unsigned char u8; + +/* +** State object for the test +*/ +typedef struct QrfTest QrfTest; +struct QrfTest { + int nErr; /* Number of errors */ + int nTest; /* Number of test cases */ + sqlite3 *db; /* Database connection used for tests */ + const char *zFilename; /* Input filename */ + i64 nLine; /* Line number of last line of input read */ + FILE *in; /* Input file stream */ + sqlite3_str *pExpected; /* Expected results */ + char *zResult; /* Results written here */ + sqlite3_str *pResult; /* Or here */ + sqlite3_str *pSql; /* Accumulated SQL script */ + sqlite3_qrf_spec spec; /* Output format spec */ +}; + +/* +** Report OOM and die +*/ +static void qrfTestOom(void){ + printf("Out of memory\n"); + exit(1); +} + +/* +** Change a string value in spec. +*/ +static void qrfTestSetStr(char **pz, const char *z){ + size_t n = strlen(z); + if( (*pz)!=0 ) sqlite3_free(*pz); + if( z==0 || strcmp(z,"")==0 ){ + *pz = 0; + }else{ + *pz = sqlite3_malloc64( n+1 ); + if( (*pz)==0 ) qrfTestOom(); + memcpy(*pz, z, n); + (*pz)[n] = 0; + } +} + +/* +** Free all resources held by p->spec. +*/ +static void qrfTestResetSpec(QrfTest *p){ + sqlite3_free(p->spec.aWidth); + sqlite3_free(p->spec.aAlign); + sqlite3_free(p->spec.zColumnSep); + sqlite3_free(p->spec.zRowSep); + sqlite3_free(p->spec.zTableName); + sqlite3_free(p->spec.zNull); + memset(&p->spec, 0, sizeof(p->spec)); + p->spec.iVersion = 1; +} + +/* +** Free all memory resources held by p. +*/ +static void qrfTestReset(QrfTest *p){ + if( p->in ){ fclose(p->in); p->in = 0; } + if( p->db ){ sqlite3_close(p->db); p->db = 0; } + if( p->pExpected ){ sqlite3_str_free(p->pExpected); p->pExpected = 0; } + if( p->zResult ){ sqlite3_free(p->zResult); p->zResult = 0; } + if( p->pResult ){ sqlite3_str_free(p->pResult); p->pResult = 0; } + qrfTestResetSpec(p); +} + +/* +** Report an error +*/ +static void qrfTestError(QrfTest *p, const char *zFormat, ...){ + va_list ap; + sqlite3_str *pErr; + va_start(ap, zFormat); + pErr = sqlite3_str_new(p->db); + sqlite3_str_vappendf(pErr, zFormat, ap); + va_end(ap); + printf("%s:%d: %s\n", p->zFilename, (int)p->nLine, sqlite3_str_value(pErr)); + sqlite3_str_free(pErr); + p->nErr++; +} + +/* +** Return a pointer to the next token in the input, or NULL if there +** are no more tokens. Leave *pz pointing to the first character past +** the end of the token. +*/ +static const char *nextToken(char **pz, int eMode){ + char *z = *pz; + char *zReturn = 0; + while( isspace(z[0]) ) z++; + if( eMode!=1 && z[0]=='*' && z[1]=='/' ) return 0; + zReturn = z; + if( z[0]==0 ) return 0; + while( z[0] && !isspace(z[0]) ){ z++; } + if( z[0] ){ + z[0] = 0; + z++; + } + *pz = z; + return zReturn; +} + +/* Arrays of names that map symbol names into numeric constants. */ + +static const char *azStyle[] = { + "auto", "box", "column", "count", "csv", "eqp", "explain", + "html", "insert", "json", "jobject", "line", "list", "markdown", + "off", "quote", "stats", "statsest", "statsvm", "table", 0 +}; +static const char *azEsc[] = { + "auto", "off", "ascii", "symbol", 0 +}; +static const char *azText[] = { + "auto", "plain", "sql", "csv", "html", "tcl", "json", 0 +}; +static const char *azBlob[] = { + "auto", "text", "sql", "hex", "tcl", "json", "size", 0 +}; +static const char *azBool[] = { + "auto", "off", "on", 0 +}; +static const char *azAlign[] = { "auto", "left", "right", "center", 0 }; + +/* +** Find the match for zArg, and azChoice[] and return its index. +** If not found, issue an error message and return 0; +*/ +static int findChoice( + QrfTest *p, + const char *zKey, + const char *zArg, + const char *const*azChoice +){ + int i; + sqlite3_str *pErr; + if( zArg==0 ){ + qrfTestError(p, "missing argument to \"%s\"", zKey); + return 0; + } + for(i=0; azChoice[i]; i++){ + if( strcmp(zArg,azChoice[i])==0 ) return i; + } + pErr = sqlite3_str_new(p->db); + sqlite3_str_appendf(pErr, "argument to %s should be one of:"); + for(i=0; azChoice[i]; i++){ + sqlite3_str_appendf(pErr, " %s", azChoice[i]); + } + qrfTestError(p, "%z", sqlite3_str_finish(pErr)); + return 0; +} + +/* +** zLine[] contains text that changes values of p->spec. Parse that +** line and make appropriate changes. +** +** Return 0 if zLine[] ends with and end-of-comment. Return 1 if the +** spec definition is to continue. +*/ +static int qrfTestParseSpec(QrfTest *p, char *zLine){ + const char *zToken; + while( (zToken = nextToken(&zLine,1))!=0 ){ + if( strcmp(zToken,"*/")==0 ) return 0; + if( strcmp(zToken,"eStyle")==0 ){ + p->spec.eStyle = findChoice(p, zToken, nextToken(&zLine,0), azStyle); + }else + if( strcmp(zToken,"eEsc")==0 ){ + p->spec.eEsc = findChoice(p, zToken, nextToken(&zLine,0), azEsc); + }else + if( strcmp(zToken,"eText")==0 ){ + p->spec.eText = findChoice(p, zToken, nextToken(&zLine,0), azText); + }else + if( strcmp(zToken,"eTitle")==0 ){ + p->spec.eTitle = findChoice(p, zToken, nextToken(&zLine,0), azText); + }else + if( strcmp(zToken,"eBlob")==0 ){ + p->spec.eBlob = findChoice(p, zToken, nextToken(&zLine,0), azBlob); + }else + if( strcmp(zToken,"bTitles")==0 ){ + p->spec.bTitles = findChoice(p, zToken, nextToken(&zLine,0),azBool); + }else + if( strcmp(zToken,"bWordWrap")==0 ){ + p->spec.bWordWrap = findChoice(p, zToken, nextToken(&zLine,0),azBool); + }else + if( strcmp(zToken,"bTextJsonb")==0 ){ + p->spec.bTextJsonb = findChoice(p, zToken, nextToken(&zLine,0),azBool); + }else + if( strcmp(zToken,"eDfltAlign")==0 ){ + p->spec.eDfltAlign = findChoice(p, zToken, nextToken(&zLine,0),azAlign); + }else + if( strcmp(zToken,"eTitleAlign")==0 ){ + p->spec.eTitleAlign = findChoice(p, zToken, nextToken(&zLine,0),azAlign); + }else + if( strcmp(zToken,"bSplitColumn")==0 ){ + p->spec.bSplitColumn = findChoice(p, zToken, nextToken(&zLine,0),azBool); + }else + if( strcmp(zToken,"bBorder")==0 ){ + p->spec.bBorder = findChoice(p, zToken, nextToken(&zLine,0),azBool); + }else + if( strcmp(zToken,"zColumnSep")==0 ){ + qrfTestSetStr(&p->spec.zColumnSep, nextToken(&zLine,0)); + }else + if( strcmp(zToken,"zRowSep")==0 ){ + qrfTestSetStr(&p->spec.zRowSep, nextToken(&zLine,0)); + }else + if( strcmp(zToken,"zTableName")==0 ){ + qrfTestSetStr(&p->spec.zTableName, nextToken(&zLine,0)); + }else + if( strcmp(zToken,"zNull")==0 ){ + qrfTestSetStr(&p->spec.zNull, nextToken(&zLine,0)); + }else + { + qrfTestError(p, "unknown spec key: \"%s\"", zToken); + } + } + return 1; +} + +/* +** Read and run a single test script. +** +** The file is SQL text. Special C-style comments control the testing. +** Because this description is itself within a C-style comment, the comment +** delimiters are shown as (*...*), with parentheses instead of "/". +** +** (* spec KEYWORD VALUE ... *) +** +** Fill out the p->spec field to use for the next test. +** +** (* result +** ** EXPECTED +** *) +** +** Run QRF and compare results against EXPECTED, with leading "** " +** removed. +** +*/ +static void qrfTestOneFile(QrfTest *p, const char *zFilename){ + int rc; /* Result code */ + int eMode; /* 0 = gather SQL. 1 = spec. 2 = gather result */ + char zLine[4000]; /* One line of input */ + + p->nLine = 0; + p->zFilename = zFilename; + p->in = 0; + p->zResult = 0; + p->pResult = 0; + p->pExpected = 0; + p->pSql = 0; + memset(&p->spec, 0, sizeof(p->spec)); + p->spec.iVersion = 1; + rc = sqlite3_open(":memory:", &p->db); + if( rc ){ + qrfTestError(p, "cannot open an in-memory database"); + return; + } + p->in = fopen(zFilename, "rb"); + if( p->in==0 ){ + qrfTestError(p, "cannot open input file \"%s\"", zFilename); + qrfTestReset(p); + return; + } + p->pSql = sqlite3_str_new(p->db); + p->pExpected = sqlite3_str_new(p->db); + while( fgets(zLine, sizeof(zLine), p->in) ){ + size_t n = strlen(zLine); + int done = 0; + p->nLine++; + if( n==0 ) continue; + if( zLine[n-1]!='\n' ){ + qrfTestError(p, "input line too long. Max length %d",(int)sizeof(zLine)); + qrfTestReset(p); + return; + } + do{ + done = 1; + switch( eMode ){ + case 0: { /* Gathering SQL text */ + if( strncmp(zLine, "/* spec", 7)==0 ){ + memmove(zLine, &zLine[7], n-7); + n-= 7; + eMode = 1; + done = 0; + }else if( strncmp(zLine, "/* result", 9)==0 ){ + /* qrfTestRunSql(p); */ + sqlite3_str_truncate(p->pExpected, 0); + eMode = 2; + }else{ + sqlite3_str_append(p->pSql, zLine, n); + } + break; + } + case 1: { /* Processing spec descriptions */ + eMode = qrfTestParseSpec(p, zLine); + break; + } + case 2: { + if( strncmp(zLine, "*/",2)==0 ){ + }else if( strcmp(zLine, "**\n")==0 ){ + sqlite3_str_append(p->pExpected, "\n", 1); + }else if( strncmp(zLine, "** ",3)==0 ){ + sqlite3_str_append(p->pExpected, zLine+3, n-3); + }else{ + qrfTestError(p, "bad result line"); + } + break; + } + } /* End of switch(eMode) */ + }while( !done ); + } + qrfTestReset(p); + if( sqlite3_memory_used()>0 ){ + qrfTestError(p, "Memory leak: %lld bytes", sqlite3_memory_used()); + } + p->nTest++; +} + +/* +** Program entry point +*/ +int main(int argc, char **argv){ + int i; + QrfTest x; + memset(&x, 0, sizeof(x)); + for(i=1; inErr ){ + printf("%s: %d error%s\n", argv[i], x.nErr-nErr, (x.nErr>nErr+1)?"s":""); + } + } + printf("Test cases: %d Errors: %d\n", x.nTest, x.nErr); +} diff --git a/main.mk b/main.mk index 627b7021cd..07d33eeb1c 100644 --- a/main.mk +++ b/main.mk @@ -1799,6 +1799,9 @@ testfixture$(T.exe): $(T.tcl.env.sh) has_tclsh85 $(TESTFIXTURE_SRC) $$TCL_LIB_SPEC $$TCL_INCLUDE_SPEC \ $(LDFLAGS.libsqlite3) +qrftest$(T.exe): sqlite3.c $(TOP)/ext/qrf/qrf.h $(TOP)/ext/qrf/qrf.c $(TOP)/ext/qrf/test/qrftest.c + $(T.link) -I$(TOP)/ext/qrf $(TOP)/ext/qrf/test/qrftest.c $(TOP)/ext/qrf/qrf.c sqlite3.c -o $@ $(LDFLAGS.libsqlite3) + coretestprogs: testfixture$(B.exe) sqlite3$(B.exe) testprogs: $(TESTPROGS) srcck1$(B.exe) fuzzcheck$(T.exe) sessionfuzz$(T.exe) diff --git a/manifest b/manifest index 78ce359306..246565499f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Disallow\sthe\sundocumented\s-recovery-db\soption\son\sthe\s".recover"\scommand\nof\sthe\sCLI\swhen\sin\s--safe\smode. -D 2025-12-01T11:07:31.854 +C Initial\scode\sfor\sa\stest\sprogram\sspecifically\sfor\sQRF.\s\sMaybe\sit\swould\sbe\nbetter\sjust\sto\suse\sthe\sTCL\sinterface\sfor\sthis.\s\sWorked\ssaved\son\sthis\sbranch\nin\scase\sI\sever\sdecide\sto\scome\sback\sto\sit\sor\sreuse\spart\sof\sit. +D 2025-12-02T11:40:45.005 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -419,6 +419,7 @@ F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d466 F ext/qrf/README.md 86fc5c3c5e3eddbe54fc1235cbdc52b8c2c0732791d224345c3014cd45c4c0e7 F ext/qrf/qrf.c 425d02cffcc5b5fe9ff5817794bf751b1fdd6912f570c354272429ce1262b866 F ext/qrf/qrf.h 322d48537a5aa39c206c2ec0764a7938ea7662a8c25be1c4e9d742789609ba1e +F ext/qrf/test/qrftest.c a2061dc04604dded25305b32689a01c567a75d8b015d3100116f16ee5a643c8c F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 @@ -657,7 +658,7 @@ F ext/wasm/tests/opfs/sahpool/index.html be736567fd92d3ecb9754c145755037cbbd2bca F ext/wasm/tests/opfs/sahpool/sahpool-pausing.js f264925cfc82155de38cecb3d204c36e0f6991460fff0cb7c15079454679a4e2 F ext/wasm/tests/opfs/sahpool/sahpool-worker.js bd25a43fc2ab2d1bafd8f2854ad3943ef673f7c3be03e95ecf1612ff6e8e2a61 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0 -F main.mk 822f9eda3e689748341597f4327a071c2b0ce41cc3ed477d72f2560b956eb5c0 +F main.mk 5a4954af031ac21dece30ebaf61c9594b8a441c2a0e4bb3636da2687d02ca70c F make.bat a136fd0b1c93e89854a86d5f4edcf0386d211e5d5ec2434480f6eea436c7420c F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -2180,8 +2181,11 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 35f39f7cb1030b1a57f2921f50ab600496fc1e774593717845e87f2e47dc49ba -R a7ecee17b65f2e02eed8810e0fb1d4ba +P 65202440874a7fea5aba262e8e78b97c833147d47837a99f301eca968f9a78b1 +R bd2d2c6a3b8d7b55e3f27798b187abb2 +T *branch * qrftest +T *sym-qrftest * +T -sym-trunk * U drh -Z 50d000086ea0500f9c141c467282e85a +Z 2febcbab07f4e3049a7b8ff917242afb # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index bec971799f..6a2a44f081 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch qrftest +tag qrftest diff --git a/manifest.uuid b/manifest.uuid index 4eb8918dd3..a5a0aca665 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -65202440874a7fea5aba262e8e78b97c833147d47837a99f301eca968f9a78b1 +ad6a56a2d2e23fb5abd9f42f8bf54044b20a987b62a91e6a9d6c70f374bcb15d