Add unit tests for json de/serialization.

This tests enjs:format, dejs:format, dejs-soft:format,
en-json:html, and de-json:html.
This commit is contained in:
Aaron Sikes 2020-04-06 13:35:21 -04:00
parent 482e87f8fc
commit ecc8d0ee29
2 changed files with 1482 additions and 9 deletions

View File

@ -0,0 +1,693 @@
/+ *test
=, format
|%
:: split a cord on newlines
::
++ test-to-wain
;: weld
:: basic usage
::
%+ expect-eq
!> ~['hello' 'world']
!> (to-wain 'hello\0aworld')
:: string with no newlines
::
%+ expect-eq
!> ~['hey']
!> (to-wain 'hey')
:: empty string works fine
::
%+ expect-eq
!> ~['']
!> (to-wain '')
:: leading/trailing/consecutive newlines all work fine
::
%+ expect-eq
!> ~['' 'hi' '' '' 'there' '']
!> (to-wain '\0ahi\0a\0a\0athere\0a')
==
:: join a list of lines (cords) into a single cord
::
++ test-of-wain
;: weld
:: basic usage
::
%+ expect-eq
!> 'hey\0athere\0aworld!'
!> (of-wain ~['hey' 'there' 'world!'])
:: empty list
::
%+ expect-eq
!> ''
!> (of-wain ~)
:: single list
::
%+ expect-eq
!> 'hey'
!> (of-wain ~['hey'])
:: list with empties
::
%+ expect-eq
!> 'hey\0a\0athere'
!> (of-wain ~['hey' '' 'there'])
==
:: join a list of lines (tapes) into a single cord.
::
:: Appends an extra newline - this matches unix conventions of a
:: trailing newline. Also see #1, #2
::
++ test-of-wall
;: weld
:: basic usage
::
%+ expect-eq
!> "hey\0athere\0aworld!\0a"
!> (of-wall ~["hey" "there" "world!"])
:: empty list
::
%+ expect-eq
!> ""
!> (of-wall ~)
:: single list
::
%+ expect-eq
!> "hey\0a"
!> (of-wall ~["hey"])
:: list with empties
::
%+ expect-eq
!> "hey\0a\0athere\0a"
!> (of-wall ~["hey" "" "there"])
==
:: encoding and decoding of beams <-> paths
:: (a beam is a fully-qualified file reference. ship, desk, version,
:: path)
::
++ test-beam
=/ b=beam [[p=~zod q=%home r=[%ud p=12]] s=/hoon/zuse/sys]
=/ p=path /~zod/home/12/sys/zuse/hoon
;: weld
:: proper encode
::
%+ expect-eq
!> p
!> (en-beam b)
:: proper decode
::
%+ expect-eq
!> (some b)
!> (de-beam p)
:: proper round trip
::
%+ expect-eq
!> (some b)
!> (de-beam (en-beam b))
:: path too short
::
%+ expect-eq
!> ~
!> (de-beam /~zod/home)
:: invalid ship
::
%+ expect-eq
!> ~
!> (de-beam /'~zodisok'/home/12/sys/zuse/hoon)
:: invalid desk
::
%+ expect-eq
!> ~
!> (de-beam /~zod/12/12/sys/zuse/hoon)
:: invalid case
::
%+ expect-eq
!> ~
!> (de-beam /~zod/home/~zod/sys/zuse/hoon)
==
:: example values used in test
::
++ ex
|%
++ nul `json`~
++ tru `json`[%b &]
++ num `json`[%n ~.12]
++ str `json`[%s 'hey']
++ frond `json`(frond:enjs 'foo' num)
++ obj `json`(pairs:enjs ~[['foo' num] ['bar' str]])
--
:: functions for creating `json` values
::
++ test-enjs
=, enjs
;: weld
:: numbers
::
%+ expect-eq
!> num:ex
!> (numb 12)
%+ expect-eq
!> num:ex
!> (numb 0xc)
%+ expect-eq
!> [%n '0']
!> (numb 0)
:: strings
::
%+ expect-eq
!> str:ex
!> (tape "hey")
%+ expect-eq
:: uses of-wall, so adds the trailing newline
::
!> [%s 'hi\0athere\0a']
!> (wall ~["hi" "there"])
:: objects
::
%+ expect-eq
!> [%o (molt ~[['foo' num:ex]])]
!> (frond 'foo' num:ex)
=+ props=~[['foo' num:ex] ['bar' tru:ex]]
%+ expect-eq
!> [%o (molt props)]
!> (pairs props)
:: time - stored as integer number of milliseconds since the unix epoch
::
%+ expect-eq
!> [%n '1000']
!> (time ~1970.1.1..0.0.1)
:: ship - store ship identity as a string
::
%+ expect-eq
!> [%s 'zod']
!> (ship ~zod)
==
:: dejs - recursive processing of `json` values
::
:: This version crashes when used on improper input. Prefer using
:: dejs-soft (also tested below) which returns units instead.
::
:: decoding from null, booleans, numbers, strings
::
++ test-dejs-primitives
=, dejs
;: weld
:: null
::
%+ expect-eq
!> ~
!> (ul `json`~)
:: booleans
::
:: bo extracts as-is, bu negates it
::
%+ expect-eq
!> &
!> (bo tru:ex)
%+ expect-eq
!> |
!> (bu tru:ex)
%- expect-fail
|. (bo num:ex)
%- expect-fail
|. (bu num:ex)
:: integers
::
:: as @
::
%+ expect-eq
!> 12
!> (ni num:ex)
%- expect-fail
|. (ni tru:ex)
:: as cord
::
%+ expect-eq
!> '12'
!> (no num:ex)
%- expect-fail
|. (no tru:ex)
:: timestamp - ms since the unix epoch
::
%+ expect-eq
!> ~1970.1.1..00.00.01
!> (di [%n ~.1000])
%- expect-fail
|. (di tru:ex)
:: strings
::
:: string as tape
::
%+ expect-eq
!> "hey"
!> (sa str:ex)
%- expect-fail
|. (sa tru:ex)
:: string as cord
::
%+ expect-eq
!> 'hey'
!> (so str:ex)
%- expect-fail
|. (so tru:ex)
:: string with custom parser
::
%+ expect-eq
!> ' '
!> ((su (just ' ')) [%s ' '])
%- expect-fail
|. ((su (just ' ')) tru:ex)
==
:: decoding arrays
::
++ test-dejs-arrays
=, dejs
;: weld
:: ar - as list
::
%+ expect-eq
!> ~[1 2 3]
!> ((ar ni) [%a ~[[%n '1'] [%n '2'] [%n '3']]])
%- expect-fail
|. ((ar ni) str:ex)
%- expect-fail
|. ((ar ni) [%a ~[str:ex]])
:: at - as tuple
::
:: handlers must match exactly
::
%+ expect-eq
!> [1 'hey']
!> ((at ~[ni so]) [%a ~[[%n '1'] [%s 'hey']]])
:: too few or many handlers crash
::
%- expect-fail
|. ((at ~[ni so]) [%a ~])
%- expect-fail
|. ((at ~[ni so]) [%a ~[[%n '1'] [%s 'hey'] [%b &]]])
:: a nested error will crash
::
%- expect-fail
|. ((at ~[ni]) [%a ~[[%s 'hey']]])
==
:: decoding objects
::
++ test-dejs-objects
=, dejs
;: weld
:: of - single-property objects
::
%+ expect-eq
!> ['foo' 12]
!> ((of ~[['foo' ni]]) frond:ex)
%+ expect-eq
!> ['foo' 12]
!> ((of ~[['bar' so] ['foo' ni]]) frond:ex)
%- expect-fail
:: the handler needs to apply properly to the value
::
|. ((of ~[['foo' ni]]) num:ex)
%- expect-fail
:: the key of the frond needs to exist in the handler list
::
|. ((of ~[['bar' so]]) frond:ex)
%- expect-fail
:: an object with multiple properties is an error
::
|. ((of ~[['bar' so] ['foo' ni]]) obj:ex)
:: ot - exact-shape objects to tuple
::
%+ expect-eq
!> [12 'hey']
!> ((ot ~[['foo' ni] ['bar' so]]) obj:ex)
%- expect-fail
:: it checks it's called on an actual object
::
|. ((ot ~[['foo' ni]]) num:ex)
%- expect-fail
:: missing property on the object
::
|. ((ot ~[['foo' ni] ['baz' so]]) obj:ex)
:: ou - object to tuple, with optional properties. value handlers
::
:: are passed (unit json)
::
%+ expect-eq
!> [12 14]
!> ((ou ~[['foo' (uf 14 ni)] ['baz' (uf 14 ni)]]) obj:ex)
:: om - simple object as map
::
%+ expect-eq
!> (molt ~[['foo' num:ex] ['bar' str:ex]])
!> ((om same) obj:ex)
:: op - object to map, but run a parsing function on the keys
::
%+ expect-eq
!> (molt ~[[12 num:ex] [14 str:ex]])
!> ((op dem same) (pairs:enjs ~[['12' num:ex] ['14' str:ex]]))
==
:: decoder transformers
::
++ test-dejs-transformers
=, dejs
;: weld
:: cu - decode, then transform
::
%+ expect-eq
!> 11
!> ((cu dec ni) [%n ~.12])
:: ci - decode, then assert a transformation succeeds
::
%+ expect-eq
!> 12
!> ((ci some ni) num:ex)
%- expect-fail
|. ((ci |=(* ~) ni) num:ex)
:: mu - decode if not null
::
%+ expect-eq
!> ~
!> ((mu ni) nul:ex)
%+ expect-eq
!> (some 12)
!> ((mu ni) num:ex)
:: pe - add prefix to decoded value
::
%+ expect-eq
!> ['a' 12]
!> ((pe 'a' ni) num:ex)
:: uf - defaults for empty (unit json)
::
%+ expect-eq
!> 'nah'
!> ((uf 'nah' ni) ~)
%+ expect-eq
!> 12
!> ((uf 'nah' ni) (some num:ex))
:: un - dangerous ensure a (unit json)
::
%+ expect-eq
!> 12
!> ((un ni) (some num:ex))
%- expect-fail
|. ((un ni) ~)
==
:: various unit/collection helpers
::
++ test-dejs-helpers
=, dejs
=+ all=`(list (unit @))`~[(some 1) (some 2) (some 3)]
=+ nall=`(list (unit @))`~[(some 1) ~ (some 3)]
;: weld
:: za - are all units in this list full?
::
%+ expect-eq
!> &
!> (za ~)
%+ expect-eq
!> &
!> (za all)
%+ expect-eq
!> |
!> (za nall)
:: zl - collapse (list (unit)) -> (unit (list))
::
%+ expect-eq
!> (some ~[1 2 3])
!> (zl all)
%+ expect-eq
!> ~
!> (zl nall)
%+ expect-eq
!> (some ~)
!> (zl ~)
:: zp - force unwrap a (list (unit)) as tuple
::
%+ expect-eq
!> [1 2 3]
!> (zp all)
%- expect-fail
|. (zp nall)
%- expect-fail
|. (zp ~)
:: zm - collapse a (map @tas (unit *)) -> (unit (map @tas *))
::
%+ expect-eq
!> (some (molt ~[['a' 1] ['b' 2]]))
!> (zm (molt ~[['a' (some 1)] ['b' (some 2)]]))
%+ expect-eq
!> ~
!> (zm (molt ~[['a' `(unit @)`(some 1)] ['b' ~]]))
%+ expect-eq
!> (some ~)
!> (zm ~)
==
::
:: dejs-soft recursive processing of `json` values
::
:: These functions return units, which will be nil if the input
:: doesn't match the defined structure.
::
++ test-dejs-soft-primitives
=, dejs-soft
;: weld
:: null
::
%+ expect-eq
!> `~
!> (ul `json`~)
:: booleans
::
:: bo extracts as-is, bu negates it
::
%+ expect-eq
!> `&
!> (bo tru:ex)
%+ expect-eq
!> `|
!> (bu tru:ex)
%+ expect-eq
!> ~
!> (bo num:ex)
%+ expect-eq
!> ~
!> (bu num:ex)
:: integers
:: as @
::
%+ expect-eq
!> `12
!> (ni num:ex)
%+ expect-eq
!> ~
!> (ni tru:ex)
:: as cord
::
%+ expect-eq
!> `'12'
!> (no num:ex)
%+ expect-eq
!> ~
!> (no tru:ex)
:: timestamp - ms since the unix epoch
::
%+ expect-eq
!> `~1970.1.1..00.00.01
!> (di [%n ~.1000])
%+ expect-eq
!> ~
!> (di tru:ex)
:: string as tape
::
%+ expect-eq
!> `"hey"
!> (sa str:ex)
%+ expect-eq
!> ~
!> (sa tru:ex)
:: string as cord
::
%+ expect-eq
!> `'hey'
!> (so str:ex)
%+ expect-eq
!> ~
!> (so tru:ex)
:: string with custom parser
::
%+ expect-eq
!> `' '
!> ((su (just ' ')) [%s ' '])
%+ expect-eq
!> ~
!> ((su (just ' ')) tru:ex)
==
:: decoding arrays
::
++ test-dejs-soft-arrays
=, dejs-soft
;: weld
:: ar - as list
::
%+ expect-eq
!> `~[1 2 3]
!> ((ar ni) [%a ~[[%n '1'] [%n '2'] [%n '3']]])
%+ expect-eq
!> ~
!> ((ar ni) str:ex)
%+ expect-eq
!> ~
!> ((ar ni) [%a ~[str:ex]])
:: at - as tuple
::
:: handlers must match exactly
::
%+ expect-eq
!> `[1 'hey']
!> ((at ~[ni so]) [%a ~[[%n '1'] [%s 'hey']]])
:: too few or many handlers won't match
::
%+ expect-eq
!> ~
!> ((at ~[ni so]) [%a ~])
%+ expect-eq
!> ~
!> ((at ~[ni so]) [%a ~[[%n '1'] [%s 'hey'] [%b &]]])
:: a nested failure to match will propagate upwards
::
%+ expect-eq
!> ~
!> ((at ~[ni]) [%a ~[[%s 'hey']]])
==
:: decoding objects
::
++ test-dejs-soft-objects
=, dejs-soft
;: weld
:: of - single-property objects
::
%+ expect-eq
!> `['foo' 12]
!> ((of ~[['foo' ni]]) frond:ex)
%+ expect-eq
!> `['foo' 12]
!> ((of ~[['bar' so] ['foo' ni]]) frond:ex)
%+ expect-eq
!> ~
:: the handler needs to apply properly to the value
::
!> ((of ~[['foo' ni]]) num:ex)
%+ expect-eq
!> ~
:: the key of the frond needs to exist in the handler list
::
!> ((of ~[['bar' so]]) frond:ex)
%+ expect-eq
!> ~
:: an object with multiple properties is an error
::
!> ((of ~[['bar' so] ['foo' ni]]) obj:ex)
:: ot - exact-shape objects to tuple
::
%+ expect-eq
!> `[12 'hey']
!> ((ot ~[['foo' ni] ['bar' so]]) obj:ex)
%+ expect-eq
!> ~
:: missing property on the object
::
!> ((ot ~[['foo' ni] ['baz' so]]) obj:ex)
:: om - simple object as map
::
%+ expect-eq
!> `(molt ~[['foo' num:ex] ['bar' str:ex]])
!> ((om some) obj:ex)
:: op - object to map, but run a parsing function on the keys
::
%+ expect-eq
!> `(molt ~[[12 num:ex] [14 str:ex]])
!> ((op dem some) (pairs:enjs ~[['12' num:ex] ['14' str:ex]]))
==
:: decoder transformers
::
++ test-dejs-soft-transformers
=, dejs-soft
;: weld
:: cu - decode, then transform
::
%+ expect-eq
!> `11
!> ((cu dec ni) [%n ~.12])
:: ci - decode, then transform, adapting the transformer to return a
:: unit
::
%+ expect-eq
!> `12
!> ((ci some ni) num:ex)
%+ expect-eq
!> ~
!> ((ci |=(* ~) ni) num:ex)
:: mu - decode if not null
::
%+ expect-eq
!> `~
!> ((mu ni) nul:ex)
%+ expect-eq
!> `(some 12)
!> ((mu ni) num:ex)
:: pe - add prefix to decoded value
::
%+ expect-eq
!> `['a' 12]
!> ((pe 'a' ni) num:ex)
==
:: various unit/collection helpers
::
++ test-dejs-soft-helpers
=, dejs-soft
=+ all=`(list (unit @))`~[(some 1) (some 2) (some 3)]
=+ nall=`(list (unit @))`~[(some 1) ~ (some 3)]
;: weld
:: za - are all units in this list full?
::
%+ expect-eq
!> &
!> (za ~)
%+ expect-eq
!> &
!> (za all)
%+ expect-eq
!> |
!> (za nall)
:: zl - collapse (list (unit)) -> (unit (list))
::
%+ expect-eq
!> (some ~[1 2 3])
!> (zl all)
%+ expect-eq
!> ~
!> (zl nall)
%+ expect-eq
!> (some ~)
!> (zl ~)
:: zp - force unwrap a (list (unit)) as tuple
::
%+ expect-eq
!> [1 2 3]
!> (zp all)
%- expect-fail
|. (zp nall)
%- expect-fail
|. (zp ~)
:: zm - collapse a (map @tas (unit *)) -> (unit (map @tas *))
::
%+ expect-eq
!> (some (molt ~[['a' 1] ['b' 2]]))
!> (zm (molt ~[['a' (some 1)] ['b' (some 2)]]))
%+ expect-eq
!> ~
!> (zm (molt ~[['a' `(unit @)`(some 1)] ['b' ~]]))
%+ expect-eq
!> (some ~)
!> (zm ~)
==
--

View File

@ -1,13 +1,15 @@
:: tests for html :: tests for html
::
/+ *test /+ *test
=, html
=, de-xml:html =, de-xml:html
=, en-xml:html =, en-xml:html
|% |%
:: de-xml takes a cord but en-xml returns a tape? :: de-xml takes a cord but en-xml returns a tape?
:: ::
++ test-de-xml ++ test-de-xml
;: weld ;: weld
:: Basic use :: Basic use
:: ::
%+ expect-eq %+ expect-eq
!> ^- manx +:(de-xml:html (crip "<html><head><title>My first webpage</title></head><body><h1>Welcome!</h1>Hello, world! We are on the web.\0a<div></div><script src=\"http://unsafely.tracking.you/cookiemonster.js\"></script></body></html>")) !> ^- manx +:(de-xml:html (crip "<html><head><title>My first webpage</title></head><body><h1>Welcome!</h1>Hello, world! We are on the web.\0a<div></div><script src=\"http://unsafely.tracking.you/cookiemonster.js\"></script></body></html>"))
@ -23,14 +25,15 @@
;script(src "http://unsafely.tracking.you/cookiemonster.js"); ;script(src "http://unsafely.tracking.you/cookiemonster.js");
== ==
== ==
:: CDATA sections :: CDATA sections
:: ::
%+ expect-eq %+ expect-eq
!> ^- manx !> ^- manx
+:(de-xml:html (crip "<elem><![CDATA[text]]></elem>")) +:(de-xml:html (crip "<elem><![CDATA[text]]></elem>"))
!> ^- manx !> ^- manx
;elem: text ;elem: text
:: comments :: comments
::
%+ expect-eq %+ expect-eq
!> ^- manx !> ^- manx
;elem: text ;elem: text
@ -39,7 +42,7 @@
!> ^- manx !> ^- manx
;elem; ;elem;
!> +:(de-xml:html (crip "<elem><!-- comment --></elem>")) !> +:(de-xml:html (crip "<elem><!-- comment --></elem>"))
:: entities :: entities
:: ::
%+ expect-eq %+ expect-eq
!> ^- manx !> ^- manx
@ -59,13 +62,13 @@
:: ::
++ test-en-xml ++ test-en-xml
;: weld ;: weld
:: Entities :: Entities
:: ::
%+ expect-eq %+ expect-eq
!> "<elem>&gt;</elem>" !> "<elem>&gt;</elem>"
!> %- en-xml:html !> %- en-xml:html
;elem: > ;elem: >
:: Basic use :: Basic use
:: ::
%+ expect-eq %+ expect-eq
!> %- en-xml:html !> %- en-xml:html
@ -82,11 +85,788 @@
== ==
== ==
!> "<html><head><title>My first webpage</title></head><body><h1>Welcome!</h1>Hello, world!\0aWe are on the web.\0a<div></div><script src=\"http://unsafely.tracking.you/cookiemonster.js\"></script></body></html>" !> "<html><head><title>My first webpage</title></head><body><h1>Welcome!</h1>Hello, world!\0aWe are on the web.\0a<div></div><script src=\"http://unsafely.tracking.you/cookiemonster.js\"></script></body></html>"
:: Attributes :: Attributes
:: ::
%+ expect-eq %+ expect-eq
!> "<input type=\"submit\">Submit</input>" !> "<input type=\"submit\">Submit</input>"
!> %- en-xml:html !> %- en-xml:html
;input(type "submit"): Submit ;input(type "submit"): Submit
== ==
:: JSON encoding/decoding
::
++ from-code-points
|= points=(list @)
(tufa `(list @c)`points)
++ from-code-point
|= point=@
(tuft point)
+$ json-parse-spec [name=tape input=cord expected=json]
+$ json-parse-rejection-spec [input=cord name=tape]
:: For checking a large list of examples against expected values.
:: It also nicely formats any failures.
::
++ run-parse-specs
:: legend tells of a man who made the Kessel run in less than 12
:: parse-specs...
|= specs=(list json-parse-spec)
%- zing
%- turn
:- specs
|= spec=json-parse-spec
^- tang
=+ result=(expect-eq !>(`expected.spec) !>((de-json:html input.spec)))
?~ result ~
`tang`[[%leaf "in {name.spec}:"] result]
:: Checks that a list of examples all fail to parse
::
++ run-parse-rejection-specs
|= specs=(list json-parse-rejection-spec)
%- zing
%- turn
:- specs
|= spec=json-parse-rejection-spec
^- tang
=+ result=(expect-eq !>(~) !>((de-json:html input.spec)))
?~ result ~
`tang`[[%leaf "in {name.spec}:"] result]
:: example values used in tests
::
++ ex
|%
++ two `json`[%n '2']
++ tru `json`[%b &]
--
:: encoding naked values
::
++ test-en-json-basics
;: weld
%+ expect-eq
!> "true"
!> (en-json [%b &])
%+ expect-eq
!> "false"
!> (en-json [%b |])
%+ expect-eq
!> "null"
!> (en-json ~)
%+ expect-eq
!> "123.45"
!> (en-json [%n '123.45'])
==
:: encoding strings, with proper escaping rules
::
++ test-en-json-strings
:: A less-confusing representation of theses strings are included in comments
::
:: Things get confusing with hoon string literal escapes. The
:: version included as a comment is if you opened the json output
:: in a simple text editor.
::
;: weld
:: "hello"
::
%+ expect-eq
!> "\"hello\""
!> (en-json [%s 'hello'])
:: it escapes quotes
:: "he said \"wow\""
::
%+ expect-eq
!> "\"he said \\\"wow\\\"\""
!> (en-json [%s 'he said "wow"'])
:: it escapes backslashes
:: "Delete C:\\Windows\\System32"
::
%+ expect-eq
!> "\"Delete C:\\\\Windows\\\\System32\""
!> (en-json [%s 'Delete C:\\Windows\\System32'])
:: it uses \n for newlines
:: "hello\nworld"
::
%+ expect-eq
!> "\"hello\\nworld\""
!> (en-json [%s 'hello\0aworld'])
:: it uses \u encoding for control characters (0x1f and below)
:: "ding!\u0007"
::
%+ expect-eq
!> "\"ding!\\u0007\""
!> (en-json [%s 'ding!\07'])
:: it supports null bytes
::
%+ expect-eq
!> "\"null\\u0000byte\\u0000separator\""
!> (en-json [%s 'null\00byte\00separator'])
:: inline unicode characters
::
%+ expect-eq
!> "\"lmao 🤣\""
!> (en-json [%s 'lmao 🤣'])
==
:: encoding arrays
::
++ test-en-json-arrays
;: weld
:: empty array
::
%+ expect-eq
!> "[]"
!> (en-json [%a ~])
:: 1 element
::
%+ expect-eq
!> "[2]"
!> (en-json [%a ~[two:ex]])
:: multiple elements are comma-separated
::
%+ expect-eq
!> "[2,2,2]"
!> (en-json [%a ~[two:ex two:ex two:ex]])
==
:: encoding basic objects
::
++ test-en-json-objects
:: opening curly braces are escaped to avoid urbit string literal
:: interpolation
::
;: weld
:: empty object
::
%+ expect-eq
!> "\{}"
!> (en-json [%o ~])
:: one property
::
%+ expect-eq
!> "\{\"foo\":2}"
!> (en-json [%o (molt ~[['foo' two:ex]])])
:: multiple properties are comma-separated
::
%+ expect-eq
!> "\{\"foo\":2,\"bar\":true}"
!> (en-json [%o (molt ~[['foo' two:ex] ['bar' tru:ex]])])
:: object keys use same encoding logic as strings
::
%+ expect-eq
:: {"\u0007\"\n\\":true}
::
!> "\{\"\\u0007\\\"\\n\\\\\":true}"
!> (en-json [%o (molt ~[['\07"\0a\\' tru:ex]])])
==
:: object encoding stress-test
::
++ test-en-json-complex-structure
%+ expect-eq
:: [{}, 4, [[], [{foo: {"4": 4, "true": true}}]]]
::
!> "[\{},4,[[],[\{\"foo\":\{\"4\":4,\"true\":true}}]]]"
!> %- en-json:html
:- %a
:~ [%o ~]
[%n '4']
:- %a
:~ [%a ~]
:- %a
:~ %+ frond:enjs:format
'foo'
(pairs:enjs:format ~[['4' [%n '4']] ['true' [%b &]]])
==
==
==
:: decoding naked values
::
++ test-de-json-simple-values
=, html
;: weld
%+ expect-eq
!> `~
!> (de-json 'null')
%+ expect-eq
!> `[%b &]
!> (de-json 'true')
%+ expect-eq
!> `[%b |]
!> (de-json 'false')
==
:: The following parser test suite (test-de-json-bad-examples and
:: test-en-json-suite) is adapted from https://github.com/nst/JSONTestSuite/
:: (Copyright (c) 2016 Nicolas Seriot) under the terms of the MIT license.
::
:: These are all inputs that should be rejected by a valid json parser
::
++ test-de-json-bad-examples
%- run-parse-rejection-specs
:~
['[1 true]' "n_array_1_true_without_comma"]
['[aÂ]' "n_array_a_invalid_utf8"]
['["": 1]' "n_array_colon_instead_of_comma"]
['[""],' "n_array_comma_after_close"]
['[,1]' "n_array_comma_and_number"]
['[1,,2]' "n_array_double_comma"]
['["x",,]' "n_array_double_extra_comma"]
['["x"]]' "n_array_extra_close"]
['["",]' "n_array_extra_comma"]
['["x"' "n_array_incomplete"]
['[x' "n_array_incomplete_invalid_value"]
['[3[4]]' "n_array_inner_array_no_comma"]
['[ˇ]' "n_array_invalid_utf8"]
['[1:2]' "n_array_items_separated_by_semicolon"]
['[,]' "n_array_just_comma"]
['[-]' "n_array_just_minus"]
['[ , ""]' "n_array_missing_value"]
['["a",\0a4\0a,1,' "n_array_newlines_unclosed"]
['[1,]' "n_array_number_and_comma"]
['[1,,]' "n_array_number_and_several_commas"]
['["\0b"\\f]' "n_array_spaces_vertical_tab_formfeed"]
['[*]' "n_array_star_inside"]
['[""' "n_array_unclosed"]
['[1,' "n_array_unclosed_trailing_comma"]
['[1,\0a1\0a,1' "n_array_unclosed_with_new_lines"]
['[{}' "n_array_unclosed_with_object_inside"]
['[fals]' "n_incomplete_false"]
['[nul]' "n_incomplete_null"]
['[tru]' "n_incomplete_true"]
['[++1234]' "n_number_++"]
['[+1]' "n_number_+1"]
['[+Inf]' "n_number_+Inf"]
['[-01]' "n_number_-01"]
['[-1.0.]' "n_number_-1.0."]
['[-NaN]' "n_number_-NaN"]
['[.-1]' "n_number_.-1"]
['[.2e-3]' "n_number_.2e-3"]
['[0.1.2]' "n_number_0.1.2"]
['[1 000.0]' "n_number_1_000"]
['[1eE2]' "n_number_1eE2"]
['[Inf]' "n_number_Inf"]
['[NaN]' "n_number_NaN"]
['[1]' "n_number_U+FF11_fullwidth_digit_one"]
['[1+2]' "n_number_expression"]
['[0x1]' "n_number_hex_1_digit"]
['[0x42]' "n_number_hex_2_digits"]
['[Infinity]' "n_number_infinity"]
['[0e+-1]' "n_number_invalid+-"]
['[-123.123foo]' "n_number_invalid-negative-real"]
['[123Â]' "n_number_invalid-utf-8-in-bigger-int"]
['[1e1Â]' "n_number_invalid-utf-8-in-exponent"]
['[0Â]' "n_number_invalid-utf-8-in-int"]
['[-Infinity]' "n_number_minus_infinity"]
['[-foo]' "n_number_minus_sign_with_trailing_garbage"]
['[- 1]' "n_number_minus_space_1"]
['[-012]' "n_number_neg_int_starting_with_zero"]
['[-.123]' "n_number_neg_real_without_int_part"]
['[-1x]' "n_number_neg_with_garbage_at_end"]
['[1ea]' "n_number_real_garbage_after_e"]
['[1eÂ]' "n_number_real_with_invalid_utf8_after_e"]
['[.123]' "n_number_starting_with_dot"]
['[1.2a-3]' "n_number_with_alpha"]
['[1.8011670033376514H-308]' "n_number_with_alpha_char"]
['[012]' "n_number_with_leading_zero"]
['["x", truth]' "n_object_bad_value"]
['{[: "x"}' "n_object_bracket_key"]
['{"x", null}' "n_object_comma_instead_of_colon"]
['{"x"::"b"}' "n_object_double_colon"]
['{🇨🇭}' "n_object_emoji"]
['{"a":"a" 123}' "n_object_garbage_at_end"]
['{key: \'value\'}' "n_object_key_with_single_quotes"]
['{"π":"0",}' "n_object_lone_continuation_byte_in_key_and_trailing_comma"]
['{"a" b}' "n_object_missing_colon"]
['{:"b"}' "n_object_missing_key"]
['{"a" "b"}' "n_object_missing_semicolon"]
['{"a":' "n_object_missing_value"]
['{"a"' "n_object_no-colon"]
['{1:1}' "n_object_non_string_key"]
['{9999E9999:1}' "n_object_non_string_key_but_huge_number_instead"]
['{null:null,null:null}' "n_object_repeated_null_null"]
['{"id":0,,,,,}' "n_object_several_trailing_commas"]
['{\'a\':0}' "n_object_single_quote"]
['{"id":0,}' "n_object_trailing_comma"]
['{"a":"b"}/**/' "n_object_trailing_comment"]
['{"a":"b"}/**//' "n_object_trailing_comment_open"]
['{"a":"b"}//' "n_object_trailing_comment_slash_open"]
['{"a":"b"}/' "n_object_trailing_comment_slash_open_incomplete"]
['{"a":"b",,"c":"d"}' "n_object_two_commas_in_a_row"]
['{a: "b"}' "n_object_unquoted_key"]
['{"a":"a' "n_object_unterminated-value"]
['{ "foo" : "bar", "a" }' "n_object_with_single_string"]
['{"a":"b"}#' "n_object_with_trailing_garbage"]
[' ' "n_single_space"]
['["\\uD800\\"]' "n_string_1_surrogate_then_escape"]
['["\\uD800\\u"]' "n_string_1_surrogate_then_escape_u"]
['["\\uD800\\u1"]' "n_string_1_surrogate_then_escape_u1"]
['["\\uD800\\u1x"]' "n_string_1_surrogate_then_escape_u1x"]
['[é]' "n_string_accentuated_char_no_quotes"]
['["\\x00"]' "n_string_escape_x"]
['["\\\\\\"]' "n_string_escaped_backslash_bad"]
['["\\\09"]' "n_string_escaped_ctrl_char_tab"]
['["\\🌀"]' "n_string_escaped_emoji"]
['["\\"]' "n_string_incomplete_escape"]
['["\\u00A"]' "n_string_incomplete_escaped_character"]
['["\\uD834\\uDd"]' "n_string_incomplete_surrogate"]
['["\\uD800\\uD800\\x"]' "n_string_incomplete_surrogate_escape_invalid"]
['["\\uÂ"]' "n_string_invalid-utf-8-in-escape"]
['["\\a"]' "n_string_invalid_backslash_esc"]
['["\\uqqqq"]' "n_string_invalid_unicode_escape"]
['["\\Â"]' "n_string_invalid_utf8_after_escape"]
['[\\u0020"asd"]' "n_string_leading_uescaped_thinspace"]
['[\\n]' "n_string_no_quotes_with_bad_escape"]
['"' "n_string_single_doublequote"]
['[\'single quote\']' "n_string_single_quote"]
['abc' "n_string_single_string_no_double_quotes"]
['["\\' "n_string_start_escape_unclosed"]
['["new' "n_string_unescaped_newline"]
['line"]' "n_string_unescaped_newline"]
['["\09"]' "n_string_unescaped_tab"]
['"\\UA66D"' "n_string_unicode_CapitalU"]
['""x' "n_string_with_trailing_garbage"]
['[⁠]' "n_structure_U+2060_word_joined"]
['Ôªø' "n_structure_UTF8_BOM_no_data"]
['<.>' "n_structure_angle_bracket_."]
['[<null>]' "n_structure_angle_bracket_null"]
['[1]x' "n_structure_array_trailing_garbage"]
['[1]]' "n_structure_array_with_extra_array_close"]
['["asd]' "n_structure_array_with_unclosed_string"]
['aå' "n_structure_ascii-unicode-identifier"]
['[True]' "n_structure_capitalized_True"]
['1]' "n_structure_close_unopened_array"]
['{"x": true,' "n_structure_comma_instead_of_closing_brace"]
['[][]' "n_structure_double_array"]
[']' "n_structure_end_array"]
['Ôª{}' "n_structure_incomplete_UTF8_BOM"]
['Â' "n_structure_lone-invalid-utf-8"]
['[' "n_structure_lone-open-bracket"]
['["a\00a"]' "n_string_unescaped_crtl_char"]
['["\\00"]' "n_string_backslash_00"]
==
:: TODO: de-json is accepting a slew of number formats it shouldn't.
::
:: Tracking issue here: https://github.com/urbit/urbit/issues/1775
:: Re-enable this test by removing the disable- prefix
::
++ disable-test-reject-invalid-numbers
%- run-parse-rejection-specs
:~
['123\00' "n_multidigit_number_then_00"]
['[1.]' "n_number_real_without_fractional_part"]
['[2.e+3]' "n_number_2.e+3"]
['[2.e-3]' "n_number_2.e-3"]
['[2.e3]' "n_number_2.e3"]
['[9.e+]' "n_number_9.e+"]
['[0.3e+]' "n_number_0.3e+"]
['[0.3e]' "n_number_0.3e"]
['[0.e1]' "n_number_0.e1"]
['[0E+]' "n_number_0_capital_E+"]
['[0E]' "n_number_0_capital_E"]
['[0e+]' "n_number_0e+"]
['[0e]' "n_number_0e"]
['[1.0e+]' "n_number_1.0e+"]
['[1.0e-]' "n_number_1.0e-"]
['[1.0e]' "n_number_1.0e"]
['[-2.]' "n_number_-2."]
==
:: these are all inputs that should be accepted by a valid parser
::
++ test-en-json-suite
=+ frond=frond:enjs:format
=+ pairs=pairs:enjs:format
%- run-parse-specs
:~
:* "y_array_arraysWithSpaces"
'[[] ]'
[%a ~[[%a ~]]]
==
:* "y_array_empty-string"
'[""]'
[%a ~[[%s '']]]
==
:* "y_array_empty"
'[]'
[%a ~]
==
:* "y_array_ending_with_newline"
'["a"]\0a'
[%a ~[[%s 'a']]]
==
:* "y_array_false"
'[false]'
[%a ~[[%b |]]]
==
:* "y_array_heterogeneous"
'[null, 1, "1", {}]'
[%a ~[~ [%n '1'] [%s '1'] [%o ~]]]
==
:* "y_array_null"
'[null]'
[%a ~[~]]
==
:* "y_array_with_1_and_newline"
'[1\0a]'
[%a ~[[%n '1']]]
==
:* "y_array_with_leading_space"
' [1]'
[%a ~[[%n '1']]]
==
:* "y_array_with_several_null"
'[1,null,null,null,2]'
[%a ~[[%n '1'] ~ ~ ~ [%n '2']]]
==
:* "y_array_with_trailing_space"
'[2] '
[%a ~[[%n '2']]]
==
:* "y_number"
'[123e65]'
[%a ~[[%n '123e65']]]
==
:* "y_number_0e+1"
'[0e+1]'
[%a ~[[%n '0e+1']]]
==
:* "y_number_0e1"
'[0e1]'
[%a ~[[%n '0e1']]]
==
:* "y_number_after_space"
'[ 4]'
[%a ~[[%n '4']]]
==
:* "y_number_double_close_to_zero"
'[-0.0000000000000000000000000000000000000000000000000000000\
/00000000000000000000001]'
[%a ~[[%n '-0.0000000000000000000000000000000000000000000000\
/00000000000000000000000000000001']]]
==
:* "y_number_int_with_exp"
'[20e1]'
[%a ~[[%n '20e1']]]
==
:* "y_number_minus_zero"
'[-0]'
[%a ~[[%n '-0']]]
==
:* "y_number_negative_int"
'[-123]'
[%a ~[[%n '-123']]]
==
:* "y_number_negative_one"
'[-1]'
[%a ~[[%n '-1']]]
==
:* "y_number_negative_zero"
'[-0]'
[%a ~[[%n '-0']]]
==
:* "y_number_real_capital_e"
'[1E22]'
[%a ~[[%n '1E22']]]
==
:* "y_number_real_capital_e_neg_exp"
'[1E-2]'
[%a ~[[%n '1E-2']]]
==
:* "y_number_real_capital_e_pos_exp"
'[1E+2]'
[%a ~[[%n '1E+2']]]
==
:* "y_number_real_exponent"
'[123e45]'
[%a ~[[%n '123e45']]]
==
:* "y_number_real_fraction_exponent"
'[123.456e78]'
[%a ~[[%n '123.456e78']]]
==
:* "y_number_real_neg_exp"
'[1e-2]'
[%a ~[[%n '1e-2']]]
==
:* "y_number_real_pos_exponent"
'[1e+2]'
[%a ~[[%n '1e+2']]]
==
:* "y_number_simple_int"
'[123]'
[%a ~[[%n '123']]]
==
:* "y_number_simple_real"
'[123.456789]'
[%a ~[[%n '123.456789']]]
==
:* "y_object"
'{"asd":"sdf", "dfg":"fgh"}'
(pairs ~[['asd' [%s 'sdf']] ['dfg' [%s ['fgh']]]])
==
:* "y_object_basic"
'{"asd":"sdf"}'
(frond ['asd' [%s 'sdf']])
==
:: duplicated keys, it takes the latest one.
::
:* "y_object_duplicated_key"
'{"a":"b","a":"c"}'
(frond ['a' [%s 'c']])
==
:* "y_object_duplicated_key_and_value"
'{"a":"b","a":"b"}'
(frond ['a' [%s 'b']])
==
:* "y_object_empty"
'{}'
[%o ~]
==
:* "y_object_empty_key"
'{"":0}'
(frond ['' [%n '0']])
==
:* "y_object_extreme_numbers"
'{ "min": -1.0e+28, "max": 1.0e+28 }'
(pairs ~[['min' [%n '-1.0e+28']] ['max' [%n '1.0e+28']]])
==
=/ long=@t 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
:* "y_object_long_strings"
'{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], \
/"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'
(pairs ~[['id' [%s long]] ['x' [%a ~[(frond ['id' [%s long]])]]]])
==
:* "y_object_simple"
'{"a":[]}'
(frond 'a' [%a ~])
==
:* "y_object_string_unicode"
'{"title":"\\u041f\\u043e\\u043b\\u0442\\u043e\\u0440\\u0430 \
/\\u0417\\u0435\\u043c\\u043b\\u0435\\u043a\\u043e\\u043f\\u0430" }'
(frond 'title' [%s 'Полтора Землекопа'])
==
:* "y_object_with_newlines"
'{\0a"a": "b"\0a}'
(frond 'a' [%s 'b'])
==
:* "y_string_allowed_escapes"
'["\\"\\\\\\/\\b\\f\\n\\r\\t"]'
[%a ~[[%s '"\\/\08\0c\0a\0d\09']]]
==
:* "y_string_backslash_and_u_escaped_zero"
'["\\\\u0000"]'
[%a ~[[%s '\\u0000']]]
==
:* "y_string_backslash_doublequotes"
'["\\""]'
[%a ~[[%s '"']]]
==
:* "y_string_comments"
'["a/*b*/c/*d//e"]'
[%a ~[[%s 'a/*b*/c/*d//e']]]
==
:* "y_string_double_escape_a"
'["\\\\a"]'
[%a ~[[%s '\\a']]]
==
:* "y_string_double_escape_n"
'["\\\\n"]'
[%a ~[[%s '\\n']]]
==
:* "y_string_escaped_control_character"
'["\\u0012"]'
[%a ~[[%s '\12']]]
==
:* "y_string_in_array_with_leading_space"
'[ "asd"]'
[%a ~[[%s 'asd']]]
==
:* "y_string_nonCharacterInUTF-8_U+10FFFF"
'["􏿿"]'
[%a ~[[%s (from-code-point 0x10.ffff)]]]
==
:* "y_string_nonCharacterInUTF-8_U+FFFF"
'["￿"]'
[%a ~[[%s (from-code-point 0xffff)]]]
==
:* "y_string_null_escape"
'["\\u0000"]'
[%a ~[[%s '\00']]]
==
:* "y_string_one-byte-utf-8"
'["\\u002c"]'
[%a ~[[%s '\2c']]]
==
:* "y_string_pi"
'["π"]'
[%a ~[[%s 'π']]]
==
:* "y_string_reservedCharacterInUTF-8_U+1BFFF"
'["𛿿"]'
[%a ~[[%s (from-code-point 0x1.bfff)]]]
==
:* "y_string_simple_ascii"
'["asd "]'
[%a ~[[%s 'asd ']]]
==
:* "y_string_space"
'" "'
[%s ' ']
==
:* "y_string_three-byte-utf-8"
'["\\u0821"]'
[%a ~[[%s (from-code-point 0x821)]]]
==
:* "y_string_two-byte-utf-8"
'["\\u0123"]'
[%a ~[[%s (from-code-point 0x123)]]]
==
:* "y_string_u+2028_line_sep"
'[""]'
[%a ~[[%s (from-code-point 0x2028)]]]
==
:* "y_string_u+2029_par_sep"
'[""]'
[%a ~[[%s (from-code-point 0x2029)]]]
==
:* "y_string_unicode_2"
'["⍂㈴⍂"]'
[%a ~[[%s '⍂㈴⍂']]]
==
:* "y_string_unicode_U+2064_invisible_plus"
'["\\u2064"]'
[%a ~[[%s (from-code-point 0x2064)]]]
==
:* "y_string_unicode_escaped_double_quote"
'["\\u0022"]'
[%a ~[[%s (from-code-point 0x22)]]]
==
:* "y_string_utf8"
'["€𝄞"]'
[%a ~[[%s '€𝄞']]]
==
:* "y_structure_lonely_false"
'false'
[%b |]
==
:* "y_structure_lonely_int"
'42'
[%n '42']
==
:* "y_structure_lonely_negative_real"
'-0.1'
[%n '-0.1']
==
:* "y_structure_lonely_null"
'null'
~
==
:* "y_structure_lonely_string"
'"asd"'
[%s 'asd']
==
:* "y_structure_lonely_true"
'true'
[%b &]
==
:* "y_structure_string_empty"
'""'
[%s '']
==
:* "y_structure_trailing_newline"
'["a"]\0a'
[%a ~[[%s 'a']]]
==
:* "y_structure_true_in_array"
'[true]'
[%a ~[[%b &]]]
==
:* "y_structure_whitespace_array"
' [] '
[%a ~]
==
==
:: TODO: de-json is rejecting or dropping unicode escape sequences
::
:: Tracking issue here: https://github.com/urbit/urbit/issues/1776
:: Re-enable this test by removing the disable- prefix
::
++ disable-test-parse-unicode-escape-sequences
=+ frond=frond:enjs:format
=+ pairs=pairs:enjs:format
%- run-parse-specs
:~
:* "y_string_with_del_character"
'["a\7fa"]'
[%a ~[[%s 'a\7fa']]]
==
:* "y_string_unicode_U+FDD0_nonchar"
'["\\uFDD0"]'
[%a ~[[%s (from-code-point 0xfdd0)]]]
==
:* "y_string_unicode_U+FFFE_nonchar"
'["\\uFFFE"]'
[%a ~[[%s (from-code-point 0xfffe)]]]
==
:* "y_string_unicode_U+10FFFE_nonchar"
'["\\uDBFF\\uDFFE"]'
[%a ~[[%s (crip (from-code-points ~[0xdbff 0xdffe]))]]]
==
:* "y_string_unicode_U+1FFFE_nonchar"
'["\\uD83F\\uDFFE"]'
[%a ~[[%s (crip (from-code-points ~[0xd83f 0xdffe]))]]]
==
:* "y_string_unicode_U+200B_ZERO_WIDTH_SPACE"
'["\\u200B"]'
[%a ~[[%s (from-code-point 0x200b)]]]
==
:* "y_string_uEscape"
'["\\u0061\\u30af\\u30EA\\u30b9"]'
[%a ~[[%s (crip (from-code-points ~[0x61 0x30af 0x30ea 0x30b9]))]]]
==
:* "y_string_uescaped_newline"
'["new\\u000Aline"]'
[%a ~[[%s 'new\0aline']]]
==
:* "y_string_unescaped_char_delete"
'["\7f"]'
[%a ~[[%s '\7f']]]
==
:* "y_string_unicode"
'["\\uA66D"]'
[%a ~[[%s (from-code-point 0xa66d)]]]
==
:* "y_string_unicodeEscapedBackslash"
'["\\u005C"]'
[%a ~[[%s (from-code-point 0x5c)]]]
==
:* "y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF"
'["\\uD834\\uDd1e"]'
[%a ~[[%s (crip (from-code-points ~[0xd834 0xdd1e]))]]]
==
:* "y_string_last_surrogates_1_and_2"
'["\\uDBFF\\uDFFF"]'
[%a ~[[%s (crip (from-code-points ~[0xdbff 0xdfff]))]]]
==
:* "y_string_nbsp_uescaped"
'["new\\u00A0line"]'
[%a ~[[%s (crip "new{(from-code-points ~[0xa0])}line")]]]
==
:* "y_string_escaped_noncharacter"
'["\\uFFFF"]'
[%a ~[[%s (from-code-point 0xffff)]]]
==
:* "y_string_escaped_null"
'"foo\\u0000bar"'
[%s 'foo\00bar']
==
:* "y_object_escaped_null_in_key"
'{"foo\\u0000bar": 42}'
(frond ['foo\00bar' [%n '42']])
==
:* "y_string_1_2_3_bytes_UTF-8_sequences"
'["\\u0060\\u012a\\u12AB"]'
[%a ~[[%s '`Īካ']]]
==
:* "y_string_accepted_surrogate_pair"
'["\\uD801\\udc37"]'
[%a ~[[%s '𐐷']]]
==
:* "y_string_accepted_surrogate_pairs"
'["\\ud83d\\ude39\\ud83d\\udc8d"]'
[%a ~[[%s '😹💍']]]
==
==
-- --