Fossil SCM

Robustify a few things in the JSON subsystem integration and get JSON tests passing.

mistachkin 2020-06-14 15:28 trunk merge
Commit a588e55fe7feed2a8012edf362b9cb8b9dd475671b7c554d0fa0acb9a338eef2
+3
--- src/cgi.c
+++ src/cgi.c
@@ -1745,10 +1745,13 @@
17451745
char *z, *zToken;
17461746
const char *zType = 0;
17471747
int i, content_length = 0;
17481748
char zLine[2000]; /* A single line of input. */
17491749
1750
+#ifdef FOSSIL_ENABLE_JSON
1751
+ if( nCycles==0 ){ json_main_bootstrap(); }
1752
+#endif
17501753
if( zIpAddr ){
17511754
if( nCycles==0 ){
17521755
cgi_setenv("REMOTE_ADDR", zIpAddr);
17531756
g.zIpAddr = mprintf("%s", zIpAddr);
17541757
}
17551758
--- src/cgi.c
+++ src/cgi.c
@@ -1745,10 +1745,13 @@
1745 char *z, *zToken;
1746 const char *zType = 0;
1747 int i, content_length = 0;
1748 char zLine[2000]; /* A single line of input. */
1749
 
 
 
1750 if( zIpAddr ){
1751 if( nCycles==0 ){
1752 cgi_setenv("REMOTE_ADDR", zIpAddr);
1753 g.zIpAddr = mprintf("%s", zIpAddr);
1754 }
1755
--- src/cgi.c
+++ src/cgi.c
@@ -1745,10 +1745,13 @@
1745 char *z, *zToken;
1746 const char *zType = 0;
1747 int i, content_length = 0;
1748 char zLine[2000]; /* A single line of input. */
1749
1750 #ifdef FOSSIL_ENABLE_JSON
1751 if( nCycles==0 ){ json_main_bootstrap(); }
1752 #endif
1753 if( zIpAddr ){
1754 if( nCycles==0 ){
1755 cgi_setenv("REMOTE_ADDR", zIpAddr);
1756 g.zIpAddr = mprintf("%s", zIpAddr);
1757 }
1758
+6
--- src/db.c
+++ src/db.c
@@ -78,10 +78,16 @@
7878
va_start(ap, zFormat);
7979
z = vmprintf(zFormat, ap);
8080
va_end(ap);
8181
#ifdef FOSSIL_ENABLE_JSON
8282
if( g.json.isJsonMode ){
83
+ /*
84
+ ** Avoid calling into the JSON support subsystem if it
85
+ ** has not yet been initialized, e.g. early SQLite log
86
+ ** messages, etc.
87
+ */
88
+ if( !json_is_main_boostrapped() ) json_main_bootstrap();
8389
json_err( 0, z, 1 );
8490
}
8591
else
8692
#endif /* FOSSIL_ENABLE_JSON */
8793
if( g.xferPanic && g.cgiOutput==1 ){
8894
--- src/db.c
+++ src/db.c
@@ -78,10 +78,16 @@
78 va_start(ap, zFormat);
79 z = vmprintf(zFormat, ap);
80 va_end(ap);
81 #ifdef FOSSIL_ENABLE_JSON
82 if( g.json.isJsonMode ){
 
 
 
 
 
 
83 json_err( 0, z, 1 );
84 }
85 else
86 #endif /* FOSSIL_ENABLE_JSON */
87 if( g.xferPanic && g.cgiOutput==1 ){
88
--- src/db.c
+++ src/db.c
@@ -78,10 +78,16 @@
78 va_start(ap, zFormat);
79 z = vmprintf(zFormat, ap);
80 va_end(ap);
81 #ifdef FOSSIL_ENABLE_JSON
82 if( g.json.isJsonMode ){
83 /*
84 ** Avoid calling into the JSON support subsystem if it
85 ** has not yet been initialized, e.g. early SQLite log
86 ** messages, etc.
87 */
88 if( !json_is_main_boostrapped() ) json_main_bootstrap();
89 json_err( 0, z, 1 );
90 }
91 else
92 #endif /* FOSSIL_ENABLE_JSON */
93 if( g.xferPanic && g.cgiOutput==1 ){
94
+13 -2
--- src/json.c
+++ src/json.c
@@ -707,10 +707,21 @@
707707
cson_value * json_req_payload_get(char const *pKey){
708708
return g.json.reqPayload.o
709709
? cson_object_get(g.json.reqPayload.o,pKey)
710710
: NULL;
711711
}
712
+
713
+/*
714
+** Returns non-zero if the json_main_bootstrap() function has already
715
+** been called. In general, this function should be used sparingly,
716
+** e.g. from low-level support functions like fossil_warning() where
717
+** there is genuine uncertainty about whether (or not) the JSON setup
718
+** has already been called.
719
+*/
720
+int json_is_main_boostrapped(){
721
+ return ((g.json.gc.v != NULL) && (g.json.gc.a != NULL));
722
+}
712723
713724
/*
714725
** Initializes some JSON bits which need to be initialized relatively
715726
** early on. It should only be called from cgi_init() or
716727
** json_cmd_top() (early on in those functions).
@@ -931,11 +942,11 @@
931942
** This must be called by the top-level JSON command dispatching code
932943
** before they do any work.
933944
**
934945
** This must only be called once, or an assertion may be triggered.
935946
*/
936
-static void json_mode_bootstrap(){
947
+void json_mode_bootstrap(){
937948
static char once = 0 /* guard against multiple runs */;
938949
char const * zPath = P("PATH_INFO");
939950
assert(g.json.gc.a && "json_main_bootstrap() was not called!");
940951
assert( (0==once) && "json_mode_bootstrap() called too many times!");
941952
if( once ){
@@ -2262,11 +2273,11 @@
22622273
** json_cmd_top().
22632274
*/
22642275
void json_page_top(void){
22652276
char const * zCommand;
22662277
assert(g.json.gc.a && "json_main_bootstrap() was not called!");
2267
- json_mode_bootstrap();
2278
+ assert(g.json.cmd.a && "json_mode_bootstrap() was not called!");
22682279
zCommand = json_command_arg(1);
22692280
if(!zCommand || !*zCommand){
22702281
json_dispatch_missing_args_err( JsonPageDefs,
22712282
"No command (sub-path) specified."
22722283
" Try one of: ");
22732284
--- src/json.c
+++ src/json.c
@@ -707,10 +707,21 @@
707 cson_value * json_req_payload_get(char const *pKey){
708 return g.json.reqPayload.o
709 ? cson_object_get(g.json.reqPayload.o,pKey)
710 : NULL;
711 }
 
 
 
 
 
 
 
 
 
 
 
712
713 /*
714 ** Initializes some JSON bits which need to be initialized relatively
715 ** early on. It should only be called from cgi_init() or
716 ** json_cmd_top() (early on in those functions).
@@ -931,11 +942,11 @@
931 ** This must be called by the top-level JSON command dispatching code
932 ** before they do any work.
933 **
934 ** This must only be called once, or an assertion may be triggered.
935 */
936 static void json_mode_bootstrap(){
937 static char once = 0 /* guard against multiple runs */;
938 char const * zPath = P("PATH_INFO");
939 assert(g.json.gc.a && "json_main_bootstrap() was not called!");
940 assert( (0==once) && "json_mode_bootstrap() called too many times!");
941 if( once ){
@@ -2262,11 +2273,11 @@
2262 ** json_cmd_top().
2263 */
2264 void json_page_top(void){
2265 char const * zCommand;
2266 assert(g.json.gc.a && "json_main_bootstrap() was not called!");
2267 json_mode_bootstrap();
2268 zCommand = json_command_arg(1);
2269 if(!zCommand || !*zCommand){
2270 json_dispatch_missing_args_err( JsonPageDefs,
2271 "No command (sub-path) specified."
2272 " Try one of: ");
2273
--- src/json.c
+++ src/json.c
@@ -707,10 +707,21 @@
707 cson_value * json_req_payload_get(char const *pKey){
708 return g.json.reqPayload.o
709 ? cson_object_get(g.json.reqPayload.o,pKey)
710 : NULL;
711 }
712
713 /*
714 ** Returns non-zero if the json_main_bootstrap() function has already
715 ** been called. In general, this function should be used sparingly,
716 ** e.g. from low-level support functions like fossil_warning() where
717 ** there is genuine uncertainty about whether (or not) the JSON setup
718 ** has already been called.
719 */
720 int json_is_main_boostrapped(){
721 return ((g.json.gc.v != NULL) && (g.json.gc.a != NULL));
722 }
723
724 /*
725 ** Initializes some JSON bits which need to be initialized relatively
726 ** early on. It should only be called from cgi_init() or
727 ** json_cmd_top() (early on in those functions).
@@ -931,11 +942,11 @@
942 ** This must be called by the top-level JSON command dispatching code
943 ** before they do any work.
944 **
945 ** This must only be called once, or an assertion may be triggered.
946 */
947 void json_mode_bootstrap(){
948 static char once = 0 /* guard against multiple runs */;
949 char const * zPath = P("PATH_INFO");
950 assert(g.json.gc.a && "json_main_bootstrap() was not called!");
951 assert( (0==once) && "json_mode_bootstrap() called too many times!");
952 if( once ){
@@ -2262,11 +2273,11 @@
2273 ** json_cmd_top().
2274 */
2275 void json_page_top(void){
2276 char const * zCommand;
2277 assert(g.json.gc.a && "json_main_bootstrap() was not called!");
2278 assert(g.json.cmd.a && "json_mode_bootstrap() was not called!");
2279 zCommand = json_command_arg(1);
2280 if(!zCommand || !*zCommand){
2281 json_dispatch_missing_args_err( JsonPageDefs,
2282 "No command (sub-path) specified."
2283 " Try one of: ");
2284
+13
--- src/main.c
+++ src/main.c
@@ -274,10 +274,13 @@
274274
always output JSON-form error
275275
responses and always (in CGI mode)
276276
exit() with code 0 to avoid an HTTP
277277
500 error.
278278
*/
279
+ int preserveRc; /* Do not convert error codes into 0.
280
+ * This is primarily intended for use
281
+ * by the test suite. */
279282
int resultCode; /* used for passing back specific codes
280283
** from /json callbacks. */
281284
int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
282285
cson_output_opt outOpt; /* formatting options for JSON mode. */
283286
cson_value *authToken; /* authentication token */
@@ -746,10 +749,13 @@
746749
g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
747750
g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
748751
g.fSshClient = 0;
749752
g.zSshCmd = 0;
750753
if( g.fSqlTrace ) g.fSqlStats = 1;
754
+#ifdef FOSSIL_ENABLE_JSON
755
+ g.json.preserveRc = find_option("json-preserve-rc", 0, 0)!=0;
756
+#endif
751757
g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
752758
#ifdef FOSSIL_ENABLE_TH1_HOOKS
753759
g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
754760
#endif
755761
g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
@@ -1909,10 +1915,17 @@
19091915
@ <h1>Server Configuration Error</h1>
19101916
@ <p>The database schema on the server is out-of-date. Please ask
19111917
@ the administrator to run <b>fossil rebuild</b>.</p>
19121918
}
19131919
}else{
1920
+#ifdef FOSSIL_ENABLE_JSON
1921
+ static int jsonOnce = 0;
1922
+ if( !jsonOnce && g.json.isJsonMode ){
1923
+ json_mode_bootstrap();
1924
+ jsonOnce = 1;
1925
+ }
1926
+#endif
19141927
if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
19151928
cgi_decode_post_parameters();
19161929
}
19171930
if( g.fCgiTrace ){
19181931
fossil_trace("######## Calling %s #########\n", pCmd->zName);
19191932
--- src/main.c
+++ src/main.c
@@ -274,10 +274,13 @@
274 always output JSON-form error
275 responses and always (in CGI mode)
276 exit() with code 0 to avoid an HTTP
277 500 error.
278 */
 
 
 
279 int resultCode; /* used for passing back specific codes
280 ** from /json callbacks. */
281 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
282 cson_output_opt outOpt; /* formatting options for JSON mode. */
283 cson_value *authToken; /* authentication token */
@@ -746,10 +749,13 @@
746 g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
747 g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
748 g.fSshClient = 0;
749 g.zSshCmd = 0;
750 if( g.fSqlTrace ) g.fSqlStats = 1;
 
 
 
751 g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
752 #ifdef FOSSIL_ENABLE_TH1_HOOKS
753 g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
754 #endif
755 g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
@@ -1909,10 +1915,17 @@
1909 @ <h1>Server Configuration Error</h1>
1910 @ <p>The database schema on the server is out-of-date. Please ask
1911 @ the administrator to run <b>fossil rebuild</b>.</p>
1912 }
1913 }else{
 
 
 
 
 
 
 
1914 if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
1915 cgi_decode_post_parameters();
1916 }
1917 if( g.fCgiTrace ){
1918 fossil_trace("######## Calling %s #########\n", pCmd->zName);
1919
--- src/main.c
+++ src/main.c
@@ -274,10 +274,13 @@
274 always output JSON-form error
275 responses and always (in CGI mode)
276 exit() with code 0 to avoid an HTTP
277 500 error.
278 */
279 int preserveRc; /* Do not convert error codes into 0.
280 * This is primarily intended for use
281 * by the test suite. */
282 int resultCode; /* used for passing back specific codes
283 ** from /json callbacks. */
284 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
285 cson_output_opt outOpt; /* formatting options for JSON mode. */
286 cson_value *authToken; /* authentication token */
@@ -746,10 +749,13 @@
749 g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
750 g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
751 g.fSshClient = 0;
752 g.zSshCmd = 0;
753 if( g.fSqlTrace ) g.fSqlStats = 1;
754 #ifdef FOSSIL_ENABLE_JSON
755 g.json.preserveRc = find_option("json-preserve-rc", 0, 0)!=0;
756 #endif
757 g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
758 #ifdef FOSSIL_ENABLE_TH1_HOOKS
759 g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
760 #endif
761 g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
@@ -1909,10 +1915,17 @@
1915 @ <h1>Server Configuration Error</h1>
1916 @ <p>The database schema on the server is out-of-date. Please ask
1917 @ the administrator to run <b>fossil rebuild</b>.</p>
1918 }
1919 }else{
1920 #ifdef FOSSIL_ENABLE_JSON
1921 static int jsonOnce = 0;
1922 if( !jsonOnce && g.json.isJsonMode ){
1923 json_mode_bootstrap();
1924 jsonOnce = 1;
1925 }
1926 #endif
1927 if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
1928 cgi_decode_post_parameters();
1929 }
1930 if( g.fCgiTrace ){
1931 fossil_trace("######## Calling %s #########\n", pCmd->zName);
1932
+17 -1
--- src/printf.c
+++ src/printf.c
@@ -1076,13 +1076,23 @@
10761076
** Write error message output
10771077
*/
10781078
static int fossil_print_error(int rc, const char *z){
10791079
#ifdef FOSSIL_ENABLE_JSON
10801080
if( g.json.isJsonMode ){
1081
+ /*
1082
+ ** Avoid calling into the JSON support subsystem if it
1083
+ ** has not yet been initialized, e.g. early SQLite log
1084
+ ** messages, etc.
1085
+ */
1086
+ if( !json_is_main_boostrapped() ) json_main_bootstrap();
10811087
json_err( 0, z, 1 );
1082
- if( g.isHTTP ){
1088
+ if( g.isHTTP && !g.json.preserveRc ){
10831089
rc = 0 /* avoid HTTP 500 */;
1090
+ }
1091
+ if( g.cgiOutput==1 ){
1092
+ g.cgiOutput = 2;
1093
+ cgi_reply();
10841094
}
10851095
}
10861096
else
10871097
#endif
10881098
if( g.cgiOutput==1 && g.db ){
@@ -1191,10 +1201,16 @@
11911201
z = vmprintf(zFormat, ap);
11921202
va_end(ap);
11931203
fossil_errorlog("warning: %s", z);
11941204
#ifdef FOSSIL_ENABLE_JSON
11951205
if(g.json.isJsonMode){
1206
+ /*
1207
+ ** Avoid calling into the JSON support subsystem if it
1208
+ ** has not yet been initialized, e.g. early SQLite log
1209
+ ** messages, etc.
1210
+ */
1211
+ if( !json_is_main_boostrapped() ) json_main_bootstrap();
11961212
json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
11971213
}else
11981214
#endif
11991215
{
12001216
if( g.cgiOutput==1 ){
12011217
--- src/printf.c
+++ src/printf.c
@@ -1076,13 +1076,23 @@
1076 ** Write error message output
1077 */
1078 static int fossil_print_error(int rc, const char *z){
1079 #ifdef FOSSIL_ENABLE_JSON
1080 if( g.json.isJsonMode ){
 
 
 
 
 
 
1081 json_err( 0, z, 1 );
1082 if( g.isHTTP ){
1083 rc = 0 /* avoid HTTP 500 */;
 
 
 
 
1084 }
1085 }
1086 else
1087 #endif
1088 if( g.cgiOutput==1 && g.db ){
@@ -1191,10 +1201,16 @@
1191 z = vmprintf(zFormat, ap);
1192 va_end(ap);
1193 fossil_errorlog("warning: %s", z);
1194 #ifdef FOSSIL_ENABLE_JSON
1195 if(g.json.isJsonMode){
 
 
 
 
 
 
1196 json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
1197 }else
1198 #endif
1199 {
1200 if( g.cgiOutput==1 ){
1201
--- src/printf.c
+++ src/printf.c
@@ -1076,13 +1076,23 @@
1076 ** Write error message output
1077 */
1078 static int fossil_print_error(int rc, const char *z){
1079 #ifdef FOSSIL_ENABLE_JSON
1080 if( g.json.isJsonMode ){
1081 /*
1082 ** Avoid calling into the JSON support subsystem if it
1083 ** has not yet been initialized, e.g. early SQLite log
1084 ** messages, etc.
1085 */
1086 if( !json_is_main_boostrapped() ) json_main_bootstrap();
1087 json_err( 0, z, 1 );
1088 if( g.isHTTP && !g.json.preserveRc ){
1089 rc = 0 /* avoid HTTP 500 */;
1090 }
1091 if( g.cgiOutput==1 ){
1092 g.cgiOutput = 2;
1093 cgi_reply();
1094 }
1095 }
1096 else
1097 #endif
1098 if( g.cgiOutput==1 && g.db ){
@@ -1191,10 +1201,16 @@
1201 z = vmprintf(zFormat, ap);
1202 va_end(ap);
1203 fossil_errorlog("warning: %s", z);
1204 #ifdef FOSSIL_ENABLE_JSON
1205 if(g.json.isJsonMode){
1206 /*
1207 ** Avoid calling into the JSON support subsystem if it
1208 ** has not yet been initialized, e.g. early SQLite log
1209 ** messages, etc.
1210 */
1211 if( !json_is_main_boostrapped() ) json_main_bootstrap();
1212 json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
1213 }else
1214 #endif
1215 {
1216 if( g.cgiOutput==1 ){
1217
--- src/th_main.c
+++ src/th_main.c
@@ -128,10 +128,11 @@
128128
** it for use.
129129
*/
130130
void Th_InitTraceLog(){
131131
g.thTrace = find_option("th-trace", 0, 0)!=0;
132132
if( g.thTrace ){
133
+ g.fAnyTrace = 1;
133134
blob_zero(&g.thLog);
134135
}
135136
}
136137
137138
/*
138139
--- src/th_main.c
+++ src/th_main.c
@@ -128,10 +128,11 @@
128 ** it for use.
129 */
130 void Th_InitTraceLog(){
131 g.thTrace = find_option("th-trace", 0, 0)!=0;
132 if( g.thTrace ){
 
133 blob_zero(&g.thLog);
134 }
135 }
136
137 /*
138
--- src/th_main.c
+++ src/th_main.c
@@ -128,10 +128,11 @@
128 ** it for use.
129 */
130 void Th_InitTraceLog(){
131 g.thTrace = find_option("th-trace", 0, 0)!=0;
132 if( g.thTrace ){
133 g.fAnyTrace = 1;
134 blob_zero(&g.thLog);
135 }
136 }
137
138 /*
139
+50 -21
--- test/json.test
+++ test/json.test
@@ -33,11 +33,14 @@
3333
# We need a JSON parser to effectively test the JSON produced by
3434
# fossil. It looks like the one from tcllib is exactly what we need.
3535
# On ActiveTcl, add it with teacup. On other platforms, YMMV.
3636
# teacup install json
3737
# teacup install json::write
38
-package require json
38
+if {[catch {package require json}] != 0} then {
39
+ puts "The \"json\" package is not available."
40
+ test_cleanup_then_return
41
+}
3942
4043
proc json2dict {txt} {
4144
set rc [catch {::json::json2dict $txt} result options]
4245
if {$rc != 0} {
4346
protOut "JSON ERROR: $result"
@@ -74,11 +77,12 @@
7477
#
7578
# Returns the status code from the HTTP header.
7679
proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
7780
global RESULT JR
7881
set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
79
- set RESULT [fossil_maybe_answer $request http {*}$args]
82
+ set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
83
+ set head ""; set body ""; set status "--NO_MATCH--"
8084
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
8185
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
8286
if {$status eq "200"} {
8387
set JR [json2dict $body]
8488
}
@@ -115,13 +119,14 @@
115119
}
116120
117121
# handle the actual request
118122
flush stdout
119123
#exec $fossilexe
120
- set RESULT [fossil_maybe_answer $request http {*}$args]
124
+ set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
121125
122126
# separate HTTP headers from body
127
+ set head ""; set body ""; set status "--NO_MATCH--"
123128
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
124129
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
125130
if {$status eq "200"} {
126131
if {[string length $body] > 0} {
127132
set JR [json2dict $body]
@@ -170,10 +175,13 @@
170175
171176
#### VERSION AKA HAI
172177
173178
# The JSON API generally assumes we have a respository, so let it have one.
174179
test_setup
180
+
181
+# Stop backoffice from running during this test as it can cause hangs.
182
+fossil settings backoffice-disable 1
175183
176184
# Check for basic envelope fields in the result with an error
177185
fossil_json -expectError
178186
test_json_envelope json-enverr [concat resultCode fossil timestamp \
179187
resultText command procTimeUs procTimeMs] {}
@@ -307,28 +315,40 @@
307315
# json cap via POST with authToken in request envelope
308316
set anon2 [read_file anon-2]
309317
fossil_post_json "/json/cap" $anon2
310318
test json-cap-POSTenv-env-0 {[string length $JR] > 0}
311319
test_json_envelope_ok json-cap-POSTenv-env
312
-test json-cap-POSTenv-name {[dict get $JR payload name] eq "anonymous"} knownBug
320
+if {[catch {test json-cap-POSTenv-name \
321
+ {[dict get $JR payload name] eq "anonymous"} knownBug} jerr]} then {
322
+ test json-cap-POSTenv-name-threw 0
323
+ protOut "CAUGHT: $jerr"
324
+}
313325
test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
314326
315327
316328
# json cap via GET with authToken in Cookie header
317329
fossil_post_json "/json/cap" {} $AnonCookie
318330
test json-cap-GETcookie-env-0 {[string length $JR] > 0}
319
-test_json_envelope_ok json-cap-GETcookie-env
320
-test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
321
-test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
331
+test_json_envelope_ok json-cap-GETcookie-env-0
332
+if {[catch {test json-cap-GETcookie-name-0 \
333
+ {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
334
+ test json-cap-GETcookie-name-0-threw 0
335
+ protOut "CAUGHT: $jerr"
336
+}
337
+test json-cap-GETcookie-notsetup-0 {![dict get $JR payload permissionFlags setup]}
322338
323339
324340
# json cap via GET with authToken in a parameter
325341
fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
326
-test json-cap-GETcookie-env-0 {[string length $JR] > 0}
327
-test_json_envelope_ok json-cap-GETcookie-env
328
-test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
329
-test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
342
+test json-cap-GETcookie-env-1 {[string length $JR] > 0}
343
+test_json_envelope_ok json-cap-GETcookie-env-1
344
+if {[catch {test json-cap-GETcookie-name-1 \
345
+ {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
346
+ test json-cap-GETcookie-name-1-threw 0
347
+ protOut "CAUGHT: $jerr"
348
+}
349
+test json-cap-GETcookie-notsetup-1 {![dict get $JR payload permissionFlags setup]}
330350
331351
332352
# whoami
333353
# via CLI with no auth token supplied
334354
fossil_json whoami
@@ -675,27 +695,36 @@
675695
# error happens before we have made the determination that the app is
676696
# in JSON mode or if the error handling is incorrectly not
677697
# recognizing JSON mode.
678698
#
679699
#test_setup x.fossil
680
-#catch {exec chmod 444 .rep.fossil}; # Unix. What about Win?
681
-fossil_http_json /json/timeline/checkin $U1Cookie
700
+fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie
682701
test json-ROrepo-1-1 {$CODE == 0}
683702
test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
684703
test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
685704
test_json_envelope_ok json-http-timeline1
705
+if {$is_windows} then {
706
+ catch {exec attrib +r .rep.fossil}; # Windows
707
+} else {
708
+ catch {exec chmod 444 .rep.fossil}; # Unix
709
+}
686710
protOut "chmod 444 repo"
687
-catch {exec chmod 444 .rep.fossil}; # Unix
688
-catch {exec attrib +r .rep.fossil}; # Windows
689
-fossil_http_json /json/timeline/checkin $U1Cookie -expectError
711
+fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc
690712
test json-ROrepo-2-1 {$CODE != 0}
691
-test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]} knownBug
692
-test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} knownBug
713
+test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]}
714
+test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
693715
#test_json_envelope_ok json-http-timeline2
694
-catch {exec attrib -r .rep.fossil}; # Windows
695
-catch {exec chmod 666 .rep.fossil}; # Unix
696
-
716
+if {$is_windows} then {
717
+ catch {exec attrib -r .rep.fossil}; # Windows
718
+ catch {exec attrib -r .rep.fossil-shm}
719
+ catch {exec attrib -r .rep.fossil-wal}
720
+} else {
721
+ catch {exec chmod 666 .rep.fossil}; # Unix
722
+ catch {exec chmod 666 .rep.fossil-shm}
723
+ catch {exec chmod 666 .rep.fossil-wal}
724
+}
725
+protOut "chmod 666 repo"
697726
698727
#### Result Codes
699728
# Test cases designed to stimulate each (documented) error code.
700729
701730
# FOSSIL-0000
702731
--- test/json.test
+++ test/json.test
@@ -33,11 +33,14 @@
33 # We need a JSON parser to effectively test the JSON produced by
34 # fossil. It looks like the one from tcllib is exactly what we need.
35 # On ActiveTcl, add it with teacup. On other platforms, YMMV.
36 # teacup install json
37 # teacup install json::write
38 package require json
 
 
 
39
40 proc json2dict {txt} {
41 set rc [catch {::json::json2dict $txt} result options]
42 if {$rc != 0} {
43 protOut "JSON ERROR: $result"
@@ -74,11 +77,12 @@
74 #
75 # Returns the status code from the HTTP header.
76 proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
77 global RESULT JR
78 set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
79 set RESULT [fossil_maybe_answer $request http {*}$args]
 
80 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
81 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
82 if {$status eq "200"} {
83 set JR [json2dict $body]
84 }
@@ -115,13 +119,14 @@
115 }
116
117 # handle the actual request
118 flush stdout
119 #exec $fossilexe
120 set RESULT [fossil_maybe_answer $request http {*}$args]
121
122 # separate HTTP headers from body
 
123 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
124 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
125 if {$status eq "200"} {
126 if {[string length $body] > 0} {
127 set JR [json2dict $body]
@@ -170,10 +175,13 @@
170
171 #### VERSION AKA HAI
172
173 # The JSON API generally assumes we have a respository, so let it have one.
174 test_setup
 
 
 
175
176 # Check for basic envelope fields in the result with an error
177 fossil_json -expectError
178 test_json_envelope json-enverr [concat resultCode fossil timestamp \
179 resultText command procTimeUs procTimeMs] {}
@@ -307,28 +315,40 @@
307 # json cap via POST with authToken in request envelope
308 set anon2 [read_file anon-2]
309 fossil_post_json "/json/cap" $anon2
310 test json-cap-POSTenv-env-0 {[string length $JR] > 0}
311 test_json_envelope_ok json-cap-POSTenv-env
312 test json-cap-POSTenv-name {[dict get $JR payload name] eq "anonymous"} knownBug
 
 
 
 
313 test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
314
315
316 # json cap via GET with authToken in Cookie header
317 fossil_post_json "/json/cap" {} $AnonCookie
318 test json-cap-GETcookie-env-0 {[string length $JR] > 0}
319 test_json_envelope_ok json-cap-GETcookie-env
320 test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
321 test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
 
 
 
 
322
323
324 # json cap via GET with authToken in a parameter
325 fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
326 test json-cap-GETcookie-env-0 {[string length $JR] > 0}
327 test_json_envelope_ok json-cap-GETcookie-env
328 test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
329 test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
 
 
 
 
330
331
332 # whoami
333 # via CLI with no auth token supplied
334 fossil_json whoami
@@ -675,27 +695,36 @@
675 # error happens before we have made the determination that the app is
676 # in JSON mode or if the error handling is incorrectly not
677 # recognizing JSON mode.
678 #
679 #test_setup x.fossil
680 #catch {exec chmod 444 .rep.fossil}; # Unix. What about Win?
681 fossil_http_json /json/timeline/checkin $U1Cookie
682 test json-ROrepo-1-1 {$CODE == 0}
683 test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
684 test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
685 test_json_envelope_ok json-http-timeline1
 
 
 
 
 
686 protOut "chmod 444 repo"
687 catch {exec chmod 444 .rep.fossil}; # Unix
688 catch {exec attrib +r .rep.fossil}; # Windows
689 fossil_http_json /json/timeline/checkin $U1Cookie -expectError
690 test json-ROrepo-2-1 {$CODE != 0}
691 test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]} knownBug
692 test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} knownBug
693 #test_json_envelope_ok json-http-timeline2
694 catch {exec attrib -r .rep.fossil}; # Windows
695 catch {exec chmod 666 .rep.fossil}; # Unix
696
 
 
 
 
 
 
 
697
698 #### Result Codes
699 # Test cases designed to stimulate each (documented) error code.
700
701 # FOSSIL-0000
702
--- test/json.test
+++ test/json.test
@@ -33,11 +33,14 @@
33 # We need a JSON parser to effectively test the JSON produced by
34 # fossil. It looks like the one from tcllib is exactly what we need.
35 # On ActiveTcl, add it with teacup. On other platforms, YMMV.
36 # teacup install json
37 # teacup install json::write
38 if {[catch {package require json}] != 0} then {
39 puts "The \"json\" package is not available."
40 test_cleanup_then_return
41 }
42
43 proc json2dict {txt} {
44 set rc [catch {::json::json2dict $txt} result options]
45 if {$rc != 0} {
46 protOut "JSON ERROR: $result"
@@ -74,11 +77,12 @@
77 #
78 # Returns the status code from the HTTP header.
79 proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
80 global RESULT JR
81 set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
82 set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
83 set head ""; set body ""; set status "--NO_MATCH--"
84 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
85 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
86 if {$status eq "200"} {
87 set JR [json2dict $body]
88 }
@@ -115,13 +119,14 @@
119 }
120
121 # handle the actual request
122 flush stdout
123 #exec $fossilexe
124 set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
125
126 # separate HTTP headers from body
127 set head ""; set body ""; set status "--NO_MATCH--"
128 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
129 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
130 if {$status eq "200"} {
131 if {[string length $body] > 0} {
132 set JR [json2dict $body]
@@ -170,10 +175,13 @@
175
176 #### VERSION AKA HAI
177
178 # The JSON API generally assumes we have a respository, so let it have one.
179 test_setup
180
181 # Stop backoffice from running during this test as it can cause hangs.
182 fossil settings backoffice-disable 1
183
184 # Check for basic envelope fields in the result with an error
185 fossil_json -expectError
186 test_json_envelope json-enverr [concat resultCode fossil timestamp \
187 resultText command procTimeUs procTimeMs] {}
@@ -307,28 +315,40 @@
315 # json cap via POST with authToken in request envelope
316 set anon2 [read_file anon-2]
317 fossil_post_json "/json/cap" $anon2
318 test json-cap-POSTenv-env-0 {[string length $JR] > 0}
319 test_json_envelope_ok json-cap-POSTenv-env
320 if {[catch {test json-cap-POSTenv-name \
321 {[dict get $JR payload name] eq "anonymous"} knownBug} jerr]} then {
322 test json-cap-POSTenv-name-threw 0
323 protOut "CAUGHT: $jerr"
324 }
325 test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
326
327
328 # json cap via GET with authToken in Cookie header
329 fossil_post_json "/json/cap" {} $AnonCookie
330 test json-cap-GETcookie-env-0 {[string length $JR] > 0}
331 test_json_envelope_ok json-cap-GETcookie-env-0
332 if {[catch {test json-cap-GETcookie-name-0 \
333 {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
334 test json-cap-GETcookie-name-0-threw 0
335 protOut "CAUGHT: $jerr"
336 }
337 test json-cap-GETcookie-notsetup-0 {![dict get $JR payload permissionFlags setup]}
338
339
340 # json cap via GET with authToken in a parameter
341 fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
342 test json-cap-GETcookie-env-1 {[string length $JR] > 0}
343 test_json_envelope_ok json-cap-GETcookie-env-1
344 if {[catch {test json-cap-GETcookie-name-1 \
345 {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
346 test json-cap-GETcookie-name-1-threw 0
347 protOut "CAUGHT: $jerr"
348 }
349 test json-cap-GETcookie-notsetup-1 {![dict get $JR payload permissionFlags setup]}
350
351
352 # whoami
353 # via CLI with no auth token supplied
354 fossil_json whoami
@@ -675,27 +695,36 @@
695 # error happens before we have made the determination that the app is
696 # in JSON mode or if the error handling is incorrectly not
697 # recognizing JSON mode.
698 #
699 #test_setup x.fossil
700 fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie
 
701 test json-ROrepo-1-1 {$CODE == 0}
702 test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
703 test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
704 test_json_envelope_ok json-http-timeline1
705 if {$is_windows} then {
706 catch {exec attrib +r .rep.fossil}; # Windows
707 } else {
708 catch {exec chmod 444 .rep.fossil}; # Unix
709 }
710 protOut "chmod 444 repo"
711 fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc
 
 
712 test json-ROrepo-2-1 {$CODE != 0}
713 test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]}
714 test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
715 #test_json_envelope_ok json-http-timeline2
716 if {$is_windows} then {
717 catch {exec attrib -r .rep.fossil}; # Windows
718 catch {exec attrib -r .rep.fossil-shm}
719 catch {exec attrib -r .rep.fossil-wal}
720 } else {
721 catch {exec chmod 666 .rep.fossil}; # Unix
722 catch {exec chmod 666 .rep.fossil-shm}
723 catch {exec chmod 666 .rep.fossil-wal}
724 }
725 protOut "chmod 666 repo"
726
727 #### Result Codes
728 # Test cases designed to stimulate each (documented) error code.
729
730 # FOSSIL-0000
731
+50 -21
--- test/json.test
+++ test/json.test
@@ -33,11 +33,14 @@
3333
# We need a JSON parser to effectively test the JSON produced by
3434
# fossil. It looks like the one from tcllib is exactly what we need.
3535
# On ActiveTcl, add it with teacup. On other platforms, YMMV.
3636
# teacup install json
3737
# teacup install json::write
38
-package require json
38
+if {[catch {package require json}] != 0} then {
39
+ puts "The \"json\" package is not available."
40
+ test_cleanup_then_return
41
+}
3942
4043
proc json2dict {txt} {
4144
set rc [catch {::json::json2dict $txt} result options]
4245
if {$rc != 0} {
4346
protOut "JSON ERROR: $result"
@@ -74,11 +77,12 @@
7477
#
7578
# Returns the status code from the HTTP header.
7679
proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
7780
global RESULT JR
7881
set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
79
- set RESULT [fossil_maybe_answer $request http {*}$args]
82
+ set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
83
+ set head ""; set body ""; set status "--NO_MATCH--"
8084
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
8185
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
8286
if {$status eq "200"} {
8387
set JR [json2dict $body]
8488
}
@@ -115,13 +119,14 @@
115119
}
116120
117121
# handle the actual request
118122
flush stdout
119123
#exec $fossilexe
120
- set RESULT [fossil_maybe_answer $request http {*}$args]
124
+ set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
121125
122126
# separate HTTP headers from body
127
+ set head ""; set body ""; set status "--NO_MATCH--"
123128
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
124129
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
125130
if {$status eq "200"} {
126131
if {[string length $body] > 0} {
127132
set JR [json2dict $body]
@@ -170,10 +175,13 @@
170175
171176
#### VERSION AKA HAI
172177
173178
# The JSON API generally assumes we have a respository, so let it have one.
174179
test_setup
180
+
181
+# Stop backoffice from running during this test as it can cause hangs.
182
+fossil settings backoffice-disable 1
175183
176184
# Check for basic envelope fields in the result with an error
177185
fossil_json -expectError
178186
test_json_envelope json-enverr [concat resultCode fossil timestamp \
179187
resultText command procTimeUs procTimeMs] {}
@@ -307,28 +315,40 @@
307315
# json cap via POST with authToken in request envelope
308316
set anon2 [read_file anon-2]
309317
fossil_post_json "/json/cap" $anon2
310318
test json-cap-POSTenv-env-0 {[string length $JR] > 0}
311319
test_json_envelope_ok json-cap-POSTenv-env
312
-test json-cap-POSTenv-name {[dict get $JR payload name] eq "anonymous"} knownBug
320
+if {[catch {test json-cap-POSTenv-name \
321
+ {[dict get $JR payload name] eq "anonymous"} knownBug} jerr]} then {
322
+ test json-cap-POSTenv-name-threw 0
323
+ protOut "CAUGHT: $jerr"
324
+}
313325
test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
314326
315327
316328
# json cap via GET with authToken in Cookie header
317329
fossil_post_json "/json/cap" {} $AnonCookie
318330
test json-cap-GETcookie-env-0 {[string length $JR] > 0}
319
-test_json_envelope_ok json-cap-GETcookie-env
320
-test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
321
-test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
331
+test_json_envelope_ok json-cap-GETcookie-env-0
332
+if {[catch {test json-cap-GETcookie-name-0 \
333
+ {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
334
+ test json-cap-GETcookie-name-0-threw 0
335
+ protOut "CAUGHT: $jerr"
336
+}
337
+test json-cap-GETcookie-notsetup-0 {![dict get $JR payload permissionFlags setup]}
322338
323339
324340
# json cap via GET with authToken in a parameter
325341
fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
326
-test json-cap-GETcookie-env-0 {[string length $JR] > 0}
327
-test_json_envelope_ok json-cap-GETcookie-env
328
-test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
329
-test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
342
+test json-cap-GETcookie-env-1 {[string length $JR] > 0}
343
+test_json_envelope_ok json-cap-GETcookie-env-1
344
+if {[catch {test json-cap-GETcookie-name-1 \
345
+ {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
346
+ test json-cap-GETcookie-name-1-threw 0
347
+ protOut "CAUGHT: $jerr"
348
+}
349
+test json-cap-GETcookie-notsetup-1 {![dict get $JR payload permissionFlags setup]}
330350
331351
332352
# whoami
333353
# via CLI with no auth token supplied
334354
fossil_json whoami
@@ -675,27 +695,36 @@
675695
# error happens before we have made the determination that the app is
676696
# in JSON mode or if the error handling is incorrectly not
677697
# recognizing JSON mode.
678698
#
679699
#test_setup x.fossil
680
-#catch {exec chmod 444 .rep.fossil}; # Unix. What about Win?
681
-fossil_http_json /json/timeline/checkin $U1Cookie
700
+fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie
682701
test json-ROrepo-1-1 {$CODE == 0}
683702
test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
684703
test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
685704
test_json_envelope_ok json-http-timeline1
705
+if {$is_windows} then {
706
+ catch {exec attrib +r .rep.fossil}; # Windows
707
+} else {
708
+ catch {exec chmod 444 .rep.fossil}; # Unix
709
+}
686710
protOut "chmod 444 repo"
687
-catch {exec chmod 444 .rep.fossil}; # Unix
688
-catch {exec attrib +r .rep.fossil}; # Windows
689
-fossil_http_json /json/timeline/checkin $U1Cookie -expectError
711
+fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc
690712
test json-ROrepo-2-1 {$CODE != 0}
691
-test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]} knownBug
692
-test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} knownBug
713
+test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]}
714
+test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
693715
#test_json_envelope_ok json-http-timeline2
694
-catch {exec attrib -r .rep.fossil}; # Windows
695
-catch {exec chmod 666 .rep.fossil}; # Unix
696
-
716
+if {$is_windows} then {
717
+ catch {exec attrib -r .rep.fossil}; # Windows
718
+ catch {exec attrib -r .rep.fossil-shm}
719
+ catch {exec attrib -r .rep.fossil-wal}
720
+} else {
721
+ catch {exec chmod 666 .rep.fossil}; # Unix
722
+ catch {exec chmod 666 .rep.fossil-shm}
723
+ catch {exec chmod 666 .rep.fossil-wal}
724
+}
725
+protOut "chmod 666 repo"
697726
698727
#### Result Codes
699728
# Test cases designed to stimulate each (documented) error code.
700729
701730
# FOSSIL-0000
702731
--- test/json.test
+++ test/json.test
@@ -33,11 +33,14 @@
33 # We need a JSON parser to effectively test the JSON produced by
34 # fossil. It looks like the one from tcllib is exactly what we need.
35 # On ActiveTcl, add it with teacup. On other platforms, YMMV.
36 # teacup install json
37 # teacup install json::write
38 package require json
 
 
 
39
40 proc json2dict {txt} {
41 set rc [catch {::json::json2dict $txt} result options]
42 if {$rc != 0} {
43 protOut "JSON ERROR: $result"
@@ -74,11 +77,12 @@
74 #
75 # Returns the status code from the HTTP header.
76 proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
77 global RESULT JR
78 set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
79 set RESULT [fossil_maybe_answer $request http {*}$args]
 
80 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
81 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
82 if {$status eq "200"} {
83 set JR [json2dict $body]
84 }
@@ -115,13 +119,14 @@
115 }
116
117 # handle the actual request
118 flush stdout
119 #exec $fossilexe
120 set RESULT [fossil_maybe_answer $request http {*}$args]
121
122 # separate HTTP headers from body
 
123 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
124 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
125 if {$status eq "200"} {
126 if {[string length $body] > 0} {
127 set JR [json2dict $body]
@@ -170,10 +175,13 @@
170
171 #### VERSION AKA HAI
172
173 # The JSON API generally assumes we have a respository, so let it have one.
174 test_setup
 
 
 
175
176 # Check for basic envelope fields in the result with an error
177 fossil_json -expectError
178 test_json_envelope json-enverr [concat resultCode fossil timestamp \
179 resultText command procTimeUs procTimeMs] {}
@@ -307,28 +315,40 @@
307 # json cap via POST with authToken in request envelope
308 set anon2 [read_file anon-2]
309 fossil_post_json "/json/cap" $anon2
310 test json-cap-POSTenv-env-0 {[string length $JR] > 0}
311 test_json_envelope_ok json-cap-POSTenv-env
312 test json-cap-POSTenv-name {[dict get $JR payload name] eq "anonymous"} knownBug
 
 
 
 
313 test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
314
315
316 # json cap via GET with authToken in Cookie header
317 fossil_post_json "/json/cap" {} $AnonCookie
318 test json-cap-GETcookie-env-0 {[string length $JR] > 0}
319 test_json_envelope_ok json-cap-GETcookie-env
320 test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
321 test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
 
 
 
 
322
323
324 # json cap via GET with authToken in a parameter
325 fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
326 test json-cap-GETcookie-env-0 {[string length $JR] > 0}
327 test_json_envelope_ok json-cap-GETcookie-env
328 test json-cap-GETcookie-name {[dict get $JR payload name] eq "anonymous"}
329 test json-cap-GETcookie-notsetup {![dict get $JR payload permissionFlags setup]}
 
 
 
 
330
331
332 # whoami
333 # via CLI with no auth token supplied
334 fossil_json whoami
@@ -675,27 +695,36 @@
675 # error happens before we have made the determination that the app is
676 # in JSON mode or if the error handling is incorrectly not
677 # recognizing JSON mode.
678 #
679 #test_setup x.fossil
680 #catch {exec chmod 444 .rep.fossil}; # Unix. What about Win?
681 fossil_http_json /json/timeline/checkin $U1Cookie
682 test json-ROrepo-1-1 {$CODE == 0}
683 test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
684 test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
685 test_json_envelope_ok json-http-timeline1
 
 
 
 
 
686 protOut "chmod 444 repo"
687 catch {exec chmod 444 .rep.fossil}; # Unix
688 catch {exec attrib +r .rep.fossil}; # Windows
689 fossil_http_json /json/timeline/checkin $U1Cookie -expectError
690 test json-ROrepo-2-1 {$CODE != 0}
691 test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]} knownBug
692 test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} knownBug
693 #test_json_envelope_ok json-http-timeline2
694 catch {exec attrib -r .rep.fossil}; # Windows
695 catch {exec chmod 666 .rep.fossil}; # Unix
696
 
 
 
 
 
 
 
697
698 #### Result Codes
699 # Test cases designed to stimulate each (documented) error code.
700
701 # FOSSIL-0000
702
--- test/json.test
+++ test/json.test
@@ -33,11 +33,14 @@
33 # We need a JSON parser to effectively test the JSON produced by
34 # fossil. It looks like the one from tcllib is exactly what we need.
35 # On ActiveTcl, add it with teacup. On other platforms, YMMV.
36 # teacup install json
37 # teacup install json::write
38 if {[catch {package require json}] != 0} then {
39 puts "The \"json\" package is not available."
40 test_cleanup_then_return
41 }
42
43 proc json2dict {txt} {
44 set rc [catch {::json::json2dict $txt} result options]
45 if {$rc != 0} {
46 protOut "JSON ERROR: $result"
@@ -74,11 +77,12 @@
77 #
78 # Returns the status code from the HTTP header.
79 proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
80 global RESULT JR
81 set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
82 set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
83 set head ""; set body ""; set status "--NO_MATCH--"
84 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
85 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
86 if {$status eq "200"} {
87 set JR [json2dict $body]
88 }
@@ -115,13 +119,14 @@
119 }
120
121 # handle the actual request
122 flush stdout
123 #exec $fossilexe
124 set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
125
126 # separate HTTP headers from body
127 set head ""; set body ""; set status "--NO_MATCH--"
128 regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
129 regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
130 if {$status eq "200"} {
131 if {[string length $body] > 0} {
132 set JR [json2dict $body]
@@ -170,10 +175,13 @@
175
176 #### VERSION AKA HAI
177
178 # The JSON API generally assumes we have a respository, so let it have one.
179 test_setup
180
181 # Stop backoffice from running during this test as it can cause hangs.
182 fossil settings backoffice-disable 1
183
184 # Check for basic envelope fields in the result with an error
185 fossil_json -expectError
186 test_json_envelope json-enverr [concat resultCode fossil timestamp \
187 resultText command procTimeUs procTimeMs] {}
@@ -307,28 +315,40 @@
315 # json cap via POST with authToken in request envelope
316 set anon2 [read_file anon-2]
317 fossil_post_json "/json/cap" $anon2
318 test json-cap-POSTenv-env-0 {[string length $JR] > 0}
319 test_json_envelope_ok json-cap-POSTenv-env
320 if {[catch {test json-cap-POSTenv-name \
321 {[dict get $JR payload name] eq "anonymous"} knownBug} jerr]} then {
322 test json-cap-POSTenv-name-threw 0
323 protOut "CAUGHT: $jerr"
324 }
325 test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
326
327
328 # json cap via GET with authToken in Cookie header
329 fossil_post_json "/json/cap" {} $AnonCookie
330 test json-cap-GETcookie-env-0 {[string length $JR] > 0}
331 test_json_envelope_ok json-cap-GETcookie-env-0
332 if {[catch {test json-cap-GETcookie-name-0 \
333 {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
334 test json-cap-GETcookie-name-0-threw 0
335 protOut "CAUGHT: $jerr"
336 }
337 test json-cap-GETcookie-notsetup-0 {![dict get $JR payload permissionFlags setup]}
338
339
340 # json cap via GET with authToken in a parameter
341 fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
342 test json-cap-GETcookie-env-1 {[string length $JR] > 0}
343 test_json_envelope_ok json-cap-GETcookie-env-1
344 if {[catch {test json-cap-GETcookie-name-1 \
345 {[dict get $JR payload name] eq "anonymous"}} jerr]} then {
346 test json-cap-GETcookie-name-1-threw 0
347 protOut "CAUGHT: $jerr"
348 }
349 test json-cap-GETcookie-notsetup-1 {![dict get $JR payload permissionFlags setup]}
350
351
352 # whoami
353 # via CLI with no auth token supplied
354 fossil_json whoami
@@ -675,27 +695,36 @@
695 # error happens before we have made the determination that the app is
696 # in JSON mode or if the error handling is incorrectly not
697 # recognizing JSON mode.
698 #
699 #test_setup x.fossil
700 fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie
 
701 test json-ROrepo-1-1 {$CODE == 0}
702 test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
703 test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
704 test_json_envelope_ok json-http-timeline1
705 if {$is_windows} then {
706 catch {exec attrib +r .rep.fossil}; # Windows
707 } else {
708 catch {exec chmod 444 .rep.fossil}; # Unix
709 }
710 protOut "chmod 444 repo"
711 fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc
 
 
712 test json-ROrepo-2-1 {$CODE != 0}
713 test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]}
714 test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
715 #test_json_envelope_ok json-http-timeline2
716 if {$is_windows} then {
717 catch {exec attrib -r .rep.fossil}; # Windows
718 catch {exec attrib -r .rep.fossil-shm}
719 catch {exec attrib -r .rep.fossil-wal}
720 } else {
721 catch {exec chmod 666 .rep.fossil}; # Unix
722 catch {exec chmod 666 .rep.fossil-shm}
723 catch {exec chmod 666 .rep.fossil-wal}
724 }
725 protOut "chmod 666 repo"
726
727 #### Result Codes
728 # Test cases designed to stimulate each (documented) error code.
729
730 # FOSSIL-0000
731
+38 -20
--- test/tester.tcl
+++ test/tester.tcl
@@ -170,39 +170,57 @@
170170
set index [lsearch -exact $args -keepNewline]
171171
if {$index != -1} {
172172
set keepNewline 1
173173
set args [lreplace $args $index $index]
174174
}
175
+ set whatIf 0
176
+ set index [lsearch -exact $args -whatIf]
177
+ if {$index != -1} {
178
+ set whatIf 1
179
+ set args [lreplace $args $index $index]
180
+ }
175181
foreach a $args {
176182
lappend cmd $a
177183
}
178184
protOut $cmd
179185
180186
flush stdout
181
- if {[string length $answer] > 0} {
182
- protOut $answer
183
- set prompt_file [file join $::tempPath fossil_prompt_answer]
184
- write_file $prompt_file $answer\n
185
- if {$keepNewline} {
186
- set rc [catch {eval exec -keepnewline $cmd <$prompt_file} result]
187
- } else {
188
- set rc [catch {eval exec $cmd <$prompt_file} result]
189
- }
190
- file delete $prompt_file
191
- } else {
192
- if {$keepNewline} {
193
- set rc [catch {eval exec -keepnewline $cmd} result]
194
- } else {
195
- set rc [catch {eval exec $cmd} result]
196
- }
187
+ if {$whatIf} {
188
+ protOut [pwd]; protOut $answer
189
+ set result WHAT-IF-MODE; set rc 42
190
+ } else {
191
+ if {[string length $answer] > 0} {
192
+ protOut $answer
193
+ set prompt_file [file join $::tempPath fossil_prompt_answer]
194
+ write_file $prompt_file $answer\n
195
+ set execCmd [list eval exec]
196
+ if {$keepNewline} {lappend execCmd -keepnewline}
197
+ lappend execCmd $cmd <$prompt_file
198
+ set rc [catch $execCmd result]
199
+ file delete $prompt_file
200
+ } else {
201
+ set execCmd [list eval exec]
202
+ if {$keepNewline} {lappend execCmd -keepnewline}
203
+ lappend execCmd $cmd
204
+ set rc [catch $execCmd result]
205
+ }
206
+ }
207
+ set ab(str) {child process exited abnormally}
208
+ set ab(len) [string length $ab(str)]
209
+ set ab(off) [expr {$ab(len) - 1}]
210
+ if {$rc && $expectError && \
211
+ [string range $result end-$ab(off) end] eq $ab(str)} {
212
+ set result [string range $result 0 end-$ab(len)]
197213
}
198214
global RESULT CODE
199215
set CODE $rc
200
- if {($rc && !$expectError) || (!$rc && $expectError)} {
201
- protOut "ERROR: $result" 1
202
- } elseif {$::VERBOSE} {
203
- protOut "RESULT: $result"
216
+ if {!$whatIf} {
217
+ if {($rc && !$expectError) || (!$rc && $expectError)} {
218
+ protOut "ERROR ($rc): $result" 1
219
+ } elseif {$::VERBOSE} {
220
+ protOut "RESULT ($rc): $result"
221
+ }
204222
}
205223
set RESULT $result
206224
}
207225
208226
# Read a file into memory.
209227
--- test/tester.tcl
+++ test/tester.tcl
@@ -170,39 +170,57 @@
170 set index [lsearch -exact $args -keepNewline]
171 if {$index != -1} {
172 set keepNewline 1
173 set args [lreplace $args $index $index]
174 }
 
 
 
 
 
 
175 foreach a $args {
176 lappend cmd $a
177 }
178 protOut $cmd
179
180 flush stdout
181 if {[string length $answer] > 0} {
182 protOut $answer
183 set prompt_file [file join $::tempPath fossil_prompt_answer]
184 write_file $prompt_file $answer\n
185 if {$keepNewline} {
186 set rc [catch {eval exec -keepnewline $cmd <$prompt_file} result]
187 } else {
188 set rc [catch {eval exec $cmd <$prompt_file} result]
189 }
190 file delete $prompt_file
191 } else {
192 if {$keepNewline} {
193 set rc [catch {eval exec -keepnewline $cmd} result]
194 } else {
195 set rc [catch {eval exec $cmd} result]
196 }
 
 
 
 
 
 
 
 
 
 
197 }
198 global RESULT CODE
199 set CODE $rc
200 if {($rc && !$expectError) || (!$rc && $expectError)} {
201 protOut "ERROR: $result" 1
202 } elseif {$::VERBOSE} {
203 protOut "RESULT: $result"
 
 
204 }
205 set RESULT $result
206 }
207
208 # Read a file into memory.
209
--- test/tester.tcl
+++ test/tester.tcl
@@ -170,39 +170,57 @@
170 set index [lsearch -exact $args -keepNewline]
171 if {$index != -1} {
172 set keepNewline 1
173 set args [lreplace $args $index $index]
174 }
175 set whatIf 0
176 set index [lsearch -exact $args -whatIf]
177 if {$index != -1} {
178 set whatIf 1
179 set args [lreplace $args $index $index]
180 }
181 foreach a $args {
182 lappend cmd $a
183 }
184 protOut $cmd
185
186 flush stdout
187 if {$whatIf} {
188 protOut [pwd]; protOut $answer
189 set result WHAT-IF-MODE; set rc 42
190 } else {
191 if {[string length $answer] > 0} {
192 protOut $answer
193 set prompt_file [file join $::tempPath fossil_prompt_answer]
194 write_file $prompt_file $answer\n
195 set execCmd [list eval exec]
196 if {$keepNewline} {lappend execCmd -keepnewline}
197 lappend execCmd $cmd <$prompt_file
198 set rc [catch $execCmd result]
199 file delete $prompt_file
200 } else {
201 set execCmd [list eval exec]
202 if {$keepNewline} {lappend execCmd -keepnewline}
203 lappend execCmd $cmd
204 set rc [catch $execCmd result]
205 }
206 }
207 set ab(str) {child process exited abnormally}
208 set ab(len) [string length $ab(str)]
209 set ab(off) [expr {$ab(len) - 1}]
210 if {$rc && $expectError && \
211 [string range $result end-$ab(off) end] eq $ab(str)} {
212 set result [string range $result 0 end-$ab(len)]
213 }
214 global RESULT CODE
215 set CODE $rc
216 if {!$whatIf} {
217 if {($rc && !$expectError) || (!$rc && $expectError)} {
218 protOut "ERROR ($rc): $result" 1
219 } elseif {$::VERBOSE} {
220 protOut "RESULT ($rc): $result"
221 }
222 }
223 set RESULT $result
224 }
225
226 # Read a file into memory.
227
+38 -20
--- test/tester.tcl
+++ test/tester.tcl
@@ -170,39 +170,57 @@
170170
set index [lsearch -exact $args -keepNewline]
171171
if {$index != -1} {
172172
set keepNewline 1
173173
set args [lreplace $args $index $index]
174174
}
175
+ set whatIf 0
176
+ set index [lsearch -exact $args -whatIf]
177
+ if {$index != -1} {
178
+ set whatIf 1
179
+ set args [lreplace $args $index $index]
180
+ }
175181
foreach a $args {
176182
lappend cmd $a
177183
}
178184
protOut $cmd
179185
180186
flush stdout
181
- if {[string length $answer] > 0} {
182
- protOut $answer
183
- set prompt_file [file join $::tempPath fossil_prompt_answer]
184
- write_file $prompt_file $answer\n
185
- if {$keepNewline} {
186
- set rc [catch {eval exec -keepnewline $cmd <$prompt_file} result]
187
- } else {
188
- set rc [catch {eval exec $cmd <$prompt_file} result]
189
- }
190
- file delete $prompt_file
191
- } else {
192
- if {$keepNewline} {
193
- set rc [catch {eval exec -keepnewline $cmd} result]
194
- } else {
195
- set rc [catch {eval exec $cmd} result]
196
- }
187
+ if {$whatIf} {
188
+ protOut [pwd]; protOut $answer
189
+ set result WHAT-IF-MODE; set rc 42
190
+ } else {
191
+ if {[string length $answer] > 0} {
192
+ protOut $answer
193
+ set prompt_file [file join $::tempPath fossil_prompt_answer]
194
+ write_file $prompt_file $answer\n
195
+ set execCmd [list eval exec]
196
+ if {$keepNewline} {lappend execCmd -keepnewline}
197
+ lappend execCmd $cmd <$prompt_file
198
+ set rc [catch $execCmd result]
199
+ file delete $prompt_file
200
+ } else {
201
+ set execCmd [list eval exec]
202
+ if {$keepNewline} {lappend execCmd -keepnewline}
203
+ lappend execCmd $cmd
204
+ set rc [catch $execCmd result]
205
+ }
206
+ }
207
+ set ab(str) {child process exited abnormally}
208
+ set ab(len) [string length $ab(str)]
209
+ set ab(off) [expr {$ab(len) - 1}]
210
+ if {$rc && $expectError && \
211
+ [string range $result end-$ab(off) end] eq $ab(str)} {
212
+ set result [string range $result 0 end-$ab(len)]
197213
}
198214
global RESULT CODE
199215
set CODE $rc
200
- if {($rc && !$expectError) || (!$rc && $expectError)} {
201
- protOut "ERROR: $result" 1
202
- } elseif {$::VERBOSE} {
203
- protOut "RESULT: $result"
216
+ if {!$whatIf} {
217
+ if {($rc && !$expectError) || (!$rc && $expectError)} {
218
+ protOut "ERROR ($rc): $result" 1
219
+ } elseif {$::VERBOSE} {
220
+ protOut "RESULT ($rc): $result"
221
+ }
204222
}
205223
set RESULT $result
206224
}
207225
208226
# Read a file into memory.
209227
--- test/tester.tcl
+++ test/tester.tcl
@@ -170,39 +170,57 @@
170 set index [lsearch -exact $args -keepNewline]
171 if {$index != -1} {
172 set keepNewline 1
173 set args [lreplace $args $index $index]
174 }
 
 
 
 
 
 
175 foreach a $args {
176 lappend cmd $a
177 }
178 protOut $cmd
179
180 flush stdout
181 if {[string length $answer] > 0} {
182 protOut $answer
183 set prompt_file [file join $::tempPath fossil_prompt_answer]
184 write_file $prompt_file $answer\n
185 if {$keepNewline} {
186 set rc [catch {eval exec -keepnewline $cmd <$prompt_file} result]
187 } else {
188 set rc [catch {eval exec $cmd <$prompt_file} result]
189 }
190 file delete $prompt_file
191 } else {
192 if {$keepNewline} {
193 set rc [catch {eval exec -keepnewline $cmd} result]
194 } else {
195 set rc [catch {eval exec $cmd} result]
196 }
 
 
 
 
 
 
 
 
 
 
197 }
198 global RESULT CODE
199 set CODE $rc
200 if {($rc && !$expectError) || (!$rc && $expectError)} {
201 protOut "ERROR: $result" 1
202 } elseif {$::VERBOSE} {
203 protOut "RESULT: $result"
 
 
204 }
205 set RESULT $result
206 }
207
208 # Read a file into memory.
209
--- test/tester.tcl
+++ test/tester.tcl
@@ -170,39 +170,57 @@
170 set index [lsearch -exact $args -keepNewline]
171 if {$index != -1} {
172 set keepNewline 1
173 set args [lreplace $args $index $index]
174 }
175 set whatIf 0
176 set index [lsearch -exact $args -whatIf]
177 if {$index != -1} {
178 set whatIf 1
179 set args [lreplace $args $index $index]
180 }
181 foreach a $args {
182 lappend cmd $a
183 }
184 protOut $cmd
185
186 flush stdout
187 if {$whatIf} {
188 protOut [pwd]; protOut $answer
189 set result WHAT-IF-MODE; set rc 42
190 } else {
191 if {[string length $answer] > 0} {
192 protOut $answer
193 set prompt_file [file join $::tempPath fossil_prompt_answer]
194 write_file $prompt_file $answer\n
195 set execCmd [list eval exec]
196 if {$keepNewline} {lappend execCmd -keepnewline}
197 lappend execCmd $cmd <$prompt_file
198 set rc [catch $execCmd result]
199 file delete $prompt_file
200 } else {
201 set execCmd [list eval exec]
202 if {$keepNewline} {lappend execCmd -keepnewline}
203 lappend execCmd $cmd
204 set rc [catch $execCmd result]
205 }
206 }
207 set ab(str) {child process exited abnormally}
208 set ab(len) [string length $ab(str)]
209 set ab(off) [expr {$ab(len) - 1}]
210 if {$rc && $expectError && \
211 [string range $result end-$ab(off) end] eq $ab(str)} {
212 set result [string range $result 0 end-$ab(len)]
213 }
214 global RESULT CODE
215 set CODE $rc
216 if {!$whatIf} {
217 if {($rc && !$expectError) || (!$rc && $expectError)} {
218 protOut "ERROR ($rc): $result" 1
219 } elseif {$::VERBOSE} {
220 protOut "RESULT ($rc): $result"
221 }
222 }
223 set RESULT $result
224 }
225
226 # Read a file into memory.
227

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button