blob: b82078eb579781d2bda0be1835edd80cf4849f16 [file] [log] [blame]
package protodesc
import (
"strings"
"testing"
"github.com/golang/protobuf/v2/internal/scalar"
"github.com/golang/protobuf/v2/reflect/protoregistry"
descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
)
// Tests validation logic for malformed descriptors.
func TestNewFile_ValidationErrors(t *testing.T) {
testCases := []struct {
name string
deps []*descriptorpb.FileDescriptorProto
fd *descriptorpb.FileDescriptorProto
wantErr string
}{{
name: "field number reserved",
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("field-number-reserved.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("BadMessage"),
ReservedRange: []*descriptorpb.DescriptorProto_ReservedRange{{
Start: scalar.Int32(3),
End: scalar.Int32(4),
}},
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("good_field"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
}, {
Name: scalar.String("bad_field"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
}},
}},
},
wantErr: "reserved number 3",
}, {
name: "field name reserved",
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("field-name-reserved.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("BadMessage"),
ReservedName: []string{"bad_field", "baz"},
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("good_field"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
}, {
Name: scalar.String("bad_field"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
}},
}},
},
wantErr: `reserved name "bad_field"`,
}, {
name: "normal field with extendee",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("extensible.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("ExtensibleMessage"),
ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
Start: scalar.Int32(1000),
End: scalar.Int32(2000),
}},
}},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("field-with-extendee.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
Dependency: []string{"extensible.proto"},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("BadMessage"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("good_field"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
}, {
Name: scalar.String("bad_field"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
Extendee: scalar.String(".foo.ExtensibleMessage"),
}},
}},
},
wantErr: "may not have extendee",
}, {
name: "type_name on int32 field",
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("int32-with-type-name.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("AnEnum"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("BadMessage"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("good_field"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
}, {
Name: scalar.String("bad_field"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
TypeName: scalar.String("AnEnum"),
}},
}},
},
wantErr: "type_name",
}, {
name: "type_name on string extension",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("extensible.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("ExtensibleMessage"),
ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
Start: scalar.Int32(1000),
End: scalar.Int32(2000),
}},
}},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("string-ext-with-type-name.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
Dependency: []string{"extensible.proto"},
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("AnEnum"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
Extension: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("my_ext"),
Number: scalar.Int32(1000),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
Extendee: scalar.String(".foo.ExtensibleMessage"),
TypeName: scalar.String("AnEnum"),
}},
},
wantErr: "type_name",
}, {
name: "oneof_index on extension",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("extensible.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("ExtensibleMessage"),
ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
Start: scalar.Int32(1000),
End: scalar.Int32(2000),
}},
}},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("ext-with-oneof-index.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
Dependency: []string{"extensible.proto"},
Extension: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("my_ext"),
Number: scalar.Int32(1000),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
Extendee: scalar.String(".foo.ExtensibleMessage"),
OneofIndex: scalar.Int32(0),
}},
},
wantErr: "oneof_index",
}, {
name: "enum with reserved number",
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("enum-with-reserved-number.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("AnEnum"),
ReservedRange: []*descriptorpb.EnumDescriptorProto_EnumReservedRange{{
Start: scalar.Int32(5),
End: scalar.Int32(6),
}, {
Start: scalar.Int32(10),
End: scalar.Int32(12),
}},
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}, {
Name: scalar.String("FOO"),
Number: scalar.Int32(1),
}, {
Name: scalar.String("BAR"),
Number: scalar.Int32(2),
}, {
Name: scalar.String("BAD"),
Number: scalar.Int32(11),
}},
}},
},
wantErr: "reserved number 11",
}, {
name: "enum with reserved number",
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("enum-with-reserved-name.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("ParentMessage"),
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("AnEnum"),
ReservedName: []string{"ABC", "XYZ"},
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}, {
Name: scalar.String("FOO"),
Number: scalar.Int32(1),
}, {
Name: scalar.String("BAR"),
Number: scalar.Int32(2),
}, {
Name: scalar.String("XYZ"),
Number: scalar.Int32(3),
}},
}},
}},
},
wantErr: `reserved name "XYZ"`,
}, {
name: "message dependency without import",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("foo.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Foo"),
}},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("message-dependency-without-import.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Bar"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("id"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
}, {
Name: scalar.String("foo"),
Number: scalar.Int32(2),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: scalar.String(".foo.Foo"),
}},
}},
},
wantErr: "foo.Foo without import of foo.proto",
}, {
name: "enum dependency without import",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("foo.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("Foo"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("enum-dependency-without-import.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Bar"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("id"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
}, {
Name: scalar.String("foo"),
Number: scalar.Int32(2),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
TypeName: scalar.String(".foo.Foo"),
}},
}},
},
wantErr: "foo.Foo without import of foo.proto",
}, {
name: "message dependency on without import on file imported by a public import",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("foo.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Foo"),
}},
}, {
Name: scalar.String("baz.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
Dependency: []string{"foo.proto"},
}, {
Name: scalar.String("old-baz.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
Dependency: []string{"baz.proto"},
PublicDependency: []int32{0},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("message-dependency-without-import.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
Dependency: []string{"old-baz.proto"},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Bar"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("id"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
}, {
Name: scalar.String("foo"),
Number: scalar.Int32(2),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: scalar.String(".foo.Foo"),
}},
}},
},
wantErr: "foo.Foo without import of foo.proto",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := new(protoregistry.Files)
for _, dep := range tc.deps {
f, err := NewFile(dep, r)
if err != nil {
t.Fatalf("Error creating dependency: %v", err)
}
if err := r.Register(f); err != nil {
t.Fatalf("Error adding dependency: %v", err)
}
}
if _, err := NewFile(tc.fd, r); err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Errorf("NewFile: got err = %v; want error containing %q", err, tc.wantErr)
}
})
}
}
// Sanity checks for well-formed descriptors. Most behavior with well-formed descriptors is covered
// by other tests that rely on generated descriptors.
func TestNewFile_ValidationOK(t *testing.T) {
testCases := []struct {
name string
deps []*descriptorpb.FileDescriptorProto
fd *descriptorpb.FileDescriptorProto
}{{
name: "self contained file",
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("self-contained.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("TopLevelEnum"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("TopLevelMessage"),
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("NestedEnum"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
NestedType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("NestedMessage"),
}},
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("id"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
}, {
Name: scalar.String("top_level_enum"),
Number: scalar.Int32(2),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
TypeName: scalar.String(".foo.TopLevelEnum"),
}, {
Name: scalar.String("nested_enum"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
TypeName: scalar.String(".foo.TopLevelMessage.NestedEnum"),
}, {
Name: scalar.String("nested_message"),
Number: scalar.Int32(4),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
TypeName: scalar.String(".foo.TopLevelMessage.NestedMessage"),
}},
}},
},
}, {
name: "external types with explicit import",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("foo.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("FooMessage"),
}},
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("BarEnum"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("external-types-with-explicit-import.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
Dependency: []string{"foo.proto"},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Bar"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("id"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
}, {
Name: scalar.String("foo"),
Number: scalar.Int32(2),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: scalar.String(".foo.FooMessage"),
}, {
Name: scalar.String("bar"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
TypeName: scalar.String(".foo.BarEnum"),
}},
}},
},
}, {
name: "external types with transitive public imports",
deps: []*descriptorpb.FileDescriptorProto{{
Name: scalar.String("quux.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("QuuxMessage"),
}},
}, {
Name: scalar.String("foo.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
Dependency: []string{"quux.proto"},
PublicDependency: []int32{0},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("FooMessage"),
}},
EnumType: []*descriptorpb.EnumDescriptorProto{{
Name: scalar.String("BarEnum"),
Value: []*descriptorpb.EnumValueDescriptorProto{{
Name: scalar.String("UNKNOWN"),
Number: scalar.Int32(0),
}},
}},
}, {
Name: scalar.String("old-name.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("foo"),
Dependency: []string{"foo.proto"},
PublicDependency: []int32{0},
}},
fd: &descriptorpb.FileDescriptorProto{
Name: scalar.String("external-types-with-public-import.proto"),
Syntax: scalar.String("proto2"),
Package: scalar.String("bar"),
Dependency: []string{"old-name.proto"},
MessageType: []*descriptorpb.DescriptorProto{{
Name: scalar.String("Bar"),
Field: []*descriptorpb.FieldDescriptorProto{{
Name: scalar.String("id"),
Number: scalar.Int32(1),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
}, {
Name: scalar.String("foo"),
Number: scalar.Int32(2),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: scalar.String(".foo.FooMessage"),
}, {
Name: scalar.String("bar"),
Number: scalar.Int32(3),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
TypeName: scalar.String(".foo.BarEnum"),
}, {
Name: scalar.String("quux"),
Number: scalar.Int32(4),
Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
TypeName: scalar.String(".foo.QuuxMessage"),
}},
}},
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := new(protoregistry.Files)
for _, dep := range tc.deps {
f, err := NewFile(dep, r)
if err != nil {
t.Fatalf("Error creating dependency: %v", err)
}
if err := r.Register(f); err != nil {
t.Fatalf("Error adding dependency: %v", err)
}
}
if _, err := NewFile(tc.fd, r); err != nil {
t.Errorf("NewFile: got err = %v", err)
}
})
}
}