github.com/syumai/protoreflect@v1.7.1-0.20200810020253-2ac7e3b3a321/desc/protoparse/validate_test.go (about)

     1  package protoparse
     2  
     3  import (
     4  	"github.com/syumai/protoreflect/internal/testutil"
     5  	"strings"
     6  	"testing"
     7  )
     8  
     9  func TestBasicValidation(t *testing.T) {
    10  	testCases := []struct {
    11  		contents string
    12  		succeeds bool
    13  		errMsg   string
    14  	}{
    15  		{
    16  			contents: `message Foo { optional double bar = 1 [default = -18446744073709551615]; }`,
    17  			succeeds: true,
    18  		},
    19  		{
    20  			// with byte order marker
    21  			contents: string([]byte{0xEF, 0xBB, 0xBF}) + `message Foo { optional double bar = 1 [default = -18446744073709551615]; }`,
    22  			succeeds: true,
    23  		},
    24  		{
    25  			contents: `message Foo { optional double bar = 1 [default = 18446744073709551616]; }`,
    26  			succeeds: true,
    27  		},
    28  		{
    29  			contents: `message Foo { optional double bar = 536870912; option message_set_wire_format = true; }`,
    30  			succeeds: true,
    31  		},
    32  		{
    33  			contents: `message Foo { oneof bar { group Baz = 1 [deprecated=true] { optional int abc = 1; } } }`,
    34  			succeeds: true,
    35  		},
    36  		{
    37  			contents: `syntax = "proto1";`,
    38  			errMsg:   `test.proto:1:10: syntax value must be "proto2" or "proto3"`,
    39  		},
    40  		{
    41  			contents: `message Foo { optional string s = 5000000000; }`,
    42  			errMsg:   `test.proto:1:35: tag number 5000000000 is higher than max allowed tag number (536870911)`,
    43  		},
    44  		{
    45  			contents: `message Foo { optional string s = 19500; }`,
    46  			errMsg:   `test.proto:1:35: tag number 19500 is in disallowed reserved range 19000-19999`,
    47  		},
    48  		{
    49  			contents: `enum Foo { V = 5000000000; }`,
    50  			errMsg:   `test.proto:1:16: value 5000000000 is out of range: should be between -2147483648 and 2147483647`,
    51  		},
    52  		{
    53  			contents: `enum Foo { V = -5000000000; }`,
    54  			errMsg:   `test.proto:1:16: value -5000000000 is out of range: should be between -2147483648 and 2147483647`,
    55  		},
    56  		{
    57  			contents: `enum Foo { V = 0; reserved 5000000000; }`,
    58  			errMsg:   `test.proto:1:28: range start 5000000000 is out of range: should be between -2147483648 and 2147483647`,
    59  		},
    60  		{
    61  			contents: `enum Foo { V = 0; reserved -5000000000; }`,
    62  			errMsg:   `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`,
    63  		},
    64  		{
    65  			contents: `enum Foo { V = 0; reserved 5000000000 to 5000000001; }`,
    66  			errMsg:   `test.proto:1:28: range start 5000000000 is out of range: should be between -2147483648 and 2147483647`,
    67  		},
    68  		{
    69  			contents: `enum Foo { V = 0; reserved 5 to 5000000000; }`,
    70  			errMsg:   `test.proto:1:33: range end 5000000000 is out of range: should be between -2147483648 and 2147483647`,
    71  		},
    72  		{
    73  			contents: `enum Foo { V = 0; reserved -5000000000 to -5; }`,
    74  			errMsg:   `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`,
    75  		},
    76  		{
    77  			contents: `enum Foo { V = 0; reserved -5000000001 to -5000000000; }`,
    78  			errMsg:   `test.proto:1:28: range start -5000000001 is out of range: should be between -2147483648 and 2147483647`,
    79  		},
    80  		{
    81  			contents: `enum Foo { V = 0; reserved -5000000000 to 5; }`,
    82  			errMsg:   `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`,
    83  		},
    84  		{
    85  			contents: `enum Foo { V = 0; reserved -5 to 5000000000; }`,
    86  			errMsg:   `test.proto:1:34: range end 5000000000 is out of range: should be between -2147483648 and 2147483647`,
    87  		},
    88  		{
    89  			contents: `enum Foo { }`,
    90  			errMsg:   `test.proto:1:1: enum Foo: enums must define at least one value`,
    91  		},
    92  		{
    93  			contents: `message Foo { oneof Bar { } }`,
    94  			errMsg:   `test.proto:1:15: oneof must contain at least one field`,
    95  		},
    96  		{
    97  			contents: `message Foo { extensions 1 to max; } extend Foo { }`,
    98  			errMsg:   `test.proto:1:38: extend sections must define at least one extension`,
    99  		},
   100  		{
   101  			contents: `message Foo { option map_entry = true; }`,
   102  			errMsg:   `test.proto:1:34: message Foo: map_entry option should not be set explicitly; use map type instead`,
   103  		},
   104  		{
   105  			contents: `message Foo { option map_entry = false; }`,
   106  			succeeds: true, // okay if explicit setting is false
   107  		},
   108  		{
   109  			contents: `syntax = "proto2"; message Foo { string s = 1; }`,
   110  			errMsg:   `test.proto:1:41: field Foo.s: field has no label; proto2 requires explicit 'optional' label`,
   111  		},
   112  		{
   113  			contents: `message Foo { string s = 1; }`, // syntax defaults to proto2
   114  			errMsg:   `test.proto:1:22: field Foo.s: field has no label; proto2 requires explicit 'optional' label`,
   115  		},
   116  		{
   117  			contents: `syntax = "proto3"; message Foo { optional string s = 1; }`,
   118  			succeeds: true, // proto3_optional
   119  		},
   120  		{
   121  			contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; extend google.protobuf.MessageOptions { optional string s = 50000; }`,
   122  			errMsg:   `test.proto:1:103: field s: label 'optional' is not allowed on extensions in proto3`,
   123  		},
   124  		{
   125  			contents: `syntax = "proto3"; message Foo { required string s = 1; }`,
   126  			errMsg:   `test.proto:1:34: field Foo.s: label 'required' is not allowed in proto3`,
   127  		},
   128  		{
   129  			contents: `message Foo { extensions 1 to max; } extend Foo { required string sss = 100; }`,
   130  			errMsg:   `test.proto:1:51: field sss: extension fields cannot be 'required'`,
   131  		},
   132  		{
   133  			contents: `syntax = "proto3"; message Foo { optional group Grp = 1 { } }`,
   134  			errMsg:   `test.proto:1:43: field Foo.grp: groups are not allowed in proto3`,
   135  		},
   136  		{
   137  			contents: `syntax = "proto3"; message Foo { extensions 1 to max; }`,
   138  			errMsg:   `test.proto:1:45: message Foo: extension ranges are not allowed in proto3`,
   139  		},
   140  		{
   141  			contents: `syntax = "proto3"; message Foo { string s = 1 [default = "abcdef"]; }`,
   142  			errMsg:   `test.proto:1:48: field Foo.s: default values are not allowed in proto3`,
   143  		},
   144  		{
   145  			contents: `enum Foo { V1 = 1; V2 = 1; }`,
   146  			errMsg:   `test.proto:1:25: enum Foo: values V1 and V2 both have the same numeric value 1; use allow_alias option if intentional`,
   147  		},
   148  		{
   149  			contents: `enum Foo { option allow_alias = true; V1 = 1; V2 = 1; }`,
   150  			succeeds: true,
   151  		},
   152  		{
   153  			contents: `syntax = "proto3"; enum Foo { V1 = 0; reserved 1 to 20; reserved "V2"; }`,
   154  			succeeds: true,
   155  		},
   156  		{
   157  			contents: `enum Foo { V1 = 1; reserved 1 to 20; reserved "V2"; }`,
   158  			errMsg:   `test.proto:1:17: enum Foo: value V1 is using number 1 which is in reserved range 1 to 20`,
   159  		},
   160  		{
   161  			contents: `enum Foo { V1 = 20; reserved 1 to 20; reserved "V2"; }`,
   162  			errMsg:   `test.proto:1:17: enum Foo: value V1 is using number 20 which is in reserved range 1 to 20`,
   163  		},
   164  		{
   165  			contents: `enum Foo { V2 = 0; reserved 1 to 20; reserved "V2"; }`,
   166  			errMsg:   `test.proto:1:12: enum Foo: value V2 is using a reserved name`,
   167  		},
   168  		{
   169  			contents: `enum Foo { V0 = 0; reserved 1 to 20; reserved 21 to 40; reserved "V2"; }`,
   170  			succeeds: true,
   171  		},
   172  		{
   173  			contents: `enum Foo { V0 = 0; reserved 1 to 20; reserved 20 to 40; reserved "V2"; }`,
   174  			errMsg:   `test.proto:1:47: enum Foo: reserved ranges overlap: 1 to 20 and 20 to 40`,
   175  		},
   176  		{
   177  			contents: `syntax = "proto3"; enum Foo { FIRST = 1; }`,
   178  			errMsg:   `test.proto:1:39: enum Foo: proto3 requires that first value in enum have numeric value of 0`,
   179  		},
   180  		{
   181  			contents: `syntax = "proto3"; message Foo { string s = 1; int32 i = 1; }`,
   182  			errMsg:   `test.proto:1:58: message Foo: fields s and i both have the same tag 1`,
   183  		},
   184  		{
   185  			contents: `message Foo { reserved 1 to 10, 10 to 12; }`,
   186  			errMsg:   `test.proto:1:33: message Foo: reserved ranges overlap: 1 to 10 and 10 to 12`,
   187  		},
   188  		{
   189  			contents: `message Foo { extensions 1 to 10, 10 to 12; }`,
   190  			errMsg:   `test.proto:1:35: message Foo: extension ranges overlap: 1 to 10 and 10 to 12`,
   191  		},
   192  		{
   193  			contents: `message Foo { reserved 1 to 10; extensions 10 to 12; }`,
   194  			errMsg:   `test.proto:1:44: message Foo: extension range 10 to 12 overlaps reserved range 1 to 10`,
   195  		},
   196  		{
   197  			contents: `message Foo { reserved 1, 2 to 10, 11 to 20; extensions 21 to 22; }`,
   198  			succeeds: true,
   199  		},
   200  		{
   201  			contents: `message Foo { reserved 10 to 1; }`,
   202  			errMsg:   `test.proto:1:24: range, 10 to 1, is invalid: start must be <= end`,
   203  		},
   204  		{
   205  			contents: `message Foo { extensions 10 to 1; }`,
   206  			errMsg:   `test.proto:1:26: range, 10 to 1, is invalid: start must be <= end`,
   207  		},
   208  		{
   209  			contents: `message Foo { reserved 1 to 5000000000; }`,
   210  			errMsg:   `test.proto:1:29: range end 5000000000 is out of range: should be between 0 and 536870911`,
   211  		},
   212  		{
   213  			contents: `message Foo { extensions 3000000000; }`,
   214  			errMsg:   `test.proto:1:26: range start 3000000000 is out of range: should be between 0 and 536870911`,
   215  		},
   216  		{
   217  			contents: `message Foo { extensions 3000000000 to 3000000001; }`,
   218  			errMsg:   `test.proto:1:26: range start 3000000000 is out of range: should be between 0 and 536870911`,
   219  		},
   220  		{
   221  			contents: `message Foo { extensions 100 to 3000000000; }`,
   222  			errMsg:   `test.proto:1:33: range end 3000000000 is out of range: should be between 0 and 536870911`,
   223  		},
   224  		{
   225  			contents: `message Foo { reserved "foo", "foo"; }`,
   226  			errMsg:   `test.proto:1:31: name "foo" is reserved multiple times`,
   227  		},
   228  		{
   229  			contents: `message Foo { reserved "foo"; reserved "foo"; }`,
   230  			errMsg:   `test.proto:1:40: name "foo" is reserved multiple times`,
   231  		},
   232  		{
   233  			contents: `message Foo { reserved "foo"; optional string foo = 1; }`,
   234  			errMsg:   `test.proto:1:47: message Foo: field foo is using a reserved name`,
   235  		},
   236  		{
   237  			contents: `message Foo { reserved 1 to 10; optional string foo = 1; }`,
   238  			errMsg:   `test.proto:1:55: message Foo: field foo is using tag 1 which is in reserved range 1 to 10`,
   239  		},
   240  		{
   241  			contents: `message Foo { extensions 1 to 10; optional string foo = 1; }`,
   242  			errMsg:   `test.proto:1:57: message Foo: field foo is using tag 1 which is in extension range 1 to 10`,
   243  		},
   244  		{
   245  			contents: `message Foo { optional group foo = 1 { } }`,
   246  			errMsg:   `test.proto:1:30: group foo should have a name that starts with a capital letter`,
   247  		},
   248  		{
   249  			contents: `message Foo { oneof foo { group bar = 1 { } } }`,
   250  			errMsg:   `test.proto:1:33: group bar should have a name that starts with a capital letter`,
   251  		},
   252  		{
   253  			contents: ``,
   254  			succeeds: true,
   255  		},
   256  		{
   257  			contents: `0`,
   258  			errMsg:   `test.proto:1:1: syntax error: unexpected int literal`,
   259  		},
   260  		{
   261  			contents: `foobar`,
   262  			errMsg:   `test.proto:1:1: syntax error: unexpected identifier`,
   263  		},
   264  		{
   265  			contents: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`,
   266  			errMsg:   `test.proto:1:1: syntax error: unexpected identifier`,
   267  		},
   268  		{
   269  			contents: `"abc"`,
   270  			errMsg:   `test.proto:1:1: syntax error: unexpected string literal`,
   271  		},
   272  		{
   273  			contents: `0.0.0.0.0`,
   274  			errMsg:   `test.proto:1:1: syntax error: unexpected float literal`,
   275  		},
   276  	}
   277  
   278  	for i, tc := range testCases {
   279  		errs := newErrorHandler(nil, nil)
   280  		_ = parseProto("test.proto", strings.NewReader(tc.contents), errs, true)
   281  		err := errs.getError()
   282  		if tc.succeeds {
   283  			testutil.Ok(t, err, "case #%d should succeed", i)
   284  		} else {
   285  			testutil.Nok(t, err, "case #%d should fail", i)
   286  			testutil.Eq(t, tc.errMsg, err.Error(), "case #%d bad error message", i)
   287  		}
   288  	}
   289  }