| | |
| | |
| | |
| |
|
| | |
| |
|
| | package jsontext |
| |
|
| | import ( |
| | "fmt" |
| | "slices" |
| | "strings" |
| | "testing" |
| | "unicode/utf8" |
| | ) |
| |
|
| | func TestPointer(t *testing.T) { |
| | tests := []struct { |
| | in Pointer |
| | wantParent Pointer |
| | wantLast string |
| | wantTokens []string |
| | wantValid bool |
| | }{ |
| | {"", "", "", nil, true}, |
| | {"a", "", "a", []string{"a"}, false}, |
| | {"~", "", "~", []string{"~"}, false}, |
| | {"/a", "", "a", []string{"a"}, true}, |
| | {"/foo/bar", "/foo", "bar", []string{"foo", "bar"}, true}, |
| | {"///", "//", "", []string{"", "", ""}, true}, |
| | {"/~0~1", "", "~/", []string{"~/"}, true}, |
| | {"/\xde\xad\xbe\xef", "", "\xde\xad\xbe\xef", []string{"\xde\xad\xbe\xef"}, false}, |
| | } |
| | for _, tt := range tests { |
| | if got := tt.in.Parent(); got != tt.wantParent { |
| | t.Errorf("Pointer(%q).Parent = %q, want %q", tt.in, got, tt.wantParent) |
| | } |
| | if got := tt.in.LastToken(); got != tt.wantLast { |
| | t.Errorf("Pointer(%q).Last = %q, want %q", tt.in, got, tt.wantLast) |
| | } |
| | if strings.HasPrefix(string(tt.in), "/") { |
| | wantRoundtrip := tt.in |
| | if !utf8.ValidString(string(wantRoundtrip)) { |
| | |
| | wantRoundtrip = Pointer([]rune(wantRoundtrip)) |
| | } |
| | if got := tt.in.Parent().AppendToken(tt.in.LastToken()); got != wantRoundtrip { |
| | t.Errorf("Pointer(%q).Parent().AppendToken(LastToken()) = %q, want %q", tt.in, got, tt.in) |
| | } |
| | in := tt.in |
| | for { |
| | if (in + "x").Contains(tt.in) { |
| | t.Errorf("Pointer(%q).Contains(%q) = true, want false", in+"x", tt.in) |
| | } |
| | if !in.Contains(tt.in) { |
| | t.Errorf("Pointer(%q).Contains(%q) = false, want true", in, tt.in) |
| | } |
| | if in == in.Parent() { |
| | break |
| | } |
| | in = in.Parent() |
| | } |
| | } |
| | if got := slices.Collect(tt.in.Tokens()); !slices.Equal(got, tt.wantTokens) { |
| | t.Errorf("Pointer(%q).Tokens = %q, want %q", tt.in, got, tt.wantTokens) |
| | } |
| | if got := tt.in.IsValid(); got != tt.wantValid { |
| | t.Errorf("Pointer(%q).IsValid = %v, want %v", tt.in, got, tt.wantValid) |
| | } |
| | } |
| | } |
| |
|
| | func TestStateMachine(t *testing.T) { |
| | |
| | |
| | |
| | |
| | |
| | type operation any |
| | type ( |
| | |
| | stackLengths []int64 |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | appendTokens string |
| |
|
| | |
| | appendToken struct { |
| | kind Kind |
| | want error |
| | } |
| |
|
| | |
| | needDelim struct { |
| | next Kind |
| | want byte |
| | } |
| | ) |
| |
|
| | |
| | tests := []struct { |
| | label string |
| | ops []operation |
| | }{{ |
| | "TopLevelValues", |
| | []operation{ |
| | stackLengths{0}, |
| | needDelim{'n', 0}, |
| | appendTokens(`nft`), |
| | stackLengths{3}, |
| | needDelim{'"', 0}, |
| | appendTokens(`"0[]{}`), |
| | stackLengths{7}, |
| | }, |
| | }, { |
| | "ArrayValues", |
| | []operation{ |
| | stackLengths{0}, |
| | needDelim{'[', 0}, |
| | appendTokens(`[`), |
| | stackLengths{1, 0}, |
| | needDelim{'n', 0}, |
| | appendTokens(`nft`), |
| | stackLengths{1, 3}, |
| | needDelim{'"', ','}, |
| | appendTokens(`"0[]{}`), |
| | stackLengths{1, 7}, |
| | needDelim{']', 0}, |
| | appendTokens(`]`), |
| | stackLengths{1}, |
| | }, |
| | }, { |
| | "ObjectValues", |
| | []operation{ |
| | stackLengths{0}, |
| | needDelim{'{', 0}, |
| | appendTokens(`{`), |
| | stackLengths{1, 0}, |
| | needDelim{'"', 0}, |
| | appendTokens(`"`), |
| | stackLengths{1, 1}, |
| | needDelim{'n', ':'}, |
| | appendTokens(`n`), |
| | stackLengths{1, 2}, |
| | needDelim{'"', ','}, |
| | appendTokens(`"f"t`), |
| | stackLengths{1, 6}, |
| | appendTokens(`"""0"[]"{}`), |
| | stackLengths{1, 14}, |
| | needDelim{'}', 0}, |
| | appendTokens(`}`), |
| | stackLengths{1}, |
| | }, |
| | }, { |
| | "ObjectCardinality", |
| | []operation{ |
| | appendTokens(`{`), |
| |
|
| | |
| | appendToken{'n', ErrNonStringName}, |
| | appendToken{'f', ErrNonStringName}, |
| | appendToken{'t', ErrNonStringName}, |
| | appendToken{'0', ErrNonStringName}, |
| | appendToken{'{', ErrNonStringName}, |
| | appendToken{'[', ErrNonStringName}, |
| | appendTokens(`"`), |
| |
|
| | |
| | appendToken{'}', errMissingValue}, |
| | appendTokens(`"`), |
| |
|
| | appendTokens(`}`), |
| | }, |
| | }, { |
| | "MismatchingDelims", |
| | []operation{ |
| | appendToken{'}', errMismatchDelim}, |
| | appendTokens(`[[{`), |
| | appendToken{']', errMismatchDelim}, |
| | appendTokens(`}]`), |
| | appendToken{'}', errMismatchDelim}, |
| | appendTokens(`]`), |
| | appendToken{']', errMismatchDelim}, |
| | }, |
| | }} |
| |
|
| | for _, tt := range tests { |
| | t.Run(tt.label, func(t *testing.T) { |
| | |
| | var ops []operation |
| | for _, op := range tt.ops { |
| | if toks, ok := op.(appendTokens); ok { |
| | for _, k := range []byte(toks) { |
| | ops = append(ops, appendToken{Kind(k), nil}) |
| | } |
| | continue |
| | } |
| | ops = append(ops, op) |
| | } |
| |
|
| | |
| | var state stateMachine |
| | state.reset() |
| | var sequence []Kind |
| | for _, op := range ops { |
| | switch op := op.(type) { |
| | case stackLengths: |
| | var got []int64 |
| | for i := range state.Depth() { |
| | e := state.index(i) |
| | got = append(got, e.Length()) |
| | } |
| | want := []int64(op) |
| | if !slices.Equal(got, want) { |
| | t.Fatalf("%s: stack lengths mismatch:\ngot %v\nwant %v", sequence, got, want) |
| | } |
| | case appendToken: |
| | got := state.append(op.kind) |
| | if !equalError(got, op.want) { |
| | t.Fatalf("%s: append('%c') = %v, want %v", sequence, op.kind, got, op.want) |
| | } |
| | if got == nil { |
| | sequence = append(sequence, op.kind) |
| | } |
| | case needDelim: |
| | if got := state.needDelim(op.next); got != op.want { |
| | t.Fatalf("%s: needDelim('%c') = '%c', want '%c'", sequence, op.next, got, op.want) |
| | } |
| | default: |
| | panic(fmt.Sprintf("unknown operation: %T", op)) |
| | } |
| | } |
| | }) |
| | } |
| | } |
| |
|
| | |
| | |
| | func (s *stateMachine) append(k Kind) error { |
| | switch k { |
| | case 'n', 'f', 't': |
| | return s.appendLiteral() |
| | case '"': |
| | return s.appendString() |
| | case '0': |
| | return s.appendNumber() |
| | case '{': |
| | return s.pushObject() |
| | case '}': |
| | return s.popObject() |
| | case '[': |
| | return s.pushArray() |
| | case ']': |
| | return s.popArray() |
| | default: |
| | panic(fmt.Sprintf("invalid token kind: '%c'", k)) |
| | } |
| | } |
| |
|
| | func TestObjectNamespace(t *testing.T) { |
| | type operation any |
| | type ( |
| | insert struct { |
| | name string |
| | wantInserted bool |
| | } |
| | removeLast struct{} |
| | ) |
| |
|
| | |
| | ops := []operation{ |
| | insert{`""`, true}, |
| | removeLast{}, |
| | insert{`""`, true}, |
| | insert{`""`, false}, |
| |
|
| | |
| | insert{`"alpha"`, true}, |
| | insert{`"ALPHA"`, true}, |
| | insert{`"alpha"`, false}, |
| | insert{`"\u0061\u006c\u0070\u0068\u0061"`, false}, |
| | removeLast{}, |
| | insert{`"alpha"`, false}, |
| | removeLast{}, |
| | insert{`"alpha"`, true}, |
| | removeLast{}, |
| |
|
| | |
| | insert{`"alpha"`, true}, |
| | insert{`"bravo"`, true}, |
| | insert{`"charlie"`, true}, |
| | insert{`"delta"`, true}, |
| | insert{`"echo"`, true}, |
| | insert{`"foxtrot"`, true}, |
| | insert{`"golf"`, true}, |
| | insert{`"hotel"`, true}, |
| | insert{`"india"`, true}, |
| | insert{`"juliet"`, true}, |
| | insert{`"kilo"`, true}, |
| | insert{`"lima"`, true}, |
| | insert{`"mike"`, true}, |
| | insert{`"november"`, true}, |
| | insert{`"oscar"`, true}, |
| | insert{`"papa"`, true}, |
| | insert{`"quebec"`, true}, |
| | insert{`"romeo"`, true}, |
| | insert{`"sierra"`, true}, |
| | insert{`"tango"`, true}, |
| | insert{`"uniform"`, true}, |
| | insert{`"victor"`, true}, |
| | insert{`"whiskey"`, true}, |
| | insert{`"xray"`, true}, |
| | insert{`"yankee"`, true}, |
| | insert{`"zulu"`, true}, |
| |
|
| | |
| | insert{`"` + "\ufffd" + `"`, true}, |
| | insert{`"` + "\ufffd" + `"`, false}, |
| | insert{`"\ufffd"`, false}, |
| | insert{`"\uFFFD"`, false}, |
| | insert{`"` + "\xff" + `"`, false}, |
| | removeLast{}, |
| | insert{`"` + "\ufffd" + `"`, true}, |
| |
|
| | |
| | insert{`"☺☻☹"`, true}, |
| | insert{`"☺☻☹"`, false}, |
| | removeLast{}, |
| | insert{`"☺☻☹"`, true}, |
| | } |
| |
|
| | |
| | |
| | var ns objectNamespace |
| | wantNames := []string{} |
| | for _, reset := range []bool{false, true} { |
| | if reset { |
| | ns.reset() |
| | wantNames = nil |
| | } |
| |
|
| | |
| | for i, op := range ops { |
| | switch op := op.(type) { |
| | case insert: |
| | gotInserted := ns.insertQuoted([]byte(op.name), false) |
| | if gotInserted != op.wantInserted { |
| | t.Fatalf("%d: objectNamespace{%v}.insert(%v) = %v, want %v", i, strings.Join(wantNames, " "), op.name, gotInserted, op.wantInserted) |
| | } |
| | if gotInserted { |
| | b, _ := AppendUnquote(nil, []byte(op.name)) |
| | wantNames = append(wantNames, string(b)) |
| | } |
| | case removeLast: |
| | ns.removeLast() |
| | wantNames = wantNames[:len(wantNames)-1] |
| | default: |
| | panic(fmt.Sprintf("unknown operation: %T", op)) |
| | } |
| |
|
| | |
| | gotNames := []string{} |
| | for i := range ns.length() { |
| | gotNames = append(gotNames, string(ns.getUnquoted(i))) |
| | } |
| | if !slices.Equal(gotNames, wantNames) { |
| | t.Fatalf("%d: objectNamespace = {%v}, want {%v}", i, strings.Join(gotNames, " "), strings.Join(wantNames, " ")) |
| | } |
| | } |
| |
|
| | |
| | if ns.mapNames != nil { |
| | t.Errorf("objectNamespace.mapNames = non-nil, want nil") |
| | } |
| |
|
| | |
| | for i := range 64 { |
| | ns.InsertUnquoted([]byte(fmt.Sprintf(`name%d`, i))) |
| | } |
| |
|
| | |
| | if ns.mapNames == nil { |
| | t.Errorf("objectNamespace.mapNames = nil, want non-nil") |
| | } |
| | } |
| | } |
| |
|