1 | /* |
2 | ** Copyright (c) 2008 D. Richard Hipp |
3 | ** |
4 | ** This program is free software; you can redistribute it and/or |
5 | ** modify it under the terms of the GNU General Public |
6 | ** License version 2 as published by the Free Software Foundation. |
7 | ** |
8 | ** This program is distributed in the hope that it will be useful, |
9 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | ** General Public License for more details. |
12 | ** |
13 | ** You should have received a copy of the GNU General Public |
14 | ** License along with this library; if not, write to the |
15 | ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
16 | ** Boston, MA 02111-1307, USA. |
17 | ** |
18 | ** Author contact information: |
19 | ** drh@hwaci.com |
20 | ** http://www.hwaci.com/drh/ |
21 | ** |
22 | ******************************************************************************* |
23 | ** |
24 | ** This main driver for the sqllogictest program. |
25 | */ |
26 | #include "catch.hpp" |
27 | #include "sqllogictest.hpp" |
28 | |
29 | #include <ctype.h> |
30 | #include <stdio.h> |
31 | #include <stdlib.h> |
32 | #ifndef _WIN32 |
33 | #include <unistd.h> |
34 | #define stricmp strcasecmp |
35 | #endif |
36 | #include "slt_duckdb.hpp" |
37 | |
38 | #include <algorithm> |
39 | #include <dirent.h> |
40 | #include <functional> |
41 | #include <string.h> |
42 | #include <string> |
43 | #include <vector> |
44 | |
45 | using namespace std; |
46 | |
47 | #define DEFAULT_HASH_THRESHOLD 8 |
48 | |
49 | /* |
50 | ** A structure to keep track of the state of scanning the input script. |
51 | */ |
52 | typedef struct Script Script; |
53 | struct Script { |
54 | char *zScript; /* Complete text of the input script */ |
55 | int iCur; /* Index in zScript of start of current line */ |
56 | char *zLine; /* Pointer to start of current line */ |
57 | int len; /* Length of current line */ |
58 | int iNext; /* index of start of next line */ |
59 | int nLine; /* line number for the current line */ |
60 | int iEnd; /* Index in zScript of '\000' at end of script */ |
61 | int startLine; /* Line number of start of current record */ |
62 | int copyFlag; /* If true, copy lines to output as they are read */ |
63 | char azToken[4][200]; /* tokenization of a line */ |
64 | }; |
65 | |
66 | // stub because not used |
67 | void sqllogictestRegisterEngine(const DbEngine *p) { |
68 | } |
69 | |
70 | /* |
71 | ** Advance the cursor to the start of the next non-comment line of the |
72 | ** script. Make p->zLine point to the start of the line. Make p->len |
73 | ** be the length of the line. Zero-terminate the line. Any \r at the |
74 | ** end of the line is removed. |
75 | ** |
76 | ** Return 1 on success. Return 0 and no-op at end-of-file. |
77 | */ |
78 | static int nextLine(Script *p) { |
79 | int i; |
80 | |
81 | /* Loop until a non-comment line is found, or until end-of-file */ |
82 | while (1) { |
83 | /* When we reach end-of-file, return 0 */ |
84 | if (p->iNext >= p->iEnd) { |
85 | p->iCur = p->iEnd; |
86 | p->zLine = &p->zScript[p->iEnd]; |
87 | p->len = 0; |
88 | return 0; |
89 | } |
90 | |
91 | /* Advance the cursor to the next line */ |
92 | p->iCur = p->iNext; |
93 | p->nLine++; |
94 | p->zLine = &p->zScript[p->iCur]; |
95 | for (i = p->iCur; i < p->iEnd && p->zScript[i] != '\n'; i++) { |
96 | } |
97 | p->zScript[i] = 0; |
98 | p->len = i - p->iCur; |
99 | p->iNext = i + 1; |
100 | |
101 | /* If the current line ends in a \r then remove the \r. */ |
102 | if (p->len > 0 && p->zScript[i - 1] == '\r') { |
103 | p->len--; |
104 | i--; |
105 | p->zScript[i] = 0; |
106 | } |
107 | |
108 | /* If the line consists of all spaces, make it an empty line */ |
109 | for (i = i - 1; i >= p->iCur && isspace(p->zScript[i]); i--) { |
110 | } |
111 | if (i < p->iCur) { |
112 | p->zLine[0] = 0; |
113 | } |
114 | |
115 | /* If the copy flag is set, write the line to standard output */ |
116 | if (p->copyFlag) { |
117 | printf("%s\n" , p->zLine); |
118 | } |
119 | |
120 | /* If the line is not a comment line, then we are finished, so break |
121 | ** out of the loop. If the line is a comment, the loop will repeat in |
122 | ** order to skip this line. */ |
123 | if (p->zLine[0] != '#') |
124 | break; |
125 | } |
126 | return 1; |
127 | } |
128 | |
129 | /* |
130 | ** Look ahead to the next line and return TRUE if it is a blank line. |
131 | ** But do not advance to the next line yet. |
132 | */ |
133 | static int nextIsBlank(Script *p) { |
134 | int i = p->iNext; |
135 | if (i >= p->iEnd) |
136 | return 1; |
137 | while (i < p->iEnd && isspace(p->zScript[i])) { |
138 | if (p->zScript[i] == '\n') |
139 | return 1; |
140 | i++; |
141 | } |
142 | return 0; |
143 | } |
144 | |
145 | /* |
146 | ** Advance the cursor to the start of the next record. To do this, |
147 | ** first skip over the tail section of the record in which we are |
148 | ** currently located, then skip over blank lines. |
149 | ** |
150 | ** Return 1 on success. Return 0 at end-of-file. |
151 | */ |
152 | static int findStartOfNextRecord(Script *p) { |
153 | /* Skip over any existing content to find a blank line */ |
154 | if (p->iCur > 0) { |
155 | while (p->zLine[0] && p->iCur < p->iEnd) { |
156 | nextLine(p); |
157 | } |
158 | } else { |
159 | nextLine(p); |
160 | } |
161 | |
162 | /* Skip over one or more blank lines to find the first line of the |
163 | ** new record */ |
164 | while (p->zLine[0] == 0 && p->iCur < p->iEnd) { |
165 | nextLine(p); |
166 | } |
167 | |
168 | /* Return 1 if we have not reached end of file. */ |
169 | return p->iCur < p->iEnd; |
170 | } |
171 | |
172 | /* |
173 | ** Find a single token in a string. Return the index of the start |
174 | ** of the token and the length of the token. |
175 | */ |
176 | static void findToken(const char *z, int *piStart, int *pLen) { |
177 | int i; |
178 | int iStart; |
179 | for (i = 0; isspace(z[i]); i++) { |
180 | } |
181 | *piStart = iStart = i; |
182 | while (z[i] && !isspace(z[i])) { |
183 | i++; |
184 | } |
185 | *pLen = i - iStart; |
186 | } |
187 | |
188 | #define count(X) (sizeof(X) / sizeof(X[0])) |
189 | |
190 | /* |
191 | ** tokenize the current line in up to 3 tokens and store those values |
192 | ** into p->azToken[0], p->azToken[1], and p->azToken[2]. Record the |
193 | ** current line in p->startLine. |
194 | */ |
195 | static void tokenizeLine(Script *p) { |
196 | int i, j, k; |
197 | int len, n; |
198 | for (i = 0; i < (int)count(p->azToken); i++) |
199 | p->azToken[i][0] = 0; |
200 | p->startLine = p->nLine; |
201 | for (i = j = 0; j < p->len && i < (int)count(p->azToken); i++) { |
202 | findToken(&p->zLine[j], &k, &len); |
203 | j += k; |
204 | n = len; |
205 | if (n >= (int)sizeof(p->azToken[0])) { |
206 | n = sizeof(p->azToken[0]) - 1; |
207 | } |
208 | memcpy(p->azToken[i], &p->zLine[j], n); |
209 | p->azToken[i][n] = 0; |
210 | j += n + 1; |
211 | } |
212 | } |
213 | |
214 | /* |
215 | ** The number columns in a row of the current result set |
216 | */ |
217 | static int nColumn = 0; |
218 | |
219 | /* |
220 | ** Comparison function for sorting the result set. |
221 | */ |
222 | static int rowCompare(const void *pA, const void *pB) { |
223 | const char **azA = (const char **)pA; |
224 | const char **azB = (const char **)pB; |
225 | int c = 0, i; |
226 | for (i = 0; c == 0 && i < nColumn; i++) { |
227 | c = strcmp(azA[i], azB[i]); |
228 | } |
229 | return c; |
230 | } |
231 | |
232 | /* |
233 | ** Entry in a hash table of prior results |
234 | */ |
235 | typedef struct HashEntry HashEntry; |
236 | struct HashEntry { |
237 | char zKey[24]; /* The search key */ |
238 | char zHash[33]; /* The hash value stored */ |
239 | HashEntry *pNext; /* Next with same hash */ |
240 | HashEntry *pAll; /* Next overall */ |
241 | }; |
242 | |
243 | /* |
244 | ** The hash table |
245 | */ |
246 | #define NHASH 1009 |
247 | static HashEntry *aHash[NHASH]; |
248 | static HashEntry *pAll; |
249 | |
250 | /* |
251 | ** Try to look up the value zKey in the hash table. If the value |
252 | ** does not exist, create it and return 0. If the value does already |
253 | ** exist return 0 if hash matches and 1 if the hash is different. |
254 | */ |
255 | static int checkValue(const char *zKey, const char *zHash) { |
256 | unsigned int h; |
257 | HashEntry *p; |
258 | unsigned int i; |
259 | |
260 | h = 0; |
261 | for (i = 0; zKey[i] && i < sizeof(p->zKey); i++) { |
262 | h = h << 3 ^ h ^ zKey[i]; |
263 | } |
264 | h = h % NHASH; |
265 | for (p = aHash[h]; p; p = p->pNext) { |
266 | if (strcmp(p->zKey, zKey) == 0) { |
267 | return strcmp(p->zHash, zHash) != 0; |
268 | } |
269 | } |
270 | p = (HashEntry *)malloc(sizeof(*p)); |
271 | if (p == 0) { |
272 | fprintf(stderr, "out of memory at %s:%d\n" , __FILE__, __LINE__); |
273 | exit(1); |
274 | } |
275 | for (i = 0; zKey[i] && i < sizeof(p->zKey) - 1; i++) { |
276 | p->zKey[i] = zKey[i]; |
277 | } |
278 | p->zKey[i] = 0; |
279 | for (i = 0; zHash[i] && i < sizeof(p->zHash) - 1; i++) { |
280 | p->zHash[i] = zHash[i]; |
281 | } |
282 | p->zHash[i] = 0; |
283 | p->pAll = pAll; |
284 | pAll = p; |
285 | p->pNext = aHash[h]; |
286 | aHash[h] = p; |
287 | return 0; |
288 | } |
289 | |
290 | #define IFAIL() \ |
291 | { \ |
292 | if (zScript) \ |
293 | free(zScript); \ |
294 | if (pConn) \ |
295 | pEngine->xDisconnect(pConn); \ |
296 | REQUIRE(false); \ |
297 | return; \ |
298 | } |
299 | |
300 | static void execute_file(string script) { |
301 | int haltOnError = 0; /* Stop on first error if true */ |
302 | int enableTrace = 0; /* Trace SQL statements if true */ |
303 | const char *zScriptFile = 0; /* Input script filename */ |
304 | const char *zDbEngine = "DuckDB" ; /* Name of database engine */ |
305 | const char *zConnection = 0; /* Connection string on DB engine */ |
306 | const DbEngine *pEngine = 0; /* Pointer to DbEngine object */ |
307 | int i; /* Loop counter */ |
308 | char *zScript; /* Content of the script */ |
309 | long nScript; /* Size of the script in bytes */ |
310 | long nGot; /* Number of bytes read */ |
311 | void *pConn = nullptr; /* Connection to the database engine */ |
312 | int rc; /* Result code from subroutine call */ |
313 | int nErr = 0; /* Number of errors */ |
314 | int nCmd = 0; /* Number of SQL statements processed */ |
315 | int nSkipped = 0; /* Number of SQL statements skipped */ |
316 | int nResult; /* Number of query results */ |
317 | char **azResult; /* Query result vector */ |
318 | Script sScript; /* Script parsing status */ |
319 | FILE *in; /* For reading script */ |
320 | char zHash[100]; /* Storage space for hash results */ |
321 | int hashThreshold = DEFAULT_HASH_THRESHOLD; /* Threshold for hashing res */ |
322 | int bHt = 0; /* True if -ht command-line option */ |
323 | const char *zParam = 0; /* Argument to -parameters */ |
324 | |
325 | const DbEngine duckdbEngine = { |
326 | "DuckDB" , /* zName */ |
327 | 0, /* pAuxData */ |
328 | duckdbConnect, /* xConnect */ |
329 | duckdbGetEngineName, /* xGetEngineName */ |
330 | duckdbStatement, /* xStatement */ |
331 | duckdbQuery, /* xQuery */ |
332 | duckdbFreeResults, /* xFreeResults */ |
333 | duckdbDisconnect /* xDisconnect */ |
334 | }; |
335 | pEngine = &duckdbEngine; |
336 | |
337 | REQUIRE(pEngine); |
338 | /* |
339 | ** Read the entire script file contents into memory |
340 | */ |
341 | |
342 | zScriptFile = script.c_str(); |
343 | in = fopen(zScriptFile, "rb" ); |
344 | if (!in) { |
345 | FAIL("Could not find test script '" + script + "'. Perhaps run `make sqlite`. " ); |
346 | } |
347 | REQUIRE(in); |
348 | fseek(in, 0L, SEEK_END); |
349 | nScript = ftell(in); |
350 | REQUIRE(nScript > 0); |
351 | zScript = (char *)malloc(nScript + 1); |
352 | if (!zScript) { |
353 | IFAIL(); |
354 | } |
355 | fseek(in, 0L, SEEK_SET); |
356 | nGot = fread(zScript, 1, nScript, in); |
357 | fclose(in); |
358 | REQUIRE(nGot <= nScript); |
359 | zScript[nGot] = 0; |
360 | |
361 | // zap hash table as result labels are only valid within one test file |
362 | memset(aHash, 0, sizeof(aHash)); |
363 | |
364 | /* Initialize the sScript structure so that the cursor will be pointing |
365 | ** to the start of the first line in the file after nextLine() is called |
366 | ** once. */ |
367 | memset(&sScript, 0, sizeof(sScript)); |
368 | sScript.zScript = zScript; |
369 | sScript.zLine = zScript; |
370 | sScript.iEnd = nScript; |
371 | sScript.copyFlag = 0; |
372 | |
373 | /* Open the database engine under test |
374 | */ |
375 | rc = pEngine->xConnect(pEngine->pAuxData, zConnection, &pConn, zParam); |
376 | REQUIRE(rc == 0); |
377 | |
378 | /* Loop over all records in the file */ |
379 | while ((nErr == 0 || !haltOnError) && findStartOfNextRecord(&sScript)) { |
380 | int bSkip = false; /* True if we should skip the current record. */ |
381 | |
382 | /* Tokenizer the first line of the record. This also records the |
383 | ** line number of the first record in sScript.startLine */ |
384 | tokenizeLine(&sScript); |
385 | |
386 | bSkip = false; |
387 | while (strcmp(sScript.azToken[0], "skipif" ) == 0 || strcmp(sScript.azToken[0], "onlyif" ) == 0) { |
388 | int bMatch; |
389 | /* The "skipif" and "onlyif" modifiers allow skipping or using |
390 | ** statement or query record for a particular database engine. |
391 | ** In this way, SQL features implmented by a majority of the |
392 | ** engines can be tested without causing spurious errors for |
393 | ** engines that don't support it. |
394 | ** |
395 | ** Once this record is encountered, an the current selected |
396 | ** db interface matches the db engine specified in the record, |
397 | ** the we skip this rest of this record for "skipif" or for |
398 | ** "onlyif" we skip the record if the record does not match. |
399 | */ |
400 | bMatch = stricmp(sScript.azToken[1], zDbEngine) == 0; |
401 | if (sScript.azToken[0][0] == 's') { |
402 | if (bMatch) |
403 | bSkip = true; |
404 | } else { |
405 | if (!bMatch) |
406 | bSkip = true; |
407 | } |
408 | nextLine(&sScript); |
409 | tokenizeLine(&sScript); |
410 | } |
411 | if (bSkip) { |
412 | int n; |
413 | nSkipped++; |
414 | if (strcmp(sScript.azToken[0], "query" ) != 0) |
415 | continue; |
416 | if (sScript.azToken[3][0] == 0) |
417 | continue; |
418 | |
419 | /* We are skipping this record. But we observe that it is a |
420 | *query |
421 | ** with a named hash value and we are in verify mode. Even |
422 | *though |
423 | ** we are going to skip the SQL evaluation, we might as well |
424 | *check |
425 | ** the hash of the result. |
426 | */ |
427 | while (!nextIsBlank(&sScript) && nextLine(&sScript) && strcmp(sScript.zLine, "----" ) != 0) { |
428 | /* Skip over the SQL text */ |
429 | } |
430 | if (strcmp(sScript.zLine, "----" ) == 0) |
431 | nextLine(&sScript); |
432 | if (sScript.zLine[0] == 0) |
433 | continue; |
434 | n = sscanf(sScript.zLine, "%*d values hashing to %32s" , zHash); |
435 | if (n != 1) { |
436 | md5_add(sScript.zLine); |
437 | md5_add("\n" ); |
438 | while (!nextIsBlank(&sScript) && nextLine(&sScript)) { |
439 | md5_add(sScript.zLine); |
440 | md5_add("\n" ); |
441 | } |
442 | strcpy(zHash, md5_finish()); |
443 | } |
444 | if (checkValue(sScript.azToken[3], zHash)) { |
445 | fprintf(stderr, |
446 | "%s:%d: labeled result [%s] does not agree with " |
447 | "previous values\n" , |
448 | zScriptFile, sScript.startLine, sScript.azToken[3]); |
449 | IFAIL(); |
450 | } |
451 | continue; |
452 | } |
453 | |
454 | /* Figure out the record type and do appropriate processing */ |
455 | if (strcmp(sScript.azToken[0], "statement" ) == 0) { |
456 | int k = 0; |
457 | int bExpectOk = 0; |
458 | int bExpectError = 0; |
459 | |
460 | /* Extract the SQL from second and subsequent lines of the |
461 | ** record. Copy the SQL into contiguous memory at the beginning |
462 | ** of zScript - we are guaranteed to have enough space there. */ |
463 | while (nextLine(&sScript) && sScript.zLine[0]) { |
464 | if (k > 0) |
465 | zScript[k++] = '\n'; |
466 | memmove(&zScript[k], sScript.zLine, sScript.len); |
467 | k += sScript.len; |
468 | } |
469 | zScript[k] = 0; |
470 | |
471 | bExpectOk = strcmp(sScript.azToken[1], "ok" ) == 0; |
472 | bExpectError = strcmp(sScript.azToken[1], "error" ) == 0; |
473 | |
474 | /* Run the statement. Remember the results |
475 | ** If we're expecting an error, pass true to suppress |
476 | ** printing of any errors. |
477 | */ |
478 | if (enableTrace) |
479 | printf("%s;\n" , zScript); |
480 | rc = pEngine->xStatement(pConn, zScript, bExpectError); |
481 | nCmd++; |
482 | |
483 | /* Check to see if we are expecting success or failure */ |
484 | if (bExpectOk) { |
485 | /* do nothing if we expect success */ |
486 | } else if (bExpectError) { |
487 | /* Invert the result if we expect failure */ |
488 | rc = !rc; |
489 | } else { |
490 | fprintf(stderr, "%s:%d: statement argument should be 'ok' or 'error'\n" , zScriptFile, |
491 | sScript.startLine); |
492 | IFAIL(); |
493 | } |
494 | |
495 | /* Report an error if the results do not match expectation */ |
496 | if (rc) { |
497 | fprintf(stderr, "%s:%d: statement error\n" , zScriptFile, sScript.startLine); |
498 | IFAIL(); |
499 | } |
500 | } else if (strcmp(sScript.azToken[0], "query" ) == 0) { |
501 | int k = 0; |
502 | int c; |
503 | |
504 | /* Verify that the type string consists of one or more |
505 | *characters |
506 | ** from the set "TIR". */ |
507 | for (k = 0; (c = sScript.azToken[1][k]) != 0; k++) { |
508 | if (c != 'T' && c != 'I' && c != 'R') { |
509 | fprintf(stderr, |
510 | "%s:%d: unknown type character '%c' in type " |
511 | "string\n" , |
512 | zScriptFile, sScript.startLine, c); |
513 | nErr++; |
514 | break; |
515 | } |
516 | } |
517 | if (c != 0) |
518 | continue; |
519 | if (k <= 0) { |
520 | fprintf(stderr, "%s:%d: missing type string\n" , zScriptFile, sScript.startLine); |
521 | IFAIL(); |
522 | } |
523 | |
524 | /* Extract the SQL from second and subsequent lines of the |
525 | *record |
526 | ** until the first "----" line or until end of record. |
527 | */ |
528 | k = 0; |
529 | while (!nextIsBlank(&sScript) && nextLine(&sScript) && sScript.zLine[0] && |
530 | strcmp(sScript.zLine, "----" ) != 0) { |
531 | if (k > 0) |
532 | zScript[k++] = '\n'; |
533 | memmove(&zScript[k], sScript.zLine, sScript.len); |
534 | k += sScript.len; |
535 | } |
536 | zScript[k] = 0; |
537 | |
538 | /* Run the query */ |
539 | nResult = 0; |
540 | azResult = 0; |
541 | if (enableTrace) |
542 | printf("%s;\n" , zScript); |
543 | rc = pEngine->xQuery(pConn, zScript, sScript.azToken[1], &azResult, &nResult); |
544 | nCmd++; |
545 | if (rc) { |
546 | fprintf(stderr, "%s:%d: query failed\n" , zScriptFile, sScript.startLine); |
547 | pEngine->xFreeResults(pConn, azResult, nResult); |
548 | IFAIL(); |
549 | } |
550 | |
551 | /* Do any required sorting of query results */ |
552 | if (sScript.azToken[2][0] == 0 || strcmp(sScript.azToken[2], "nosort" ) == 0) { |
553 | /* Do no sorting */ |
554 | } else if (strcmp(sScript.azToken[2], "rowsort" ) == 0) { |
555 | /* Row-oriented sorting */ |
556 | nColumn = (int)strlen(sScript.azToken[1]); |
557 | qsort(azResult, nResult / nColumn, sizeof(azResult[0]) * nColumn, rowCompare); |
558 | } else if (strcmp(sScript.azToken[2], "valuesort" ) == 0) { |
559 | /* Sort all values independently */ |
560 | nColumn = 1; |
561 | qsort(azResult, nResult, sizeof(azResult[0]), rowCompare); |
562 | } else { |
563 | fprintf(stderr, "%s:%d: unknown sort method: '%s'\n" , zScriptFile, sScript.startLine, |
564 | sScript.azToken[2]); |
565 | IFAIL(); |
566 | } |
567 | |
568 | /* Hash the results if we are over the hash threshold or if we |
569 | ** there is a hash label */ |
570 | if (sScript.azToken[3][0] || (hashThreshold > 0 && nResult > hashThreshold)) { |
571 | md5_add("" ); /* make sure md5 is reset, even if no results */ |
572 | for (i = 0; i < nResult; i++) { |
573 | md5_add(azResult[i]); |
574 | md5_add("\n" ); |
575 | } |
576 | snprintf(zHash, sizeof(zHash), "%d values hashing to %s" , nResult, md5_finish()); |
577 | sScript.azToken[3][20] = 0; |
578 | if (sScript.azToken[3][0] && checkValue(sScript.azToken[3], md5_finish())) { |
579 | fprintf(stderr, |
580 | "%s:%d: labeled result [%s] does not agree with " |
581 | "previous values\n" , |
582 | zScriptFile, sScript.startLine, sScript.azToken[3]); |
583 | IFAIL(); |
584 | } |
585 | } |
586 | |
587 | /* In verify mode, first skip over the ---- line if we are |
588 | *still |
589 | ** pointing at it. */ |
590 | if (strcmp(sScript.zLine, "----" ) == 0) |
591 | nextLine(&sScript); |
592 | |
593 | /* Compare subsequent lines of the script against the |
594 | *results |
595 | ** from the query. Report an error if any differences are |
596 | *found. |
597 | */ |
598 | if (hashThreshold == 0 || nResult <= hashThreshold) { |
599 | for (i = 0; i < nResult && sScript.zLine[0]; nextLine(&sScript), i++) { |
600 | if (strcmp(sScript.zLine, azResult[i]) != 0) { |
601 | fprintf(stderr, "%s:%d: wrong result\n" , zScriptFile, sScript.nLine); |
602 | |
603 | fprintf(stderr, "%s <> %s\n" , sScript.zLine, azResult[i]); |
604 | IFAIL(); |
605 | } |
606 | // we check this already but this inflates the test |
607 | // case count as desired |
608 | REQUIRE(strcmp(sScript.zLine, azResult[i]) == 0); |
609 | } |
610 | } else { |
611 | if (strcmp(sScript.zLine, zHash) != 0) { |
612 | fprintf(stderr, "%s:%d: wrong result hash\n" , zScriptFile, sScript.nLine); |
613 | IFAIL(); |
614 | } |
615 | } |
616 | |
617 | /* Free the query results */ |
618 | pEngine->xFreeResults(pConn, azResult, nResult); |
619 | } else if (strcmp(sScript.azToken[0], "hash-threshold" ) == 0) { |
620 | /* Set the maximum number of result values that will be accepted |
621 | ** for a query. If the number of result values exceeds this |
622 | *number, |
623 | ** then an MD5 hash is computed of all values, and the resulting |
624 | *hash |
625 | ** is the only result. |
626 | ** |
627 | ** If the threshold is 0, then hashing is never used. |
628 | ** |
629 | ** If a threshold was specified on the command line, ignore |
630 | ** any specifed in the script. |
631 | */ |
632 | if (!bHt) { |
633 | hashThreshold = atoi(sScript.azToken[1]); |
634 | } |
635 | } else if (strcmp(sScript.azToken[0], "halt" ) == 0) { |
636 | /* Used for debugging. Stop reading the test script and shut |
637 | *down. |
638 | ** A "halt" record can be inserted in the middle of a test |
639 | *script in |
640 | ** to run the script up to a particular point that is giving a |
641 | ** faulty result, then terminate at that point for analysis. |
642 | */ |
643 | fprintf(stderr, "%s:%d: halt\n" , zScriptFile, sScript.startLine); |
644 | break; |
645 | } else { |
646 | /* An unrecognized record type is an error */ |
647 | fprintf(stderr, "%s:%d: unknown record type: '%s'\n" , zScriptFile, sScript.startLine, sScript.azToken[0]); |
648 | IFAIL(); |
649 | } |
650 | } |
651 | |
652 | /* Shutdown the database connection. |
653 | */ |
654 | pEngine->xDisconnect(pConn); |
655 | free(zScript); |
656 | } |
657 | |
658 | // code below traverses the test directory and makes individual test cases out |
659 | // of each script |
660 | static void listFiles(const string &path, std::function<void(const string &)> cb) { |
661 | #ifndef SUN |
662 | if (auto dir = opendir(path.c_str())) { |
663 | while (auto f = readdir(dir)) { |
664 | if (f->d_name[0] == '.') |
665 | continue; |
666 | if (f->d_type == DT_DIR) |
667 | listFiles(path + f->d_name + "/" , cb); |
668 | |
669 | if (f->d_type == DT_REG) |
670 | cb(path + f->d_name); |
671 | } |
672 | closedir(dir); |
673 | } |
674 | #endif |
675 | } |
676 | |
677 | static bool endsWith(const string &mainStr, const string &toMatch) { |
678 | return (mainStr.size() >= toMatch.size() && |
679 | mainStr.compare(mainStr.size() - toMatch.size(), toMatch.size(), toMatch) == 0); |
680 | } |
681 | |
682 | static void testRunner() { |
683 | // this is an ugly hack that uses the test case name to pass the script file |
684 | // name if someone has a better idea... |
685 | auto name = Catch::getResultCapture().getCurrentTestName(); |
686 | fprintf(stderr, "%s\n" , name.c_str()); |
687 | execute_file(name); |
688 | } |
689 | |
690 | TEST_CASE("SQLite select1" , "[sqlitelogic]" ) { |
691 | execute_file("test/sqlite/select1.test" ); |
692 | } |
693 | |
694 | TEST_CASE("SQLite select2" , "[sqlitelogic]" ) { |
695 | execute_file("test/sqlite/select2.test" ); |
696 | } |
697 | |
698 | TEST_CASE("SQLite select3" , "[sqlitelogic]" ) { |
699 | execute_file("test/sqlite/select3.test" ); |
700 | } |
701 | |
702 | TEST_CASE("SQLite select4" , "[sqlitelogic][.]" ) { |
703 | execute_file("test/sqlite/select4.test" ); |
704 | } |
705 | |
706 | struct AutoRegTests { |
707 | AutoRegTests() { |
708 | vector<string> excludes = { |
709 | "test/select1.test" , // tested separately |
710 | "test/select2.test" , "test/select3.test" , "test/select4.test" , |
711 | "test/index" , // no index yet |
712 | "random/groupby/" , // having column binding issue with first |
713 | "random/select/slt_good_70.test" , // join on not between |
714 | "random/expr/slt_good_10.test" , // these all fail because the AVG |
715 | // decimal rewrite |
716 | "random/expr/slt_good_102.test" , "random/expr/slt_good_107.test" , "random/expr/slt_good_108.test" , |
717 | "random/expr/slt_good_109.test" , "random/expr/slt_good_111.test" , "random/expr/slt_good_112.test" , |
718 | "random/expr/slt_good_113.test" , "random/expr/slt_good_115.test" , "random/expr/slt_good_116.test" , |
719 | "random/expr/slt_good_117.test" , "random/expr/slt_good_13.test" , "random/expr/slt_good_15.test" , |
720 | "random/expr/slt_good_16.test" , "random/expr/slt_good_17.test" , "random/expr/slt_good_19.test" , |
721 | "random/expr/slt_good_21.test" , "random/expr/slt_good_22.test" , "random/expr/slt_good_24.test" , |
722 | "random/expr/slt_good_28.test" , "random/expr/slt_good_29.test" , "random/expr/slt_good_3.test" , |
723 | "random/expr/slt_good_30.test" , "random/expr/slt_good_34.test" , "random/expr/slt_good_38.test" , |
724 | "random/expr/slt_good_4.test" , "random/expr/slt_good_41.test" , "random/expr/slt_good_44.test" , |
725 | "random/expr/slt_good_45.test" , "random/expr/slt_good_49.test" , "random/expr/slt_good_52.test" , |
726 | "random/expr/slt_good_53.test" , "random/expr/slt_good_55.test" , "random/expr/slt_good_59.test" , |
727 | "random/expr/slt_good_6.test" , "random/expr/slt_good_60.test" , "random/expr/slt_good_63.test" , |
728 | "random/expr/slt_good_64.test" , "random/expr/slt_good_67.test" , "random/expr/slt_good_69.test" , |
729 | "random/expr/slt_good_7.test" , "random/expr/slt_good_71.test" , "random/expr/slt_good_72.test" , |
730 | "random/expr/slt_good_8.test" , "random/expr/slt_good_80.test" , "random/expr/slt_good_82.test" , |
731 | "random/expr/slt_good_85.test" , "random/expr/slt_good_9.test" , "random/expr/slt_good_90.test" , |
732 | "random/expr/slt_good_91.test" , "random/expr/slt_good_94.test" , "random/expr/slt_good_95.test" , |
733 | "random/expr/slt_good_96.test" , "random/expr/slt_good_99.test" , "random/aggregates/slt_good_2.test" , |
734 | "random/aggregates/slt_good_5.test" , "random/aggregates/slt_good_7.test" , |
735 | "random/aggregates/slt_good_9.test" , "random/aggregates/slt_good_17.test" , |
736 | "random/aggregates/slt_good_28.test" , "random/aggregates/slt_good_45.test" , |
737 | "random/aggregates/slt_good_50.test" , "random/aggregates/slt_good_52.test" , |
738 | "random/aggregates/slt_good_58.test" , "random/aggregates/slt_good_65.test" , |
739 | "random/aggregates/slt_good_66.test" , "random/aggregates/slt_good_76.test" , |
740 | "random/aggregates/slt_good_81.test" , "random/aggregates/slt_good_90.test" , |
741 | "random/aggregates/slt_good_96.test" , "random/aggregates/slt_good_102.test" , |
742 | "random/aggregates/slt_good_106.test" , "random/aggregates/slt_good_112.test" , |
743 | "random/aggregates/slt_good_118.test" , |
744 | "third_party/sqllogictest/test/evidence/in1.test" , // UNIQUE index on text |
745 | "evidence/slt_lang_replace.test" , // feature not supported |
746 | "evidence/slt_lang_reindex.test" , // " |
747 | "evidence/slt_lang_dropindex.test" , // " |
748 | "evidence/slt_lang_createtrigger.test" , // " |
749 | "evidence/slt_lang_droptrigger.test" // " |
750 | }; |
751 | listFiles("third_party/sqllogictest/test/" , [excludes](const string &path) { |
752 | if (endsWith(path, ".test" )) { |
753 | for (auto excl : excludes) { |
754 | if (path.find(excl) != string::npos) { |
755 | return; |
756 | } |
757 | } |
758 | REGISTER_TEST_CASE(testRunner, path, "[sqlitelogic][.]" ); |
759 | } |
760 | }); |
761 | } |
762 | }; |
763 | AutoRegTests autoreg; |
764 | |