Skip to content

Commit 0ab6e62

Browse files
bindings/go: Handle nil visit functions properly when streaming (#177)
1 parent a5e3a22 commit 0ab6e62

File tree

2 files changed

+90
-43
lines changed

2 files changed

+90
-43
lines changed

bindings/go/scip/parse.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,37 @@ func (pi *IndexVisitor) ParseStreaming(r io.Reader) error {
7979
"expected to read %d bytes based on LEN but read %d bytes", dataLen, numRead)
8080
}
8181
}
82-
if fieldNumber == metadataFieldNumber && pi.VisitMetadata != nil {
83-
m := Metadata{}
84-
if err := proto.Unmarshal(dataBuf, &m); err != nil {
85-
return errors.Wrapf(err, "failed to read %s", indexFieldName(fieldNumber))
82+
if fieldNumber == metadataFieldNumber {
83+
if pi.VisitMetadata != nil {
84+
m := Metadata{}
85+
if err := proto.Unmarshal(dataBuf, &m); err != nil {
86+
return errors.Wrapf(err, "failed to read %s", indexFieldName(fieldNumber))
87+
}
88+
pi.VisitMetadata(&m)
8689
}
87-
pi.VisitMetadata(&m)
88-
} else if fieldNumber == documentsFieldNumber && pi.VisitDocument != nil {
89-
d := Document{}
90-
if err := proto.Unmarshal(dataBuf, &d); err != nil {
91-
return errors.Wrapf(err, "failed to read %s", indexFieldName(fieldNumber))
90+
} else if fieldNumber == documentsFieldNumber {
91+
if pi.VisitDocument != nil {
92+
d := Document{}
93+
if err := proto.Unmarshal(dataBuf, &d); err != nil {
94+
return errors.Wrapf(err, "failed to read %s", indexFieldName(fieldNumber))
95+
}
96+
pi.VisitDocument(&d)
9297
}
93-
pi.VisitDocument(&d)
94-
} else if fieldNumber == externalSymbolsFieldNumber && pi.VisitExternalSymbol != nil {
95-
s := SymbolInformation{}
96-
if err := proto.Unmarshal(dataBuf, &s); err != nil {
97-
return errors.Wrapf(err, "failed to read %s", indexFieldName(fieldNumber))
98+
} else if fieldNumber == externalSymbolsFieldNumber {
99+
if pi.VisitExternalSymbol != nil {
100+
s := SymbolInformation{}
101+
if err := proto.Unmarshal(dataBuf, &s); err != nil {
102+
return errors.Wrapf(err, "failed to read %s", indexFieldName(fieldNumber))
103+
}
104+
pi.VisitExternalSymbol(&s)
98105
}
99-
pi.VisitExternalSymbol(&s)
100106
} else {
101-
return errors.Newf("added new field in scip.Index but forgot to add unmarshaling code")
107+
return errors.Newf(
108+
"added new field with number: %v in scip.Index but missing unmarshaling code", fieldNumber)
102109
}
103110
default:
104-
return errors.Newf("added new field in scip.Index but forgot to update streaming parser")
111+
return errors.Newf(
112+
"added new field with number: %v in scip.Index but forgot to update streaming parser", fieldNumber)
105113
}
106114
}
107115
}

bindings/go/scip/parse_test.go

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scip
33
import (
44
"bytes"
55
"compress/gzip"
6+
"io"
67
"os"
78
"regexp"
89
"testing"
@@ -14,42 +15,23 @@ import (
1415
"google.golang.org/protobuf/proto"
1516
)
1617

18+
func TestEmpty(t *testing.T) {
19+
index := Index{}
20+
checkRoundtrip(t, &index)
21+
}
22+
1723
func TestFuzz(t *testing.T) {
1824
pat := regexp.MustCompile("^(state|sizeCache|unknownFields|SignatureDocumentation)$")
1925
f := fuzz.New().NumElements(0, 2).SkipFieldsWithPattern(pat)
2026
for i := 0; i < 100; i++ {
2127
index := Index{}
2228
f.Fuzz(&index)
2329

24-
indexBytes, err := proto.Marshal(&index)
25-
require.NoError(t, err)
26-
bytesReader := bytes.NewReader(indexBytes)
27-
parsedIndex := Index{}
28-
29-
indexVisitor := IndexVisitor{func(metadata *Metadata) {
30-
parsedIndex.Metadata = metadata
31-
}, func(document *Document) {
32-
parsedIndex.Documents = append(parsedIndex.Documents, document)
33-
}, func(extSym *SymbolInformation) {
34-
parsedIndex.ExternalSymbols = append(parsedIndex.ExternalSymbols, extSym)
35-
}}
36-
37-
if err := indexVisitor.ParseStreaming(bytesReader); err != nil {
38-
t.Fatalf("failed to parse index: %s\ngot error: %v",
39-
protojson.MarshalOptions{Multiline: true}.Format(&index), err)
40-
}
41-
42-
if !proto.Equal(&index, &parsedIndex) {
43-
want := protojson.MarshalOptions{Multiline: true}.Format(&index)
44-
got := protojson.MarshalOptions{Multiline: true}.Format(&parsedIndex)
45-
diff := cmp.Diff(want, got)
46-
require.NotEqual(t, diff, "")
47-
t.Fatalf("index (-want, +got): %s", diff)
48-
}
30+
checkRoundtrip(t, &index)
4931
}
5032
}
5133

52-
func TestLargeDocuments(t *testing.T) {
34+
func getTestIndex(t *testing.T) *gzip.Reader {
5335
// Copied from the Sourcegraph monorepo, which triggered a bug
5436
// where Reader.read() didn't actually fill a buffer completely,
5537
// due to the presence of large documents.
@@ -61,7 +43,63 @@ func TestLargeDocuments(t *testing.T) {
6143
if err != nil {
6244
t.Fatalf("unexpected error unzipping test file: %s", err)
6345
}
46+
return reader
47+
}
48+
49+
func TestLargeDocuments(t *testing.T) {
50+
reader := getTestIndex(t)
51+
_ = parseStreaming(t, reader)
52+
}
53+
54+
func TestDocumentsOnly(t *testing.T) {
55+
pat := regexp.MustCompile("^(state|sizeCache|unknownFields|SignatureDocumentation)$")
56+
f := fuzz.New().NumElements(0, 2).SkipFieldsWithPattern(pat)
57+
for i := 0; i < 100; i++ {
58+
index := Index{}
59+
f.Fuzz(&index)
60+
61+
parsedIndex := Index{}
62+
63+
indexVisitor := IndexVisitor{VisitDocument: func(document *Document) {
64+
parsedIndex.Documents = append(parsedIndex.Documents, document)
65+
}}
66+
67+
indexBytes, err := proto.Marshal(&index)
68+
require.NoError(t, err)
69+
bytesReader := bytes.NewReader(indexBytes)
70+
71+
if err := indexVisitor.ParseStreaming(bytesReader); err != nil {
72+
t.Fatalf("got error parsing index %v", err)
73+
}
74+
75+
onlyDocumentsIndex := Index{}
76+
onlyDocumentsIndex.Documents = index.Documents
77+
78+
checkIndexEqual(t, &onlyDocumentsIndex, &parsedIndex)
79+
}
80+
}
81+
82+
func checkIndexEqual(t *testing.T, expected *Index, got *Index) {
83+
if !proto.Equal(expected, got) {
84+
want := protojson.MarshalOptions{Multiline: true}.Format(expected)
85+
got := protojson.MarshalOptions{Multiline: true}.Format(got)
86+
diff := cmp.Diff(want, got)
87+
require.NotEqual(t, diff, "")
88+
t.Fatalf("index (-want, +got): %s", diff)
89+
}
90+
}
91+
92+
func checkRoundtrip(t *testing.T, index *Index) {
93+
indexBytes, err := proto.Marshal(index)
94+
require.NoError(t, err)
95+
bytesReader := bytes.NewReader(indexBytes)
96+
97+
parsedIndex := parseStreaming(t, bytesReader)
98+
99+
checkIndexEqual(t, index, parsedIndex)
100+
}
64101

102+
func parseStreaming(t *testing.T, reader io.Reader) *Index {
65103
parsedIndex := Index{}
66104

67105
indexVisitor := IndexVisitor{func(metadata *Metadata) {
@@ -75,4 +113,5 @@ func TestLargeDocuments(t *testing.T) {
75113
if err := indexVisitor.ParseStreaming(reader); err != nil {
76114
t.Fatalf("got error parsing index %v", err)
77115
}
116+
return &parsedIndex
78117
}

0 commit comments

Comments
 (0)