Fossil

Check-in [2e4238368e]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Experimental changes to get JSON tests passing.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | jsonTestsPass
Files: files | file ages | folders
SHA3-256: 2e4238368ea5093773a0849946d6ce541a16c2d2d0801661026f9b5f7f586a5d
User & Date: mistachkin 2020-06-11 22:38:27.207
Context
2020-06-11
23:06
More JSON test adjustments. ... (check-in: ad0679cabf user: mistachkin tags: jsonTestsPass)
22:38
Experimental changes to get JSON tests passing. ... (check-in: 2e4238368e user: mistachkin tags: jsonTestsPass)
21:03
In the Markdown formatter, bring emphasis markup into closer alignment with the CommonMark spec. In particular, this should allow underscores in the middle of identifiers to be rendered correctly without escapes. ... (check-in: 37806e85d2 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/db.c.
76
77
78
79
80
81
82

83
84
85
86
87
88
89
  va_list ap;
  char *z;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
  if( g.json.isJsonMode ){

    json_err( 0, z, 1 );
  }
  else
#endif /* FOSSIL_ENABLE_JSON */
  if( g.xferPanic && g.cgiOutput==1 ){
    cgi_reset_content();
    @ error Database\serror:\s%F(z)







>







76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
  va_list ap;
  char *z;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
  if( g.json.isJsonMode ){
    if( !json_is_main_boostrapped() ) json_main_bootstrap();
    json_err( 0, z, 1 );
  }
  else
#endif /* FOSSIL_ENABLE_JSON */
  if( g.xferPanic && g.cgiOutput==1 ){
    cgi_reset_content();
    @ error Database\serror:\s%F(z)
Changes to src/json.c.
705
706
707
708
709
710
711











712
713
714
715
716
717
718
** caller.
*/
cson_value * json_req_payload_get(char const *pKey){
  return g.json.reqPayload.o
    ? cson_object_get(g.json.reqPayload.o,pKey)
    : NULL;
}












/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and







>
>
>
>
>
>
>
>
>
>
>







705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
** caller.
*/
cson_value * json_req_payload_get(char const *pKey){
  return g.json.reqPayload.o
    ? cson_object_get(g.json.reqPayload.o,pKey)
    : NULL;
}

/*
** Returns non-zero if the json_main_bootstrap() function has already
** been called.  In general, this function should be used sparingly,
** e.g. from low-level support functions like fossil_warning() where
** there is genuine uncertainty about whether (or not) the JSON setup
** has already been called.
*/
int json_is_main_boostrapped(){
  return ((g.json.gc.v != NULL) && (g.json.gc.a != NULL));
}

/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and
1563
1564
1565
1566
1567
1568
1569

1570
1571
1572
1573
1574
1575
1576
        cgi_printf("%s(",g.json.jsonp);
      }
      cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt );
      if( g.json.jsonp ){
        cgi_append_content(")",1);
      }
    }

  }else{
    json_send_response(resp);
  }
  cson_value_free(resp);
}

/*







>







1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
        cgi_printf("%s(",g.json.jsonp);
      }
      cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt );
      if( g.json.jsonp ){
        cgi_append_content(")",1);
      }
    }
    cgi_reply();
  }else{
    json_send_response(resp);
  }
  cson_value_free(resp);
}

/*
Changes to src/main.c.
272
273
274
275
276
277
278



279
280
281
282
283
284
285
                                  false. This changes how errors are
                                  reported. In JSON mode we try to
                                  always output JSON-form error
                                  responses and always (in CGI mode)
                                  exit() with code 0 to avoid an HTTP
                                  500 error.
                               */



    int resultCode;            /* used for passing back specific codes
                               ** from /json callbacks. */
    int errorDetailParanoia;   /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
    cson_output_opt outOpt;    /* formatting options for JSON mode. */
    cson_value *authToken;     /* authentication token */
    const char *jsonp;         /* Name of JSONP function wrapper. */
    unsigned char dispatchDepth /* Tells JSON command dispatching







>
>
>







272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
                                  false. This changes how errors are
                                  reported. In JSON mode we try to
                                  always output JSON-form error
                                  responses and always (in CGI mode)
                                  exit() with code 0 to avoid an HTTP
                                  500 error.
                               */
    int preserveRc;            /* Do not convert error codes into 0.
                                * This is primarily intended for use
                                * by the test suite. */
    int resultCode;            /* used for passing back specific codes
                               ** from /json callbacks. */
    int errorDetailParanoia;   /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
    cson_output_opt outOpt;    /* formatting options for JSON mode. */
    cson_value *authToken;     /* authentication token */
    const char *jsonp;         /* Name of JSONP function wrapper. */
    unsigned char dispatchDepth /* Tells JSON command dispatching
744
745
746
747
748
749
750



751
752
753
754
755
756
757
    g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
    g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
    g.fSshClient = 0;
    g.zSshCmd = 0;
    if( g.fSqlTrace ) g.fSqlStats = 1;



    g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
#ifdef FOSSIL_ENABLE_TH1_HOOKS
    g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
#endif
    g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
                  g.fHttpTrace|g.fCgiTrace;
    g.zHttpAuth = 0;







>
>
>







747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
    g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
    g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
    g.fSshClient = 0;
    g.zSshCmd = 0;
    if( g.fSqlTrace ) g.fSqlStats = 1;
#ifdef FOSSIL_ENABLE_JSON
    g.json.preserveRc = find_option("json-preserve-rc", 0, 0)!=0;
#endif
    g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
#ifdef FOSSIL_ENABLE_TH1_HOOKS
    g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
#endif
    g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
                  g.fHttpTrace|g.fCgiTrace;
    g.zHttpAuth = 0;
Changes to src/printf.c.
1074
1075
1076
1077
1078
1079
1080

1081
1082
1083
1084
1085
1086
1087
1088
1089

/*
** Write error message output
*/
static int fossil_print_error(int rc, const char *z){
#ifdef FOSSIL_ENABLE_JSON
  if( g.json.isJsonMode ){

    json_err( 0, z, 1 );
    if( g.isHTTP ){
      rc = 0 /* avoid HTTP 500 */;
    }
  }
  else
#endif
  if( g.cgiOutput==1 && g.db ){
    g.cgiOutput = 2;







>

|







1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090

/*
** Write error message output
*/
static int fossil_print_error(int rc, const char *z){
#ifdef FOSSIL_ENABLE_JSON
  if( g.json.isJsonMode ){
    if( !json_is_main_boostrapped() ) json_main_bootstrap();
    json_err( 0, z, 1 );
    if( g.isHTTP && !g.json.preserveRc ){
      rc = 0 /* avoid HTTP 500 */;
    }
  }
  else
#endif
  if( g.cgiOutput==1 && g.db ){
    g.cgiOutput = 2;
1188
1189
1190
1191
1192
1193
1194





1195

1196
1197
1198
1199
1200
1201
1202
  char *z;
  va_list ap;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
  fossil_errorlog("warning: %s", z);
#ifdef FOSSIL_ENABLE_JSON





  if(g.json.isJsonMode){

    json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
  }else
#endif
  {
    if( g.cgiOutput==1 ){
      cgi_printf("<p class=\"generalError\">\n%h\n</p>\n", z);
    }else{







>
>
>
>
>

>







1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
  char *z;
  va_list ap;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
  fossil_errorlog("warning: %s", z);
#ifdef FOSSIL_ENABLE_JSON
  /*
  ** Avoid calling into the JSON support subsystem if it
  ** has not yet been initialized, e.g. early SQLite log
  ** messages, etc.
  */
  if(g.json.isJsonMode){
    if( !json_is_main_boostrapped() ) json_main_bootstrap();
    json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
  }else
#endif
  {
    if( g.cgiOutput==1 ){
      cgi_printf("<p class=\"generalError\">\n%h\n</p>\n", z);
    }else{
Changes to test/json.test.
73
74
75
76
77
78
79

80
81
82
83
84
85
86
# the response body.
#
# Returns the status code from the HTTP header.
proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
  global RESULT JR
  set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
  set RESULT [fossil_maybe_answer $request http {*}$args]

  regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
  regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
  if {$status eq "200"} {
    set JR [json2dict $body]
  }
  return $status
}







>







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# the response body.
#
# Returns the status code from the HTTP header.
proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
  global RESULT JR
  set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
  set RESULT [fossil_maybe_answer $request http {*}$args]
  set head ""; set status "--NO_MATCH--"
  regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
  regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
  if {$status eq "200"} {
    set JR [json2dict $body]
  }
  return $status
}
168
169
170
171
172
173
174



175
176
177
178
179
180
181
  test_dict_keys $testname [dict get $::JR payload] $okfields $badfields
}

#### VERSION AKA HAI

# The JSON API generally assumes we have a respository, so let it have one.
test_setup




# Check for basic envelope fields in the result with an error
fossil_json -expectError
test_json_envelope json-enverr [concat resultCode fossil timestamp \
resultText command procTimeUs procTimeMs] {}
test json-enverr-rc-1 {[dict get $JR resultCode] eq "FOSSIL-3002"}








>
>
>







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
  test_dict_keys $testname [dict get $::JR payload] $okfields $badfields
}

#### VERSION AKA HAI

# The JSON API generally assumes we have a respository, so let it have one.
test_setup

# Stop backoffice from running during this test as it can cause hangs.
fossil settings backoffice-disable 1

# Check for basic envelope fields in the result with an error
fossil_json -expectError
test_json_envelope json-enverr [concat resultCode fossil timestamp \
resultText command procTimeUs procTimeMs] {}
test json-enverr-rc-1 {[dict get $JR resultCode] eq "FOSSIL-3002"}

673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688




689
690
691
692
693

694

695
696

697
698
699
700
701
702
703
# which writes something (timeline creates a temp table). The "repo
# is not writable" error comes back as HTML. i don't know if the
# error happens before we have made the determination that the app is
# in JSON mode or if the error handling is incorrectly not
# recognizing JSON mode.
#
#test_setup x.fossil
#catch {exec chmod 444 .rep.fossil}; # Unix. What about Win?
fossil_http_json /json/timeline/checkin $U1Cookie
test json-ROrepo-1-1 {$CODE == 0}
test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
test_json_envelope_ok json-http-timeline1
protOut "chmod 444 repo"
catch {exec chmod 444 .rep.fossil}; # Unix
catch {exec attrib +r .rep.fossil}; # Windows




fossil_http_json /json/timeline/checkin $U1Cookie -expectError
test json-ROrepo-2-1 {$CODE != 0}
test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]} knownBug
test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} knownBug
#test_json_envelope_ok json-http-timeline2

catch {exec attrib -r .rep.fossil}; # Windows

catch {exec chmod 666 .rep.fossil}; # Unix



#### Result Codes
# Test cases designed to stimulate each (documented) error code.

# FOSSIL-0000
# Not returned by any command. We generally verify that in the
# test_json_envelope_ok command by verifying that the resultCode







<
|




|
<
|
>
>
>
>
|

|
|

>
|
>
|
|
>







677
678
679
680
681
682
683

684
685
686
687
688
689

690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# which writes something (timeline creates a temp table). The "repo
# is not writable" error comes back as HTML. i don't know if the
# error happens before we have made the determination that the app is
# in JSON mode or if the error handling is incorrectly not
# recognizing JSON mode.
#
#test_setup x.fossil

fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie
test json-ROrepo-1-1 {$CODE == 0}
test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
test_json_envelope_ok json-http-timeline1
if {$is_windows} then {

  catch {exec attrib +r .rep.fossil}; # Windows
} else {
  catch {exec chmod 444 .rep.fossil}; # Unix
}
protOut "chmod 444 repo"
fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc
test json-ROrepo-2-1 {$CODE != 0}
test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]}
test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
#test_json_envelope_ok json-http-timeline2
if {$is_windows} then {
  catch {exec attrib -r .rep.fossil}; # Windows
} else {
  catch {exec chmod 666 .rep.fossil}; # Unix
}
protOut "chmod 666 repo"

#### Result Codes
# Test cases designed to stimulate each (documented) error code.

# FOSSIL-0000
# Not returned by any command. We generally verify that in the
# test_json_envelope_ok command by verifying that the resultCode
Changes to test/tester.tcl.
168
169
170
171
172
173
174






175
176
177
178
179
180




181
182
183
184

185
186
187
188
189
190
191

192
193
194
195
196







197
198
199

200
201
202
203

204
205
206
207
208
209
210
  }
  set keepNewline 0
  set index [lsearch -exact $args -keepNewline]
  if {$index != -1} {
    set keepNewline 1
    set args [lreplace $args $index $index]
  }






  foreach a $args {
    lappend cmd $a
  }
  protOut $cmd

  flush stdout




  if {[string length $answer] > 0} {
    protOut $answer
    set prompt_file [file join $::tempPath fossil_prompt_answer]
    write_file $prompt_file $answer\n

    if {$keepNewline} {
      set rc [catch {eval exec -keepnewline $cmd <$prompt_file} result]
    } else {
      set rc [catch {eval exec $cmd <$prompt_file} result]
    }
    file delete $prompt_file
  } else {

    if {$keepNewline} {
      set rc [catch {eval exec -keepnewline $cmd} result]
    } else {
      set rc [catch {eval exec $cmd} result]
    }







  }
  global RESULT CODE
  set CODE $rc

  if {($rc && !$expectError) || (!$rc && $expectError)} {
    protOut "ERROR: $result" 1
  } elseif {$::VERBOSE} {
    protOut "RESULT: $result"

  }
  set RESULT $result
}

# Read a file into memory.
#
proc read_file {filename} {







>
>
>
>
>
>






>
>
>
>
|
|
|
|
>
|
|
<
|
<
|
|
>
|
|
<
|

>
>
>
>
>
>
>



>
|
|
|
|
>







168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

198

199
200
201
202
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
  }
  set keepNewline 0
  set index [lsearch -exact $args -keepNewline]
  if {$index != -1} {
    set keepNewline 1
    set args [lreplace $args $index $index]
  }
  set whatIf 0
  set index [lsearch -exact $args -whatIf]
  if {$index != -1} {
    set whatIf 1
    set args [lreplace $args $index $index]
  }
  foreach a $args {
    lappend cmd $a
  }
  protOut $cmd

  flush stdout
  if {$whatIf} {
    protOut [pwd]; protOut $answer
    set result WHAT-IF-MODE; set rc 42
  } else {
    if {[string length $answer] > 0} {
      protOut $answer
      set prompt_file [file join $::tempPath fossil_prompt_answer]
      write_file $prompt_file $answer\n
      set execCmd [list eval exec]
      if {$keepNewline} {lappend execCmd -keepnewline}
      lappend execCmd $cmd <$prompt_file

      set rc [catch $execCmd result]

      file delete $prompt_file
    } else {
      set execCmd [list eval exec]
      if {$keepNewline} {lappend execCmd -keepnewline}
      lappend execCmd $cmd

      set rc [catch $execCmd result]
    }
  }
  set ab(str) {child process exited abnormally}
  set ab(len) [string length $ab(str)]
  set ab(off) [expr {$ab(len) - 1}]
  if {$rc && $expectError && \
      [string range $result end-$ab(off) end] eq $ab(str)} {
    set result [string range $result 0 end-$ab(len)]
  }
  global RESULT CODE
  set CODE $rc
  if {!$whatIf} {
    if {($rc && !$expectError) || (!$rc && $expectError)} {
      protOut "ERROR ($rc): $result" 1
    } elseif {$::VERBOSE} {
      protOut "RESULT ($rc): $result"
    }
  }
  set RESULT $result
}

# Read a file into memory.
#
proc read_file {filename} {