Fossil SCM
Improvements to error detection and reporting in the artifact parser. Add the test-parse-all-blobs command for verifying the artifact parser against historical repositories.
Commit
d2d1a86fa29f12a69735c90478d3638d4d50383d7134069dd5c7fe21c717cacb
Parent
bb50f0dce86d179…
1 file changed
+58
-8
+58
-8
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -421,11 +421,13 @@ | ||
| 421 | 421 | char cType; |
| 422 | 422 | char *z; |
| 423 | 423 | int n; |
| 424 | 424 | char *zUuid; |
| 425 | 425 | int sz = 0; |
| 426 | - int isRepeat, hasSelfRefTag = 0; | |
| 426 | + int isRepeat; | |
| 427 | + int nSelfTag = 0; /* Number of T cards referring to this manifest */ | |
| 428 | + int nSimpleTag = 0; /* Number of T cards with "+" prefix */ | |
| 427 | 429 | static Bag seen; |
| 428 | 430 | const char *zErr = 0; |
| 429 | 431 | unsigned int m; |
| 430 | 432 | unsigned int seenCard = 0; /* Which card types have been seen */ |
| 431 | 433 | char zErrBuf[100]; /* Write error messages here */ |
| @@ -882,24 +884,21 @@ | ||
| 882 | 884 | if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); |
| 883 | 885 | zValue = next_token(&x, 0); |
| 884 | 886 | if( zValue ) defossilize(zValue); |
| 885 | 887 | if( hname_validate(zUuid, sz) ){ |
| 886 | 888 | /* A valid artifact hash */ |
| 887 | - if( p->zEventId ) SYNTAX("non-self-referential T-card in technote"); | |
| 888 | 889 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 889 | 890 | zUuid = 0; |
| 890 | - hasSelfRefTag = 1; | |
| 891 | - if( p->zEventId && zName[0]!='+' ){ | |
| 892 | - SYNTAX("propagating T-card in event"); | |
| 893 | - } | |
| 891 | + nSelfTag++; | |
| 894 | 892 | }else{ |
| 895 | 893 | SYNTAX("malformed artifact hash on T-card"); |
| 896 | 894 | } |
| 897 | 895 | defossilize(zName); |
| 898 | 896 | if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){ |
| 899 | 897 | SYNTAX("T-card name does not begin with '-', '+', or '*'"); |
| 900 | 898 | } |
| 899 | + if( zName[0]=='+' ) nSimpleTag++; | |
| 901 | 900 | if( validate16(&zName[1], strlen(&zName[1])) ){ |
| 902 | 901 | /* Do not allow tags whose names look like a hash */ |
| 903 | 902 | SYNTAX("T-card name looks like a hexadecimal hash"); |
| 904 | 903 | } |
| 905 | 904 | if( p->nTag>=p->nTagAlloc ){ |
| @@ -1018,10 +1017,23 @@ | ||
| 1018 | 1017 | goto manifest_syntax_error; |
| 1019 | 1018 | } |
| 1020 | 1019 | |
| 1021 | 1020 | /* Additional checks based on artifact type */ |
| 1022 | 1021 | switch( p->type ){ |
| 1022 | + case CFTYPE_CONTROL: { | |
| 1023 | + if( nSelfTag ) SYNTAX("self-referential T-card in control artifact"); | |
| 1024 | + break; | |
| 1025 | + } | |
| 1026 | + case CFTYPE_EVENT: { | |
| 1027 | + if( p->nTag!=nSelfTag ){ | |
| 1028 | + SYNTAX("non-self-referential T-card in technote"); | |
| 1029 | + } | |
| 1030 | + if( p->nTag!=nSimpleTag ){ | |
| 1031 | + SYNTAX("T-card with '*' or '-' in technote"); | |
| 1032 | + } | |
| 1033 | + break; | |
| 1034 | + } | |
| 1023 | 1035 | case CFTYPE_FORUM: { |
| 1024 | 1036 | if( p->zThreadTitle && p->zInReplyTo ){ |
| 1025 | 1037 | SYNTAX("cannot have I-card and H-card in a forum post"); |
| 1026 | 1038 | } |
| 1027 | 1039 | if( p->nParent>1 ) SYNTAX("too many arguments to P-card"); |
| @@ -1099,11 +1111,12 @@ | ||
| 1099 | 1111 | /* |
| 1100 | 1112 | ** COMMAND: test-parse-manifest |
| 1101 | 1113 | ** |
| 1102 | 1114 | ** Usage: %fossil test-parse-manifest FILENAME ?N? |
| 1103 | 1115 | ** |
| 1104 | -** Parse the manifest and discarded. Use for testing only. | |
| 1116 | +** Parse the manifest(s) given on the command-line and report any | |
| 1117 | +** errors. If the N argument is given, run the parsing N times. | |
| 1105 | 1118 | */ |
| 1106 | 1119 | void manifest_test_parse_cmd(void){ |
| 1107 | 1120 | Manifest *p; |
| 1108 | 1121 | Blob b; |
| 1109 | 1122 | int i; |
| @@ -1123,10 +1136,47 @@ | ||
| 1123 | 1136 | if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err)); |
| 1124 | 1137 | blob_reset(&err); |
| 1125 | 1138 | manifest_destroy(p); |
| 1126 | 1139 | } |
| 1127 | 1140 | } |
| 1141 | + | |
| 1142 | +/* | |
| 1143 | +** COMMAND: test-parse-all-blobs | |
| 1144 | +** | |
| 1145 | +** Usage: %fossil test-parse-all-blobs | |
| 1146 | +** | |
| 1147 | +** Parse all entries in the BLOB table that are believed to be non-data | |
| 1148 | +** artifacts and report any errors. Run this test command on historical | |
| 1149 | +** repositories after making any changes to the manifest_parse() | |
| 1150 | +** implementation to confirm that the changes did not break anything. | |
| 1151 | +*/ | |
| 1152 | +void manifest_test_parse_all_blobs_cmd(void){ | |
| 1153 | + Manifest *p; | |
| 1154 | + Blob err; | |
| 1155 | + Stmt q; | |
| 1156 | + int nTest = 0; | |
| 1157 | + int nErr = 0; | |
| 1158 | + db_find_and_open_repository(0, 0); | |
| 1159 | + verify_all_options(); | |
| 1160 | + db_prepare(&q, "SELECT DISTINCT objid FROM EVENT"); | |
| 1161 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 1162 | + int id = db_column_int(&q,0); | |
| 1163 | + fossil_print("Checking %d \r", id); | |
| 1164 | + nTest++; | |
| 1165 | + fflush(stdout); | |
| 1166 | + blob_init(&err, 0, 0); | |
| 1167 | + p = manifest_get(id, CFTYPE_ANY, &err); | |
| 1168 | + if( p==0 ){ | |
| 1169 | + fossil_print("%d ERROR: %s\n", id, blob_str(&err)); | |
| 1170 | + nErr++; | |
| 1171 | + } | |
| 1172 | + blob_reset(&err); | |
| 1173 | + manifest_destroy(p); | |
| 1174 | + } | |
| 1175 | + db_finalize(&q); | |
| 1176 | + fossil_print("%d tests with %d errors\n", nTest, nErr); | |
| 1177 | +} | |
| 1128 | 1178 | |
| 1129 | 1179 | /* |
| 1130 | 1180 | ** Fetch the baseline associated with the delta-manifest p. |
| 1131 | 1181 | ** Return 0 on success. If unable to parse the baseline, |
| 1132 | 1182 | ** throw an error. If the baseline is a manifest, throw an |
| @@ -2466,11 +2516,11 @@ | ||
| 2466 | 2516 | blob_reset(&comment); |
| 2467 | 2517 | } |
| 2468 | 2518 | if( p->type==CFTYPE_FORUM ){ |
| 2469 | 2519 | int froot, fprev, firt; |
| 2470 | 2520 | char *zFType; |
| 2471 | - const char *zTitle; | |
| 2521 | + char *zTitle; | |
| 2472 | 2522 | schema_forum(); |
| 2473 | 2523 | froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid; |
| 2474 | 2524 | fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0; |
| 2475 | 2525 | firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0; |
| 2476 | 2526 | db_multi_exec( |
| 2477 | 2527 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -421,11 +421,13 @@ | |
| 421 | char cType; |
| 422 | char *z; |
| 423 | int n; |
| 424 | char *zUuid; |
| 425 | int sz = 0; |
| 426 | int isRepeat, hasSelfRefTag = 0; |
| 427 | static Bag seen; |
| 428 | const char *zErr = 0; |
| 429 | unsigned int m; |
| 430 | unsigned int seenCard = 0; /* Which card types have been seen */ |
| 431 | char zErrBuf[100]; /* Write error messages here */ |
| @@ -882,24 +884,21 @@ | |
| 882 | if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); |
| 883 | zValue = next_token(&x, 0); |
| 884 | if( zValue ) defossilize(zValue); |
| 885 | if( hname_validate(zUuid, sz) ){ |
| 886 | /* A valid artifact hash */ |
| 887 | if( p->zEventId ) SYNTAX("non-self-referential T-card in technote"); |
| 888 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 889 | zUuid = 0; |
| 890 | hasSelfRefTag = 1; |
| 891 | if( p->zEventId && zName[0]!='+' ){ |
| 892 | SYNTAX("propagating T-card in event"); |
| 893 | } |
| 894 | }else{ |
| 895 | SYNTAX("malformed artifact hash on T-card"); |
| 896 | } |
| 897 | defossilize(zName); |
| 898 | if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){ |
| 899 | SYNTAX("T-card name does not begin with '-', '+', or '*'"); |
| 900 | } |
| 901 | if( validate16(&zName[1], strlen(&zName[1])) ){ |
| 902 | /* Do not allow tags whose names look like a hash */ |
| 903 | SYNTAX("T-card name looks like a hexadecimal hash"); |
| 904 | } |
| 905 | if( p->nTag>=p->nTagAlloc ){ |
| @@ -1018,10 +1017,23 @@ | |
| 1018 | goto manifest_syntax_error; |
| 1019 | } |
| 1020 | |
| 1021 | /* Additional checks based on artifact type */ |
| 1022 | switch( p->type ){ |
| 1023 | case CFTYPE_FORUM: { |
| 1024 | if( p->zThreadTitle && p->zInReplyTo ){ |
| 1025 | SYNTAX("cannot have I-card and H-card in a forum post"); |
| 1026 | } |
| 1027 | if( p->nParent>1 ) SYNTAX("too many arguments to P-card"); |
| @@ -1099,11 +1111,12 @@ | |
| 1099 | /* |
| 1100 | ** COMMAND: test-parse-manifest |
| 1101 | ** |
| 1102 | ** Usage: %fossil test-parse-manifest FILENAME ?N? |
| 1103 | ** |
| 1104 | ** Parse the manifest and discarded. Use for testing only. |
| 1105 | */ |
| 1106 | void manifest_test_parse_cmd(void){ |
| 1107 | Manifest *p; |
| 1108 | Blob b; |
| 1109 | int i; |
| @@ -1123,10 +1136,47 @@ | |
| 1123 | if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err)); |
| 1124 | blob_reset(&err); |
| 1125 | manifest_destroy(p); |
| 1126 | } |
| 1127 | } |
| 1128 | |
| 1129 | /* |
| 1130 | ** Fetch the baseline associated with the delta-manifest p. |
| 1131 | ** Return 0 on success. If unable to parse the baseline, |
| 1132 | ** throw an error. If the baseline is a manifest, throw an |
| @@ -2466,11 +2516,11 @@ | |
| 2466 | blob_reset(&comment); |
| 2467 | } |
| 2468 | if( p->type==CFTYPE_FORUM ){ |
| 2469 | int froot, fprev, firt; |
| 2470 | char *zFType; |
| 2471 | const char *zTitle; |
| 2472 | schema_forum(); |
| 2473 | froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid; |
| 2474 | fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0; |
| 2475 | firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0; |
| 2476 | db_multi_exec( |
| 2477 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -421,11 +421,13 @@ | |
| 421 | char cType; |
| 422 | char *z; |
| 423 | int n; |
| 424 | char *zUuid; |
| 425 | int sz = 0; |
| 426 | int isRepeat; |
| 427 | int nSelfTag = 0; /* Number of T cards referring to this manifest */ |
| 428 | int nSimpleTag = 0; /* Number of T cards with "+" prefix */ |
| 429 | static Bag seen; |
| 430 | const char *zErr = 0; |
| 431 | unsigned int m; |
| 432 | unsigned int seenCard = 0; /* Which card types have been seen */ |
| 433 | char zErrBuf[100]; /* Write error messages here */ |
| @@ -882,24 +884,21 @@ | |
| 884 | if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); |
| 885 | zValue = next_token(&x, 0); |
| 886 | if( zValue ) defossilize(zValue); |
| 887 | if( hname_validate(zUuid, sz) ){ |
| 888 | /* A valid artifact hash */ |
| 889 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 890 | zUuid = 0; |
| 891 | nSelfTag++; |
| 892 | }else{ |
| 893 | SYNTAX("malformed artifact hash on T-card"); |
| 894 | } |
| 895 | defossilize(zName); |
| 896 | if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){ |
| 897 | SYNTAX("T-card name does not begin with '-', '+', or '*'"); |
| 898 | } |
| 899 | if( zName[0]=='+' ) nSimpleTag++; |
| 900 | if( validate16(&zName[1], strlen(&zName[1])) ){ |
| 901 | /* Do not allow tags whose names look like a hash */ |
| 902 | SYNTAX("T-card name looks like a hexadecimal hash"); |
| 903 | } |
| 904 | if( p->nTag>=p->nTagAlloc ){ |
| @@ -1018,10 +1017,23 @@ | |
| 1017 | goto manifest_syntax_error; |
| 1018 | } |
| 1019 | |
| 1020 | /* Additional checks based on artifact type */ |
| 1021 | switch( p->type ){ |
| 1022 | case CFTYPE_CONTROL: { |
| 1023 | if( nSelfTag ) SYNTAX("self-referential T-card in control artifact"); |
| 1024 | break; |
| 1025 | } |
| 1026 | case CFTYPE_EVENT: { |
| 1027 | if( p->nTag!=nSelfTag ){ |
| 1028 | SYNTAX("non-self-referential T-card in technote"); |
| 1029 | } |
| 1030 | if( p->nTag!=nSimpleTag ){ |
| 1031 | SYNTAX("T-card with '*' or '-' in technote"); |
| 1032 | } |
| 1033 | break; |
| 1034 | } |
| 1035 | case CFTYPE_FORUM: { |
| 1036 | if( p->zThreadTitle && p->zInReplyTo ){ |
| 1037 | SYNTAX("cannot have I-card and H-card in a forum post"); |
| 1038 | } |
| 1039 | if( p->nParent>1 ) SYNTAX("too many arguments to P-card"); |
| @@ -1099,11 +1111,12 @@ | |
| 1111 | /* |
| 1112 | ** COMMAND: test-parse-manifest |
| 1113 | ** |
| 1114 | ** Usage: %fossil test-parse-manifest FILENAME ?N? |
| 1115 | ** |
| 1116 | ** Parse the manifest(s) given on the command-line and report any |
| 1117 | ** errors. If the N argument is given, run the parsing N times. |
| 1118 | */ |
| 1119 | void manifest_test_parse_cmd(void){ |
| 1120 | Manifest *p; |
| 1121 | Blob b; |
| 1122 | int i; |
| @@ -1123,10 +1136,47 @@ | |
| 1136 | if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err)); |
| 1137 | blob_reset(&err); |
| 1138 | manifest_destroy(p); |
| 1139 | } |
| 1140 | } |
| 1141 | |
| 1142 | /* |
| 1143 | ** COMMAND: test-parse-all-blobs |
| 1144 | ** |
| 1145 | ** Usage: %fossil test-parse-all-blobs |
| 1146 | ** |
| 1147 | ** Parse all entries in the BLOB table that are believed to be non-data |
| 1148 | ** artifacts and report any errors. Run this test command on historical |
| 1149 | ** repositories after making any changes to the manifest_parse() |
| 1150 | ** implementation to confirm that the changes did not break anything. |
| 1151 | */ |
| 1152 | void manifest_test_parse_all_blobs_cmd(void){ |
| 1153 | Manifest *p; |
| 1154 | Blob err; |
| 1155 | Stmt q; |
| 1156 | int nTest = 0; |
| 1157 | int nErr = 0; |
| 1158 | db_find_and_open_repository(0, 0); |
| 1159 | verify_all_options(); |
| 1160 | db_prepare(&q, "SELECT DISTINCT objid FROM EVENT"); |
| 1161 | while( db_step(&q)==SQLITE_ROW ){ |
| 1162 | int id = db_column_int(&q,0); |
| 1163 | fossil_print("Checking %d \r", id); |
| 1164 | nTest++; |
| 1165 | fflush(stdout); |
| 1166 | blob_init(&err, 0, 0); |
| 1167 | p = manifest_get(id, CFTYPE_ANY, &err); |
| 1168 | if( p==0 ){ |
| 1169 | fossil_print("%d ERROR: %s\n", id, blob_str(&err)); |
| 1170 | nErr++; |
| 1171 | } |
| 1172 | blob_reset(&err); |
| 1173 | manifest_destroy(p); |
| 1174 | } |
| 1175 | db_finalize(&q); |
| 1176 | fossil_print("%d tests with %d errors\n", nTest, nErr); |
| 1177 | } |
| 1178 | |
| 1179 | /* |
| 1180 | ** Fetch the baseline associated with the delta-manifest p. |
| 1181 | ** Return 0 on success. If unable to parse the baseline, |
| 1182 | ** throw an error. If the baseline is a manifest, throw an |
| @@ -2466,11 +2516,11 @@ | |
| 2516 | blob_reset(&comment); |
| 2517 | } |
| 2518 | if( p->type==CFTYPE_FORUM ){ |
| 2519 | int froot, fprev, firt; |
| 2520 | char *zFType; |
| 2521 | char *zTitle; |
| 2522 | schema_forum(); |
| 2523 | froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid; |
| 2524 | fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0; |
| 2525 | firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0; |
| 2526 | db_multi_exec( |
| 2527 |