github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/parser/validate_test.go (about) 1 package parser 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 9 "github.com/jhump/protocompile/reporter" 10 ) 11 12 func TestBasicValidation(t *testing.T) { 13 testCases := []struct { 14 contents string 15 succeeds bool 16 errMsg string 17 }{ 18 { 19 contents: `message Foo { optional double bar = 1 [default = -18446744073709551615]; }`, 20 succeeds: true, 21 }, 22 { 23 // with byte order marker 24 contents: string([]byte{0xEF, 0xBB, 0xBF}) + `message Foo { optional double bar = 1 [default = -18446744073709551615]; }`, 25 succeeds: true, 26 }, 27 { 28 contents: `message Foo { optional double bar = 1 [default = 18446744073709551616]; }`, 29 succeeds: true, 30 }, 31 { 32 contents: `message Foo { extensions 100 to max; option message_set_wire_format = true; } message Bar { } extend Foo { optional Bar bar = 536870912; }`, 33 succeeds: true, 34 }, 35 { 36 contents: `message Foo { option message_set_wire_format = true; extensions 1 to 100; }`, 37 succeeds: true, 38 }, 39 { 40 contents: `message Foo { optional double bar = 536870912; option message_set_wire_format = true; }`, 41 errMsg: "test.proto:1:15: messages with message-set wire format cannot contain non-extension fields", 42 }, 43 { 44 contents: `message Foo { option message_set_wire_format = true; }`, 45 errMsg: "test.proto:1:15: messages with message-set wire format must contain at least one extension range", 46 }, 47 { 48 contents: `message Foo { oneof bar { group Baz = 1 [deprecated=true] { optional int32 abc = 1; } } }`, 49 succeeds: true, 50 }, 51 { 52 contents: `syntax = "proto1";`, 53 errMsg: `test.proto:1:10: syntax value must be "proto2" or "proto3"`, 54 }, 55 { 56 contents: `message Foo { optional string s = 5000000000; }`, 57 errMsg: `test.proto:1:35: tag number 5000000000 is higher than max allowed tag number (536870911)`, 58 }, 59 { 60 contents: `message Foo { optional string s = 19500; }`, 61 errMsg: `test.proto:1:35: tag number 19500 is in disallowed reserved range 19000-19999`, 62 }, 63 { 64 contents: `enum Foo { V = 5000000000; }`, 65 errMsg: `test.proto:1:16: value 5000000000 is out of range: should be between -2147483648 and 2147483647`, 66 }, 67 { 68 contents: `enum Foo { V = -5000000000; }`, 69 errMsg: `test.proto:1:16: value -5000000000 is out of range: should be between -2147483648 and 2147483647`, 70 }, 71 { 72 contents: `enum Foo { V = 0; reserved 5000000000; }`, 73 errMsg: `test.proto:1:28: range start 5000000000 is out of range: should be between -2147483648 and 2147483647`, 74 }, 75 { 76 contents: `enum Foo { V = 0; reserved -5000000000; }`, 77 errMsg: `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`, 78 }, 79 { 80 contents: `enum Foo { V = 0; reserved 5000000000 to 5000000001; }`, 81 errMsg: `test.proto:1:28: range start 5000000000 is out of range: should be between -2147483648 and 2147483647`, 82 }, 83 { 84 contents: `enum Foo { V = 0; reserved 5 to 5000000000; }`, 85 errMsg: `test.proto:1:33: range end 5000000000 is out of range: should be between -2147483648 and 2147483647`, 86 }, 87 { 88 contents: `enum Foo { V = 0; reserved -5000000000 to -5; }`, 89 errMsg: `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`, 90 }, 91 { 92 contents: `enum Foo { V = 0; reserved -5000000001 to -5000000000; }`, 93 errMsg: `test.proto:1:28: range start -5000000001 is out of range: should be between -2147483648 and 2147483647`, 94 }, 95 { 96 contents: `enum Foo { V = 0; reserved -5000000000 to 5; }`, 97 errMsg: `test.proto:1:28: range start -5000000000 is out of range: should be between -2147483648 and 2147483647`, 98 }, 99 { 100 contents: `enum Foo { V = 0; reserved -5 to 5000000000; }`, 101 errMsg: `test.proto:1:34: range end 5000000000 is out of range: should be between -2147483648 and 2147483647`, 102 }, 103 { 104 contents: `enum Foo { }`, 105 errMsg: `test.proto:1:1: enum Foo: enums must define at least one value`, 106 }, 107 { 108 contents: `message Foo { oneof Bar { } }`, 109 errMsg: `test.proto:1:15: oneof must contain at least one field`, 110 }, 111 { 112 contents: `message Foo { extensions 1 to max; } extend Foo { }`, 113 errMsg: `test.proto:1:38: extend sections must define at least one extension`, 114 }, 115 { 116 contents: `message Foo { option map_entry = true; }`, 117 errMsg: `test.proto:1:34: message Foo: map_entry option should not be set explicitly; use map type instead`, 118 }, 119 { 120 contents: `message Foo { option map_entry = false; }`, 121 succeeds: true, // okay if explicit setting is false 122 }, 123 { 124 contents: `syntax = "proto2"; message Foo { string s = 1; }`, 125 errMsg: `test.proto:1:41: field Foo.s: field has no label; proto2 requires explicit 'optional' label`, 126 }, 127 { 128 contents: `message Foo { string s = 1; }`, // syntax defaults to proto2 129 errMsg: `test.proto:1:22: field Foo.s: field has no label; proto2 requires explicit 'optional' label`, 130 }, 131 { 132 contents: `syntax = "proto3"; message Foo { optional string s = 1; }`, 133 succeeds: true, // proto3_optional 134 }, 135 { 136 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; extend google.protobuf.MessageOptions { optional string s = 50000; }`, 137 succeeds: true, // proto3_optional for extensions 138 }, 139 { 140 contents: `syntax = "proto3"; message Foo { required string s = 1; }`, 141 errMsg: `test.proto:1:34: field Foo.s: label 'required' is not allowed in proto3`, 142 }, 143 { 144 contents: `message Foo { extensions 1 to max; } extend Foo { required string sss = 100; }`, 145 errMsg: `test.proto:1:51: field sss: extension fields cannot be 'required'`, 146 }, 147 { 148 contents: `syntax = "proto3"; message Foo { optional group Grp = 1 { } }`, 149 errMsg: `test.proto:1:43: field Foo.grp: groups are not allowed in proto3`, 150 }, 151 { 152 contents: `syntax = "proto3"; message Foo { extensions 1 to max; }`, 153 errMsg: `test.proto:1:45: message Foo: extension ranges are not allowed in proto3`, 154 }, 155 { 156 contents: `syntax = "proto3"; message Foo { string s = 1 [default = "abcdef"]; }`, 157 errMsg: `test.proto:1:48: field Foo.s: default values are not allowed in proto3`, 158 }, 159 { 160 contents: `enum Foo { V1 = 1; V2 = 1; }`, 161 errMsg: `test.proto:1:25: enum Foo: values V1 and V2 both have the same numeric value 1; use allow_alias option if intentional`, 162 }, 163 { 164 contents: `enum Foo { option allow_alias = true; V1 = 1; V2 = 1; }`, 165 succeeds: true, 166 }, 167 { 168 contents: `enum Foo { option allow_alias = false; V1 = 1; V2 = 2; }`, 169 succeeds: true, 170 }, 171 { 172 contents: `enum Foo { option allow_alias = true; V1 = 1; V2 = 2; }`, 173 errMsg: `test.proto:1:33: enum Foo: allow_alias is true but no values are aliases`, 174 }, 175 { 176 contents: `syntax = "proto3"; enum Foo { V1 = 0; reserved 1 to 20; reserved "V2"; }`, 177 succeeds: true, 178 }, 179 { 180 contents: `enum Foo { V1 = 1; reserved 1 to 20; reserved "V2"; }`, 181 errMsg: `test.proto:1:17: enum Foo: value V1 is using number 1 which is in reserved range 1 to 20`, 182 }, 183 { 184 contents: `enum Foo { V1 = 20; reserved 1 to 20; reserved "V2"; }`, 185 errMsg: `test.proto:1:17: enum Foo: value V1 is using number 20 which is in reserved range 1 to 20`, 186 }, 187 { 188 contents: `enum Foo { V2 = 0; reserved 1 to 20; reserved "V2"; }`, 189 errMsg: `test.proto:1:12: enum Foo: value V2 is using a reserved name`, 190 }, 191 { 192 contents: `enum Foo { V0 = 0; reserved 1 to 20; reserved 21 to 40; reserved "V2"; }`, 193 succeeds: true, 194 }, 195 { 196 contents: `enum Foo { V0 = 0; reserved 1 to 20; reserved 20 to 40; reserved "V2"; }`, 197 errMsg: `test.proto:1:47: enum Foo: reserved ranges overlap: 1 to 20 and 20 to 40`, 198 }, 199 { 200 contents: `syntax = "proto3"; enum Foo { FIRST = 1; }`, 201 errMsg: `test.proto:1:39: enum Foo: proto3 requires that first value in enum have numeric value of 0`, 202 }, 203 { 204 contents: `syntax = "proto3"; message Foo { string s = 1; int32 i = 1; }`, 205 errMsg: `test.proto:1:58: message Foo: fields s and i both have the same tag 1`, 206 }, 207 { 208 contents: `message Foo { reserved 1 to 10, 10 to 12; }`, 209 errMsg: `test.proto:1:33: message Foo: reserved ranges overlap: 1 to 10 and 10 to 12`, 210 }, 211 { 212 contents: `message Foo { extensions 1 to 10, 10 to 12; }`, 213 errMsg: `test.proto:1:35: message Foo: extension ranges overlap: 1 to 10 and 10 to 12`, 214 }, 215 { 216 contents: `message Foo { reserved 1 to 10; extensions 10 to 12; }`, 217 errMsg: `test.proto:1:44: message Foo: extension range 10 to 12 overlaps reserved range 1 to 10`, 218 }, 219 { 220 contents: `message Foo { reserved 1, 2 to 10, 11 to 20; extensions 21 to 22; }`, 221 succeeds: true, 222 }, 223 { 224 contents: `message Foo { reserved 10 to 1; }`, 225 errMsg: `test.proto:1:24: range, 10 to 1, is invalid: start must be <= end`, 226 }, 227 { 228 contents: `message Foo { extensions 10 to 1; }`, 229 errMsg: `test.proto:1:26: range, 10 to 1, is invalid: start must be <= end`, 230 }, 231 { 232 contents: `message Foo { reserved 1 to 5000000000; }`, 233 errMsg: `test.proto:1:29: range end 5000000000 is out of range: should be between 1 and 536870911`, 234 }, 235 { 236 contents: `message Foo { reserved 0 to 10; }`, 237 errMsg: `test.proto:1:24: range start 0 is out of range: should be between 1 and 536870911`, 238 }, 239 { 240 contents: `message Foo { extensions 3000000000; }`, 241 errMsg: `test.proto:1:26: range start 3000000000 is out of range: should be between 1 and 536870911`, 242 }, 243 { 244 contents: `message Foo { extensions 3000000000 to 3000000001; }`, 245 errMsg: `test.proto:1:26: range start 3000000000 is out of range: should be between 1 and 536870911`, 246 }, 247 { 248 contents: `message Foo { extensions 0 to 10; }`, 249 errMsg: `test.proto:1:26: range start 0 is out of range: should be between 1 and 536870911`, 250 }, 251 { 252 contents: `message Foo { extensions 100 to 3000000000; }`, 253 errMsg: `test.proto:1:33: range end 3000000000 is out of range: should be between 1 and 536870911`, 254 }, 255 { 256 contents: `message Foo { reserved "foo", "foo"; }`, 257 errMsg: `test.proto:1:31: name "foo" is reserved multiple times`, 258 }, 259 { 260 contents: `message Foo { reserved "foo"; reserved "foo"; }`, 261 errMsg: `test.proto:1:40: name "foo" is reserved multiple times`, 262 }, 263 { 264 contents: `message Foo { reserved "foo"; optional string foo = 1; }`, 265 errMsg: `test.proto:1:47: message Foo: field foo is using a reserved name`, 266 }, 267 { 268 contents: `message Foo { reserved 1 to 10; optional string foo = 1; }`, 269 errMsg: `test.proto:1:55: message Foo: field foo is using tag 1 which is in reserved range 1 to 10`, 270 }, 271 { 272 contents: `message Foo { extensions 1 to 10; optional string foo = 1; }`, 273 errMsg: `test.proto:1:57: message Foo: field foo is using tag 1 which is in extension range 1 to 10`, 274 }, 275 { 276 contents: `message Foo { optional group foo = 1 { } }`, 277 errMsg: `test.proto:1:30: group foo should have a name that starts with a capital letter`, 278 }, 279 { 280 contents: `message Foo { oneof foo { group bar = 1 { } } }`, 281 errMsg: `test.proto:1:33: group bar should have a name that starts with a capital letter`, 282 }, 283 { 284 contents: `enum Foo { option = 1; }`, 285 errMsg: `test.proto:1:19: syntax error: unexpected '='`, 286 }, 287 { 288 contents: `enum Foo { reserved = 1; }`, 289 errMsg: `test.proto:1:21: syntax error: unexpected '=', expecting string literal or int literal or '-'`, 290 }, 291 { 292 contents: `syntax = "proto3"; enum message { unset = 0; } message Foo { message bar = 1; }`, 293 errMsg: `test.proto:1:74: syntax error: unexpected '=', expecting '{'`, 294 }, 295 { 296 contents: `syntax = "proto3"; enum enum { unset = 0; } message Foo { enum bar = 1; }`, 297 errMsg: `test.proto:1:68: syntax error: unexpected '=', expecting '{'`, 298 }, 299 { 300 contents: `syntax = "proto3"; enum reserved { unset = 0; } message Foo { reserved bar = 1; }`, 301 errMsg: `test.proto:1:72: syntax error: unexpected identifier, expecting string literal or int literal`, 302 }, 303 { 304 contents: `syntax = "proto3"; enum extend { unset = 0; } message Foo { extend bar = 1; }`, 305 errMsg: `test.proto:1:72: syntax error: unexpected '=', expecting '{'`, 306 }, 307 { 308 contents: `syntax = "proto3"; enum oneof { unset = 0; } message Foo { oneof bar = 1; }`, 309 errMsg: `test.proto:1:70: syntax error: unexpected '=', expecting '{'`, 310 }, 311 { 312 contents: `syntax = "proto3"; enum optional { unset = 0; } message Foo { optional bar = 1; }`, 313 errMsg: `test.proto:1:76: syntax error: unexpected '='`, 314 }, 315 { 316 contents: `syntax = "proto3"; enum repeated { unset = 0; } message Foo { repeated bar = 1; }`, 317 errMsg: `test.proto:1:76: syntax error: unexpected '='`, 318 }, 319 { 320 contents: `syntax = "proto3"; enum required { unset = 0; } message Foo { required bar = 1; }`, 321 errMsg: `test.proto:1:76: syntax error: unexpected '='`, 322 }, 323 { 324 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; enum optional { unset = 0; } extend google.protobuf.MethodOptions { optional bar = 22222; }`, 325 errMsg: `test.proto:1:144: syntax error: unexpected '='`, 326 }, 327 { 328 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; enum repeated { unset = 0; } extend google.protobuf.MethodOptions { repeated bar = 22222; }`, 329 errMsg: `test.proto:1:144: syntax error: unexpected '='`, 330 }, 331 { 332 contents: `syntax = "proto3"; import "google/protobuf/descriptor.proto"; enum required { unset = 0; } extend google.protobuf.MethodOptions { required bar = 22222; }`, 333 errMsg: `test.proto:1:144: syntax error: unexpected '='`, 334 }, 335 { 336 contents: `syntax = "proto3"; enum optional { unset = 0; } message Foo { oneof bar { optional bar = 1; } }`, 337 errMsg: `test.proto:1:75: syntax error: unexpected "optional"`, 338 }, 339 { 340 contents: `syntax = "proto3"; enum repeated { unset = 0; } message Foo { oneof bar { repeated bar = 1; } }`, 341 errMsg: `test.proto:1:75: syntax error: unexpected "repeated"`, 342 }, 343 { 344 contents: `syntax = "proto3"; enum required { unset = 0; } message Foo { oneof bar { required bar = 1; } }`, 345 errMsg: `test.proto:1:75: syntax error: unexpected "required"`, 346 }, 347 { 348 contents: ``, 349 succeeds: true, 350 }, 351 { 352 contents: `0`, 353 errMsg: `test.proto:1:1: syntax error: unexpected int literal`, 354 }, 355 { 356 contents: `foobar`, 357 errMsg: `test.proto:1:1: syntax error: unexpected identifier`, 358 }, 359 { 360 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.`, 361 errMsg: `test.proto:1:1: syntax error: unexpected identifier`, 362 }, 363 { 364 contents: `"abc"`, 365 errMsg: `test.proto:1:1: syntax error: unexpected string literal`, 366 }, 367 { 368 contents: `0.0.0.0.0`, 369 errMsg: `test.proto:1:1: invalid syntax in float value: 0.0.0.0.0`, 370 }, 371 { 372 contents: `0.0`, 373 errMsg: `test.proto:1:1: syntax error: unexpected float literal`, 374 }, 375 { 376 contents: `option (opt) = {m: [{key: "a",value: {}}]};`, 377 succeeds: true, 378 }, 379 { 380 contents: `option (opt) = {m [{key: "a",value: {}}]};`, 381 succeeds: true, 382 }, 383 { 384 contents: `option (opt) = {m: []};`, 385 succeeds: true, 386 }, 387 { 388 contents: `option (opt) = {m []};`, 389 succeeds: true, 390 }, 391 } 392 393 for i, tc := range testCases { 394 errs := reporter.NewHandler(nil) 395 if ast, err := Parse("test.proto", strings.NewReader(tc.contents), errs); err == nil { 396 _, _ = ResultFromAST(ast, true, errs) 397 } 398 399 err := errs.Error() 400 if tc.succeeds { 401 assert.Nil(t, err, "case #%d should succeed", i) 402 } else { 403 if assert.NotNil(t, err, "case #%d should fail", i) { 404 assert.Equal(t, tc.errMsg, err.Error(), "case #%d bad error message", i) 405 } 406 } 407 } 408 }