OILS / spec / ysh-json.test.sh View on Github | oilshell.org

1031 lines, 267 significant
1## oils_failures_allowed: 2
2## tags: dev-minimal
3
4#### usage errors
5
6json read zz
7echo status=$?
8
9json write
10
11## status: 3
12## STDOUT:
13status=2
14## END
15
16#### json write STRING
17shopt --set parse_proc
18
19json write ('foo')
20var s = 'foo'
21json write (s)
22## STDOUT:
23"foo"
24"foo"
25## END
26
27#### json write ARRAY
28json write (:|foo.cc foo.h|)
29json write (['foo.cc', 'foo.h'], space=0)
30## STDOUT:
31[
32 "foo.cc",
33 "foo.h"
34]
35["foo.cc","foo.h"]
36## END
37
38#### json write Dict
39json write ({k: 'v', k2: [4, 5]})
40
41json write ([{k: 'v', k2: 'v2'}, {}])
42
43## STDOUT:
44{
45 "k": "v",
46 "k2": [
47 4,
48 5
49 ]
50}
51[
52 {
53 "k": "v",
54 "k2": "v2"
55 },
56 {}
57]
58## END
59
60#### json write space=0, space=4
61shopt --set parse_proc
62
63var mydict = {name: "bob", age: 30}
64
65json write (mydict, space=0)
66json write (mydict, space=4)
67## STDOUT:
68{"name":"bob","age":30}
69{
70 "name": "bob",
71 "age": 30
72}
73## END
74
75#### json write in command sub
76shopt -s oil:all # for echo
77var mydict = {name: "bob", age: 30}
78json write (mydict)
79var x = $(json write (mydict))
80echo $x
81## STDOUT:
82{
83 "name": "bob",
84 "age": 30
85}
86{
87 "name": "bob",
88 "age": 30
89}
90## END
91
92#### json read passed invalid args
93
94# EOF
95json read
96echo status=$?
97
98json read 'z z'
99echo status=$?
100
101json read a b c
102echo status=$?
103
104## STDOUT:
105status=1
106status=2
107status=2
108## END
109
110#### json read uses $_reply var
111
112# space before true
113echo ' true' | json read
114json write (_reply)
115
116## STDOUT:
117true
118## END
119
120#### json read then json write
121
122# BUG with space before true
123echo '{"name": "bob", "age": 42, "ok": true}' | json read
124json write (_reply)
125
126echo '{"name": "bob", "age": 42, "ok":true}' | json read
127json write (_reply)
128
129echo '{"name": {}, "age": {}, "x":-1, "y": -0}' | json read
130json write (_reply)
131
132## STDOUT:
133{
134 "name": "bob",
135 "age": 42,
136 "ok": true
137}
138{
139 "name": "bob",
140 "age": 42,
141 "ok": true
142}
143{
144 "name": {},
145 "age": {},
146 "x": -1,
147 "y": 0
148}
149## END
150
151#### json read with redirect
152echo '{"age": 42}' > $TMP/foo.txt
153json read (&x) < $TMP/foo.txt
154pp cell :x
155## STDOUT:
156x = (Cell exported:F readonly:F nameref:F val:(value.Dict d:[Dict age (value.Int i:42)]))
157## END
158
159#### json read at end of pipeline (relies on lastpipe)
160echo '{"age": 43}' | json read (&y)
161pp cell y
162## STDOUT:
163y = (Cell exported:F readonly:F nameref:F val:(value.Dict d:[Dict age (value.Int i:43)]))
164## END
165
166#### invalid JSON
167echo '{' | json read (&y)
168echo pipeline status = $?
169pp line (y)
170## status: 1
171## STDOUT:
172pipeline status = 1
173## END
174
175#### Extra data after valid JSON
176
177# Trailing space is OK
178echo '42 ' | json read
179echo num space $?
180
181echo '{} ' | json read
182echo obj space $?
183
184echo '42 # comment' | json8 read
185echo num comment $?
186
187echo '{} # comment ' | json8 read
188echo obj comment $?
189
190echo '42]' | json read
191echo num bracket $?
192
193echo '{}]' | json read
194echo obj bracket $?
195
196## STDOUT:
197num space 0
198obj space 0
199num comment 0
200obj comment 0
201num bracket 1
202obj bracket 1
203## END
204
205#### json write expression
206json write ([1,2,3], space=0)
207echo status=$?
208
209json write (5, 6) # to many args
210echo status=$?
211
212## status: 3
213## STDOUT:
214[1,2,3]
215status=0
216## END
217
218#### json write evaluation error
219
220#var block = ^(echo hi)
221#json write (block)
222#echo status=$?
223
224# undefined var
225json write (a)
226echo 'should have failed'
227
228## status: 1
229## STDOUT:
230## END
231
232#### json write of List in cycle
233
234var L = [1, 2, 3]
235setvar L[0] = L
236
237shopt -s ysh:upgrade
238fopen >tmp.txt {
239 pp line (L)
240}
241fgrep -n -o '[ -->' tmp.txt
242
243json write (L)
244echo 'should have failed'
245
246## status: 1
247## STDOUT:
2481:[ -->
249## END
250
251#### json write of Dict in cycle
252
253var d = {}
254setvar d.k = d
255
256shopt -s ysh:upgrade
257fopen >tmp.txt {
258 pp line (d)
259}
260fgrep -n -o '{ -->' tmp.txt
261
262json write (d)
263echo 'should have failed'
264
265## status: 1
266## STDOUT:
2671:{ -->
268## END
269
270#### json write of List/Dict referenced twice (bug fix)
271
272var mylist = [1,2,3]
273var mydict = {foo: "bar"}
274
275var top = {k: mylist, k2: mylist, k3: mydict, k4: mydict}
276
277# BUG!
278json write (top, space=0)
279
280## STDOUT:
281{"k":[1,2,3],"k2":[1,2,3],"k3":{"foo":"bar"},"k4":{"foo":"bar"}}
282## END
283
284#### json read doesn't accept u'' or b'' strings
285
286json read <<EOF
287{"key": u'val'}
288EOF
289echo status=$?
290
291#pp line (_reply)
292
293json read <<EOF
294{"key": b'val'}
295EOF
296echo status=$?
297
298## STDOUT:
299status=1
300status=1
301## END
302
303#### json read doesn't accept comments, but json8 does
304
305json8 read <<EOF
306{ # comment
307 "key": # zz
308 b'val', # yy
309 "k2": "v2" #
310}
311EOF
312echo status=$?
313
314json8 write (_reply)
315
316json read <<EOF
317{"key": "val"} # comment
318EOF
319echo status=$?
320## STDOUT:
321status=0
322{
323 "key": "val",
324 "k2": "v2"
325}
326status=1
327## END
328
329
330#### json write emits Unicode replacement char for binary data \yff
331
332json write ([3, "foo", $'-\xff\xfe---\xfd=']) > tmp.txt
333
334# Round trip it for good measure
335json read < tmp.txt
336
337json write (_reply)
338
339## STDOUT:
340[
341 3,
342 "foo",
343 "-��---�="
344]
345## END
346
347#### json8 write emits b'' strings for binary data \yff
348
349json8 write ([3, "foo", $'-\xff\xfe-\xfd='])
350
351## STDOUT:
352[
353 3,
354 "foo",
355 b'-\yff\yfe-\yfd='
356]
357## END
358
359
360#### json8 write bytes vs unicode string
361
362u=$'mu \u03bc \x01 \" \\ \b\f\n\r\t'
363u2=$'\x01\x1f' # this is a valid unicode string
364
365b=$'\xff' # this isn't valid unicode
366
367json8 write (u)
368json8 write (u2)
369
370json8 write (b)
371
372## STDOUT:
373"mu μ \u0001 \" \\ \b\f\n\r\t"
374"\u0001\u001f"
375b'\yff'
376## END
377
378#### JSON \/ escapes supported
379
380msg='"\/"'
381
382echo "$msg" | python3 -c 'import json, sys; print(json.load(sys.stdin))'
383
384echo "$msg" | json read
385echo reply=$_reply
386
387j8="b'\\/'"
388echo "$msg" | json read
389echo reply=$_reply
390
391
392## STDOUT:
393/
394reply=/
395reply=/
396## END
397
398#### JSON string can have unescaped ' and J8 string can have unescaped "
399
400json read <<EOF
401"'"
402EOF
403
404pp line (_reply)
405
406json8 read <<EOF
407u'"'
408EOF
409
410pp line (_reply)
411
412## STDOUT:
413(Str) "'"
414(Str) "\""
415## END
416
417
418#### J8 supports superfluous \" escapes, but JSON doesn't support \' escapes
419
420json8 read <<'EOF'
421b'\"'
422EOF
423echo reply=$_reply
424
425json8 read <<'EOF'
426b'\'\'\b\f\n\r\t\"\\'
427EOF
428pp line (_reply)
429
430# Suppress traceback
431python3 -c 'import json, sys; print(json.load(sys.stdin))' 2>/dev/null <<'EOF'
432"\'"
433EOF
434echo python3=$?
435
436json read <<'EOF'
437"\'"
438EOF
439echo json=$?
440
441## STDOUT:
442reply="
443(Str) "''\b\f\n\r\t\"\\"
444python3=1
445json=1
446## END
447
448#### Escaping uses \u0001 in "", but \u{1} in b''
449
450s1=$'\x01'
451s2=$'\x01\xff\x1f' # byte string
452
453json8 write (s1)
454json8 write (s2)
455
456## STDOUT:
457"\u0001"
458b'\u{1}\yff\u{1f}'
459## END
460
461
462#### json8 read
463
464echo '{ }' | json8 read
465pp line (_reply)
466
467echo '[ ]' | json8 read
468pp line (_reply)
469
470echo '[42]' | json8 read
471pp line (_reply)
472
473echo '[true, false]' | json8 read
474pp line (_reply)
475
476echo '{"k": "v"}' | json8 read
477pp line (_reply)
478
479echo '{"k": null}' | json8 read
480pp line (_reply)
481
482echo '{"k": 1, "k2": 2}' | json8 read
483pp line (_reply)
484
485echo "{u'k': {b'k2': null}}" | json8 read
486pp line (_reply)
487
488echo '{"k": {"k2": "v2"}, "k3": "backslash \\ \" \n line 2 \u03bc "}' | json8 read
489pp line (_reply)
490
491json8 read (&x) <<'EOF'
492{u'k': {u'k2': u'v2'}, u'k3': u'backslash \\ \" \n line 2 \u{3bc} '}
493EOF
494pp line (x)
495
496## STDOUT:
497(Dict) {}
498(List) []
499(List) [42]
500(List) [true,false]
501(Dict) {"k":"v"}
502(Dict) {"k":null}
503(Dict) {"k":1,"k2":2}
504(Dict) {"k":{"k2":null}}
505(Dict) {"k":{"k2":"v2"},"k3":"backslash \\ \" \n line 2 μ "}
506(Dict) {"k":{"k2":"v2"},"k3":"backslash \\ \" \n line 2 μ "}
507## END
508
509#### json8 round trip
510
511var obj = [42, 1.5, null, true, "hi", b'\yff\yfe\b\n""']
512
513json8 write (obj, space=0) > j
514
515cat j
516
517json8 read < j
518
519json8 write (_reply)
520
521## STDOUT:
522[42,1.5,null,true,"hi",b'\yff\yfe\b\n""']
523[
524 42,
525 1.5,
526 null,
527 true,
528 "hi",
529 b'\yff\yfe\b\n""'
530]
531## END
532
533#### json round trip (regression)
534
535var d = {
536 short: '-v', long: '--verbose', type: null, default: '', help: 'Enable verbose logging'
537}
538
539json write (d) | json read
540
541pp line (_reply)
542
543## STDOUT:
544(Dict) {"short":"-v","long":"--verbose","type":null,"default":"","help":"Enable verbose logging"}
545## END
546
547#### round trip: decode surrogate pair and encode
548
549var j = r'"\ud83e\udd26"'
550echo $j | json read (&c1)
551
552json write (c1)
553
554var j = r'"\uD83E\uDD26"'
555echo $j | json read (&c2)
556
557json write (c2)
558
559# Not a surrogate pair
560var j = r'"\u0001\u0002"'
561echo $j | json read (&c3)
562
563json write (c3)
564
565var j = r'"\u0100\u0101\u0102"'
566echo $j | json read (&c4)
567
568json write (c4)
569
570## STDOUT:
571"🤦"
572"🤦"
573"\u0001\u0002"
574"ĀāĂ"
575## END
576
577#### round trip: decode surrogate half and encode
578
579# TODO: Weird Python allows this to be decoded, but I think the Bjoern state
580# machine will not!
581
582shopt -s ysh:upgrade
583
584for j in '"\ud83e"' '"\udd26"' {
585 var s = fromJson(j)
586 pp line (s)
587
588 # TODO: modify DFA to return the code point in the surrogate range, and
589 # print it in JSON mode
590 # j8 mode could possibly use byte strings
591 json write (s)
592}
593
594## STDOUT:
595(Str) b'\ya0\ybe'
596"\ud83e"
597(Str) b'\yb4\ya6'
598"\udd26"
599## END
600
601#### toJson() toJson8()
602
603var obj = [42, 1.5, null, true, "hi", b'\yf0']
604
605echo $[toJson(obj)]
606echo $[toJson8(obj)]
607
608var obj2 = [3, 4]
609echo $[toJson(obj2, space=0)] # same as the default
610echo $[toJson8(obj2, space=0)]
611
612echo $[toJson(obj2, space=2)]
613echo $[toJson8(obj2, space=2)]
614
615# fully specify this behavior
616echo $[toJson(obj2, space=-2)]
617echo $[toJson8(obj2, space=-2)]
618
619## STDOUT:
620[42,1.5,null,true,"hi",""]
621[42,1.5,null,true,"hi",b'\yf0']
622[3,4]
623[3,4]
624[
625 3,
626 4
627]
628[
629 3,
630 4
631]
632[3,4]
633[3,4]
634## END
635
636#### fromJson() fromJson8()
637
638var m1 = '[42,1.5,null,true,"hi"]'
639
640# JSON8 message
641var m2 = '[42,1.5,null,true,"hi",' ++ "u''" ++ ']'
642
643pp line (fromJson8(m1))
644pp line (fromJson(m1))
645
646pp line (fromJson8(m2))
647pp line (fromJson(m2)) # fails
648
649## status: 4
650## STDOUT:
651(List) [42,1.5,null,true,"hi"]
652(List) [42,1.5,null,true,"hi"]
653(List) [42,1.5,null,true,"hi",""]
654## END
655
656#### User can handle errors - toJson() toJson8()
657shopt -s ysh:upgrade
658
659var obj = []
660call obj->append(obj)
661
662try {
663 echo $[toJson(obj)]
664}
665echo status=$_status
666echo "encode error $[_error.message]" | sed 's/0x[a-f0-9]\+/(object id)/'
667
668try { # use different style
669 echo $[toJson8( /d+/ )]
670}
671echo status=$_status
672echo "encode error $[_error.message]"
673
674# This makes the interpreter fail with a message
675echo $[toJson(obj)]
676
677## status: 4
678## STDOUT:
679status=4
680encode error Can't encode List (object id) in object cycle
681status=4
682encode error Can't serialize object of type Eggex
683## END
684
685#### User can handle errors - fromJson() fromJson8()
686shopt -s ysh:upgrade
687
688var message ='[42,1.5,null,true,"hi"'
689
690try {
691 var obj = fromJson(message)
692}
693echo status=$_status
694echo "decode error $[_error.message]" | egrep -o '.*Expected.*RBracket'
695
696try {
697 var obj = fromJson8(message)
698}
699echo status=$_status
700echo "decode error $[_error.message]" | egrep -o '.*Expected.*RBracket'
701
702try {
703 var obj = fromJson('[+]')
704}
705echo "positions $[_error.start_pos] - $[_error.end_pos]"
706
707# This makes the interpreter fail with a message
708var obj = fromJson(message)
709
710## status: 4
711## STDOUT:
712status=4
713decode error Expected Id.J8_RBracket
714status=4
715decode error Expected Id.J8_RBracket
716positions 1 - 2
717## END
718
719
720#### ASCII control chars can't appear literally in messages
721shopt -s ysh:upgrade
722
723var message=$'"\x01"'
724#echo $message | od -c
725
726try {
727 var obj = fromJson(message)
728}
729echo status=$_status
730echo "$[_error.message]" | egrep -o 'ASCII control chars'
731
732## STDOUT:
733status=4
734ASCII control chars
735## END
736
737
738#### \yff can't appear in u'' code strings (command)
739
740shopt -s ysh:upgrade
741
742echo -n b'\yfd' | od -A n -t x1
743echo -n u'\yfd' | od -A n -t x1
744
745## status: 2
746## STDOUT:
747 fd
748## END
749
750#### \yff can't appear in u'' code strings (expr)
751
752var x = b'\yfe'
753write -n -- $x | od -A n -t x1
754
755var x = u'\yfe'
756write -n -- $x | od -A n -t x1
757
758## status: 2
759## STDOUT:
760 fe
761## END
762
763#### \yff can't appear in u'' multiline code strings
764
765shopt -s ysh:upgrade
766
767echo -n b'''\yfc''' | od -A n -t x1
768echo -n u'''\yfd''' | od -A n -t x1
769
770## status: 2
771## STDOUT:
772 fc
773## END
774
775#### \yff can't appear in u'' data strings
776
777#shopt -s ysh:upgrade
778
779json8 read (&b) <<'EOF'
780b'\yfe'
781EOF
782pp line (b)
783
784json8 read (&u) <<'EOF'
785u'\yfe'
786EOF
787pp line (u) # undefined
788
789## status: 1
790## STDOUT:
791(Str) b'\yfe'
792## END
793
794#### \u{dc00} can't be in surrogate range in code (command)
795
796shopt -s ysh:upgrade
797
798echo -n u'\u{dc00}' | od -A n -t x1
799
800## status: 2
801## STDOUT:
802## END
803
804#### \u{dc00} can't be in surrogate range in code (expr)
805
806shopt -s ysh:upgrade
807
808var x = u'\u{dc00}'
809echo $x | od -A n -t x1
810
811## status: 2
812## STDOUT:
813## END
814
815#### \u{dc00} can't be in surrogate range in data
816
817json8 read <<'EOF'
818["long string", u'hello \u{d7ff}', "other"]
819EOF
820echo status=$?
821
822json8 read <<'EOF'
823["long string", u'hello \u{d800}', "other"]
824EOF
825echo status=$?
826
827json8 read <<'EOF'
828["long string", u'hello \u{dfff}', "other"]
829EOF
830echo status=$?
831
832json8 read <<'EOF'
833["long string", u'hello \u{e000}', "other"]
834EOF
835echo status=$?
836
837
838## STDOUT:
839status=0
840status=1
841status=1
842status=0
843## END
844
845
846
847#### Inf and NaN can't be encoded or decoded
848
849# This works in Python, should probably support it
850
851var n = float("NaN")
852var i = float("inf")
853
854pp line (n)
855pp line (i)
856
857json dump (n)
858json dump (i)
859
860## status: 2
861## STDOUT:
862## END
863
864#### Invalid UTF-8 in JSON is rejected
865
866echo $'"\xff"' | json read
867echo status=$?
868
869echo $'"\xff"' | json8 read
870echo status=$?
871
872echo $'\xff' | json read
873echo status=$?
874
875echo $'\xff' | json8 read
876echo status=$?
877
878## STDOUT:
879status=1
880status=1
881status=1
882status=1
883## END
884
885#### Invalid JSON in J8 is rejected
886
887json8 read <<EOF
888b'$(echo -e -n '\xff')'
889EOF
890echo status=$?
891
892json8 read <<EOF
893u'$(echo -e -n '\xff')'
894EOF
895echo status=$?
896
897## STDOUT:
898status=1
899status=1
900## END
901
902#### '' means the same thing as u''
903
904echo "''" | json8 read
905pp line (_reply)
906
907echo "'\u{3bc}'" | json8 read
908pp line (_reply)
909
910echo "'\yff'" | json8 read
911echo status=$?
912
913## STDOUT:
914(Str) ""
915(Str) "μ"
916status=1
917## END
918
919#### decode deeply nested structure (stack overflow)
920
921shopt -s ysh:upgrade
922
923proc pairs(n) {
924 var m = int(n) # TODO: 1 .. n should auto-convert?
925
926 for i in (1 .. m) {
927 write -n -- '['
928 }
929 for i in (1 .. m) {
930 write -n -- ']'
931 }
932}
933
934# This is all Python can handle; C++ can handle more
935msg=$(pairs 50)
936
937#echo $msg
938
939echo "$msg" | json read
940pp line (_reply)
941echo len=$[len(_reply)]
942
943## STDOUT:
944(List) [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
945len=1
946## END
947
948#### decode integer larger than 2^32
949
950json=$(( 1 << 33 ))
951echo $json
952
953echo $json | json read
954pp line (_reply)
955
956## STDOUT:
9578589934592
958(Int) 8589934592
959## END
960
961#### round trip: read/write with ysh
962
963var file = "$REPO_ROOT/spec/testdata/bug.json"
964#cat $file
965cat $file | json read (&cfg)
966json write (cfg) > ysh-json
967
968diff -u $file ysh-json
969echo diff=$?
970
971## STDOUT:
972diff=0
973## END
974
975#### round trip: read/write with ysh, read/write with Python 3 (bug regression)
976
977var file = "$REPO_ROOT/spec/testdata/bug.json"
978#cat $file
979cat $file | json read (&cfg)
980json write (cfg) > ysh-json
981
982cat ysh-json | python3 -c \
983 'import json, sys; obj = json.load(sys.stdin); json.dump(obj, sys.stdout, indent=2); print()' \
984 > py-json
985
986diff -u $file py-json
987echo diff=$?
988
989## STDOUT:
990diff=0
991## END
992
993#### Encoding bytes that don't hit UTF8_REJECT immediately (bug fix)
994
995var x = $'\xce'
996json8 write (x)
997declare -p x
998echo
999
1000var y = $'\xbc'
1001json8 write (y)
1002declare -p y
1003echo
1004
1005var z = $'\xf0\x9f\xa4\xff'
1006json8 write (z)
1007declare -p z
1008
1009## STDOUT:
1010b'\yce'
1011declare -- x=$'\xce'
1012
1013b'\ybc'
1014declare -- y=$'\xbc'
1015
1016b'\yf0\y9f\ya4\yff'
1017declare -- z=$'\xf0\x9f\xa4\xff'
1018## END
1019
1020#### NIL8 token in JSON / JSON8
1021
1022echo "(" | json read
1023echo status=$?
1024
1025echo ")" | json8 read
1026echo status=$?
1027
1028## STDOUT:
1029status=1
1030status=1
1031## END