| |
| |
| |
|
|
| |
|
|
| 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") |
| } |
| } |
| } |
|
|