file_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  1. // Copyright 2019 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package protodesc
  5. import (
  6. "fmt"
  7. "strings"
  8. "testing"
  9. "google.golang.org/protobuf/encoding/prototext"
  10. "google.golang.org/protobuf/internal/flags"
  11. "google.golang.org/protobuf/proto"
  12. "google.golang.org/protobuf/reflect/protoregistry"
  13. "google.golang.org/protobuf/types/descriptorpb"
  14. )
  15. func mustParseFile(s string) *descriptorpb.FileDescriptorProto {
  16. pb := new(descriptorpb.FileDescriptorProto)
  17. if err := prototext.Unmarshal([]byte(s), pb); err != nil {
  18. panic(err)
  19. }
  20. return pb
  21. }
  22. func cloneFile(in *descriptorpb.FileDescriptorProto) *descriptorpb.FileDescriptorProto {
  23. out := new(descriptorpb.FileDescriptorProto)
  24. proto.Merge(out, in)
  25. return out
  26. }
  27. var (
  28. proto2Enum = mustParseFile(`
  29. syntax: "proto2"
  30. name: "proto2_enum.proto"
  31. package: "test.proto2"
  32. enum_type: [{name:"Enum" value:[{name:"ONE" number:1}]}]
  33. `)
  34. proto3Message = mustParseFile(`
  35. syntax: "proto3"
  36. name: "proto3_message.proto"
  37. package: "test.proto3"
  38. message_type: [{
  39. name: "Message"
  40. field: [
  41. {name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  42. {name:"bar" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
  43. ]
  44. }]
  45. `)
  46. extendableMessage = mustParseFile(`
  47. syntax: "proto2"
  48. name: "extendable_message.proto"
  49. package: "test.proto2"
  50. message_type: [{name:"Message" extension_range:[{start:1 end:1000}]}]
  51. `)
  52. importPublicFile1 = mustParseFile(`
  53. syntax: "proto3"
  54. name: "import_public1.proto"
  55. dependency: ["proto2_enum.proto", "proto3_message.proto", "extendable_message.proto"]
  56. message_type: [{name:"Public1"}]
  57. `)
  58. importPublicFile2 = mustParseFile(`
  59. syntax: "proto3"
  60. name: "import_public2.proto"
  61. dependency: ["import_public1.proto"]
  62. public_dependency: [0]
  63. message_type: [{name:"Public2"}]
  64. `)
  65. importPublicFile3 = mustParseFile(`
  66. syntax: "proto3"
  67. name: "import_public3.proto"
  68. dependency: ["import_public2.proto", "extendable_message.proto"]
  69. public_dependency: [0]
  70. message_type: [{name:"Public3"}]
  71. `)
  72. importPublicFile4 = mustParseFile(`
  73. syntax: "proto3"
  74. name: "import_public4.proto"
  75. dependency: ["import_public2.proto", "import_public3.proto", "proto2_enum.proto"]
  76. public_dependency: [0, 1]
  77. message_type: [{name:"Public4"}]
  78. `)
  79. )
  80. func TestNewFile(t *testing.T) {
  81. tests := []struct {
  82. label string
  83. inDeps []*descriptorpb.FileDescriptorProto
  84. inDesc *descriptorpb.FileDescriptorProto
  85. inOpts []option
  86. wantDesc *descriptorpb.FileDescriptorProto
  87. wantErr string
  88. }{{
  89. label: "empty path",
  90. inDesc: mustParseFile(``),
  91. wantErr: `path must be populated`,
  92. }, {
  93. label: "empty package and syntax",
  94. inDesc: mustParseFile(`name:"weird" package:""`),
  95. }, {
  96. label: "invalid syntax",
  97. inDesc: mustParseFile(`name:"weird" syntax:"proto9"`),
  98. wantErr: `invalid syntax: "proto9"`,
  99. }, {
  100. label: "bad package",
  101. inDesc: mustParseFile(`name:"weird" package:"$"`),
  102. wantErr: `invalid package: "$"`,
  103. }, {
  104. label: "unresolvable import",
  105. inDesc: mustParseFile(`
  106. name: "test.proto"
  107. package: ""
  108. dependency: "dep.proto"
  109. `),
  110. wantErr: `could not resolve import "dep.proto": not found`,
  111. }, {
  112. label: "unresolvable import but allowed",
  113. inDesc: mustParseFile(`
  114. name: "test.proto"
  115. package: ""
  116. dependency: "dep.proto"
  117. `),
  118. inOpts: []option{allowUnresolvable()},
  119. }, {
  120. label: "duplicate import",
  121. inDesc: mustParseFile(`
  122. name: "test.proto"
  123. package: ""
  124. dependency: ["dep.proto", "dep.proto"]
  125. `),
  126. inOpts: []option{allowUnresolvable()},
  127. wantErr: `already imported "dep.proto"`,
  128. }, {
  129. label: "invalid weak import",
  130. inDesc: mustParseFile(`
  131. name: "test.proto"
  132. package: ""
  133. dependency: "dep.proto"
  134. weak_dependency: [-23]
  135. `),
  136. inOpts: []option{allowUnresolvable()},
  137. wantErr: `invalid or duplicate weak import index: -23`,
  138. }, {
  139. label: "normal weak and public import",
  140. inDesc: mustParseFile(`
  141. name: "test.proto"
  142. package: ""
  143. dependency: "dep.proto"
  144. weak_dependency: [0]
  145. public_dependency: [0]
  146. `),
  147. inOpts: []option{allowUnresolvable()},
  148. }, {
  149. label: "import public indirect dependency duplicate",
  150. inDeps: []*descriptorpb.FileDescriptorProto{
  151. mustParseFile(`name:"leaf.proto"`),
  152. mustParseFile(`name:"public.proto" dependency:"leaf.proto" public_dependency:0`),
  153. },
  154. inDesc: mustParseFile(`
  155. name: "test.proto"
  156. package: ""
  157. dependency: ["public.proto", "leaf.proto"]
  158. `),
  159. }, {
  160. label: "import public graph",
  161. inDeps: []*descriptorpb.FileDescriptorProto{
  162. cloneFile(proto2Enum),
  163. cloneFile(proto3Message),
  164. cloneFile(extendableMessage),
  165. cloneFile(importPublicFile1),
  166. cloneFile(importPublicFile2),
  167. cloneFile(importPublicFile3),
  168. cloneFile(importPublicFile4),
  169. },
  170. inDesc: mustParseFile(`
  171. name: "test.proto"
  172. package: "test.graph"
  173. dependency: ["import_public4.proto"],
  174. `),
  175. // TODO: Test import public
  176. }, {
  177. label: "preserve source code locations",
  178. inDesc: mustParseFile(`
  179. name: "test.proto"
  180. package: "fizz.buzz"
  181. source_code_info: {location: [{
  182. span: [39,0,882,1]
  183. }, {
  184. path: [12]
  185. span: [39,0,18]
  186. leading_detached_comments: [" foo\n"," bar\n"]
  187. }, {
  188. path: [8,9]
  189. span: [51,0,28]
  190. leading_comments: " Comment\n"
  191. }]}
  192. `),
  193. }, {
  194. label: "invalid source code span",
  195. inDesc: mustParseFile(`
  196. name: "test.proto"
  197. package: "fizz.buzz"
  198. source_code_info: {location: [{
  199. span: [39]
  200. }]}
  201. `),
  202. wantErr: `invalid span: [39]`,
  203. }, {
  204. label: "resolve relative reference",
  205. inDesc: mustParseFile(`
  206. name: "test.proto"
  207. package: "fizz.buzz"
  208. message_type: [{
  209. name: "A"
  210. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
  211. nested_type: [{name: "B"}]
  212. }, {
  213. name: "B"
  214. nested_type: [{name: "C"}]
  215. }]
  216. `),
  217. wantDesc: mustParseFile(`
  218. name: "test.proto"
  219. package: "fizz.buzz"
  220. message_type: [{
  221. name: "A"
  222. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
  223. nested_type: [{name: "B"}]
  224. }, {
  225. name: "B"
  226. nested_type: [{name: "C"}]
  227. }]
  228. `),
  229. }, {
  230. label: "resolve the wrong type",
  231. inDesc: mustParseFile(`
  232. name: "test.proto"
  233. package: ""
  234. message_type: [{
  235. name: "M"
  236. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
  237. enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
  238. }]
  239. `),
  240. wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
  241. }, {
  242. label: "auto-resolve unknown kind",
  243. inDesc: mustParseFile(`
  244. name: "test.proto"
  245. package: ""
  246. message_type: [{
  247. name: "M"
  248. field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
  249. enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
  250. }]
  251. `),
  252. wantDesc: mustParseFile(`
  253. name: "test.proto"
  254. package: ""
  255. message_type: [{
  256. name: "M"
  257. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
  258. enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
  259. }]
  260. `),
  261. }, {
  262. label: "unresolved import",
  263. inDesc: mustParseFile(`
  264. name: "test.proto"
  265. package: "fizz.buzz"
  266. dependency: "remote.proto"
  267. `),
  268. wantErr: `could not resolve import "remote.proto": not found`,
  269. }, {
  270. label: "unresolved message field",
  271. inDesc: mustParseFile(`
  272. name: "test.proto"
  273. package: "fizz.buzz"
  274. message_type: [{
  275. name: "M"
  276. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
  277. }]
  278. `),
  279. wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
  280. }, {
  281. label: "unresolved default enum value",
  282. inDesc: mustParseFile(`
  283. name: "test.proto"
  284. package: "fizz.buzz"
  285. message_type: [{
  286. name: "M"
  287. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
  288. enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
  289. }]
  290. `),
  291. wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
  292. }, {
  293. label: "allowed unresolved default enum value",
  294. inDesc: mustParseFile(`
  295. name: "test.proto"
  296. package: "fizz.buzz"
  297. message_type: [{
  298. name: "M"
  299. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
  300. enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
  301. }]
  302. `),
  303. inOpts: []option{allowUnresolvable()},
  304. }, {
  305. label: "unresolved extendee",
  306. inDesc: mustParseFile(`
  307. name: "test.proto"
  308. package: "fizz.buzz"
  309. extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
  310. `),
  311. wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
  312. }, {
  313. label: "unresolved method input",
  314. inDesc: mustParseFile(`
  315. name: "test.proto"
  316. package: "fizz.buzz"
  317. service: [{
  318. name: "S"
  319. method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
  320. }]
  321. `),
  322. wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
  323. }, {
  324. label: "allowed unresolved references",
  325. inDesc: mustParseFile(`
  326. name: "test.proto"
  327. package: "fizz.buzz"
  328. dependency: "remote.proto"
  329. message_type: [{
  330. name: "M"
  331. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
  332. }]
  333. extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
  334. service: [{
  335. name: "S"
  336. method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
  337. }]
  338. `),
  339. inOpts: []option{allowUnresolvable()},
  340. }, {
  341. label: "resolved but not imported",
  342. inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
  343. name: "dep.proto"
  344. package: "fizz"
  345. message_type: [{name:"M" nested_type:[{name:"M"}]}]
  346. `)},
  347. inDesc: mustParseFile(`
  348. name: "test.proto"
  349. package: "fizz.buzz"
  350. message_type: [{
  351. name: "M"
  352. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
  353. }]
  354. `),
  355. wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
  356. }, {
  357. label: "resolved from remote import",
  358. inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
  359. name: "dep.proto"
  360. package: "fizz"
  361. message_type: [{name:"M" nested_type:[{name:"M"}]}]
  362. `)},
  363. inDesc: mustParseFile(`
  364. name: "test.proto"
  365. package: "fizz.buzz"
  366. dependency: "dep.proto"
  367. message_type: [{
  368. name: "M"
  369. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
  370. }]
  371. `),
  372. wantDesc: mustParseFile(`
  373. name: "test.proto"
  374. package: "fizz.buzz"
  375. dependency: "dep.proto"
  376. message_type: [{
  377. name: "M"
  378. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
  379. }]
  380. `),
  381. }, {
  382. label: "namespace conflict on enum value",
  383. inDesc: mustParseFile(`
  384. name: "test.proto"
  385. package: ""
  386. enum_type: [{
  387. name: "foo"
  388. value: [{name:"foo" number:0}]
  389. }]
  390. `),
  391. wantErr: `descriptor "foo" already declared`,
  392. }, {
  393. label: "no namespace conflict on message field",
  394. inDesc: mustParseFile(`
  395. name: "test.proto"
  396. package: ""
  397. message_type: [{
  398. name: "foo"
  399. field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  400. }]
  401. `),
  402. }, {
  403. label: "invalid name",
  404. inDesc: mustParseFile(`
  405. name: "test.proto"
  406. package: ""
  407. message_type: [{name: "$"}]
  408. `),
  409. wantErr: `descriptor "" has an invalid nested name: "$"`,
  410. }, {
  411. label: "invalid empty enum",
  412. inDesc: mustParseFile(`
  413. name: "test.proto"
  414. package: ""
  415. message_type: [{name:"M" enum_type:[{name:"E"}]}]
  416. `),
  417. wantErr: `enum "M.E" must contain at least one value declaration`,
  418. }, {
  419. label: "invalid enum value without number",
  420. inDesc: mustParseFile(`
  421. name: "test.proto"
  422. package: ""
  423. message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one"}]}]}]
  424. `),
  425. wantErr: `enum value "M.one" must have a specified number`,
  426. }, {
  427. label: "valid enum",
  428. inDesc: mustParseFile(`
  429. name: "test.proto"
  430. package: ""
  431. message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one" number:1}]}]}]
  432. `),
  433. }, {
  434. label: "invalid enum reserved names",
  435. inDesc: mustParseFile(`
  436. name: "test.proto"
  437. package: ""
  438. message_type: [{name:"M" enum_type:[{
  439. name: "E"
  440. reserved_name: [""]
  441. value: [{name:"V" number:0}]
  442. }]}]
  443. `),
  444. // NOTE: In theory this should be an error.
  445. // See https://github.com/protocolbuffers/protobuf/issues/6335.
  446. /*wantErr: `enum "M.E" reserved names has invalid name: ""`,*/
  447. }, {
  448. label: "duplicate enum reserved names",
  449. inDesc: mustParseFile(`
  450. name: "test.proto"
  451. package: ""
  452. message_type: [{name:"M" enum_type:[{
  453. name: "E"
  454. reserved_name: ["foo", "foo"]
  455. }]}]
  456. `),
  457. wantErr: `enum "M.E" reserved names has duplicate name: "foo"`,
  458. }, {
  459. label: "valid enum reserved names",
  460. inDesc: mustParseFile(`
  461. name: "test.proto"
  462. package: ""
  463. message_type: [{name:"M" enum_type:[{
  464. name: "E"
  465. reserved_name: ["foo", "bar"]
  466. value: [{name:"baz" number:1}]
  467. }]}]
  468. `),
  469. }, {
  470. label: "use of enum reserved names",
  471. inDesc: mustParseFile(`
  472. name: "test.proto"
  473. package: ""
  474. message_type: [{name:"M" enum_type:[{
  475. name: "E"
  476. reserved_name: ["foo", "bar"]
  477. value: [{name:"foo" number:1}]
  478. }]}]
  479. `),
  480. wantErr: `enum value "M.foo" must not use reserved name`,
  481. }, {
  482. label: "invalid enum reserved ranges",
  483. inDesc: mustParseFile(`
  484. name: "test.proto"
  485. package: ""
  486. message_type: [{name:"M" enum_type:[{
  487. name: "E"
  488. reserved_range: [{start:5 end:4}]
  489. }]}]
  490. `),
  491. wantErr: `enum "M.E" reserved ranges has invalid range: 5 to 4`,
  492. }, {
  493. label: "overlapping enum reserved ranges",
  494. inDesc: mustParseFile(`
  495. name: "test.proto"
  496. package: ""
  497. message_type: [{name:"M" enum_type:[{
  498. name: "E"
  499. reserved_range: [{start:1 end:1000}, {start:10 end:100}]
  500. }]}]
  501. `),
  502. wantErr: `enum "M.E" reserved ranges has overlapping ranges: 1 to 1000 with 10 to 100`,
  503. }, {
  504. label: "valid enum reserved names",
  505. inDesc: mustParseFile(`
  506. name: "test.proto"
  507. package: ""
  508. message_type: [{name:"M" enum_type:[{
  509. name: "E"
  510. reserved_range: [{start:1 end:10}, {start:100 end:1000}]
  511. value: [{name:"baz" number:50}]
  512. }]}]
  513. `),
  514. }, {
  515. label: "use of enum reserved range",
  516. inDesc: mustParseFile(`
  517. name: "test.proto"
  518. package: ""
  519. message_type: [{name:"M" enum_type:[{
  520. name: "E"
  521. reserved_range: [{start:1 end:10}, {start:100 end:1000}]
  522. value: [{name:"baz" number:500}]
  523. }]}]
  524. `),
  525. wantErr: `enum value "M.baz" must not use reserved number 500`,
  526. }, {
  527. label: "unused enum alias feature",
  528. inDesc: mustParseFile(`
  529. name: "test.proto"
  530. package: ""
  531. message_type: [{name:"M" enum_type:[{
  532. name: "E"
  533. value: [{name:"baz" number:500}]
  534. options: {allow_alias:true}
  535. }]}]
  536. `),
  537. wantErr: `enum "M.E" allows aliases, but none were found`,
  538. }, {
  539. label: "enum number conflicts",
  540. inDesc: mustParseFile(`
  541. name: "test.proto"
  542. package: ""
  543. message_type: [{name:"M" enum_type:[{
  544. name: "E"
  545. value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
  546. }]}]
  547. `),
  548. wantErr: `enum "M.E" has conflicting non-aliased values on number 1: "baz" with "bar"`,
  549. }, {
  550. label: "aliased enum numbers",
  551. inDesc: mustParseFile(`
  552. name: "test.proto"
  553. package: ""
  554. message_type: [{name:"M" enum_type:[{
  555. name: "E"
  556. value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
  557. options: {allow_alias:true}
  558. }]}]
  559. `),
  560. }, {
  561. label: "invalid proto3 enum",
  562. inDesc: mustParseFile(`
  563. syntax: "proto3"
  564. name: "test.proto"
  565. package: ""
  566. message_type: [{name:"M" enum_type:[{
  567. name: "E"
  568. value: [{name:"baz" number:500}]
  569. }]}]
  570. `),
  571. wantErr: `enum "M.baz" using proto3 semantics must have zero number for the first value`,
  572. }, {
  573. label: "valid proto3 enum",
  574. inDesc: mustParseFile(`
  575. syntax: "proto3"
  576. name: "test.proto"
  577. package: ""
  578. message_type: [{name:"M" enum_type:[{
  579. name: "E"
  580. value: [{name:"baz" number:0}]
  581. }]}]
  582. `),
  583. }, {
  584. label: "proto3 enum name prefix conflict",
  585. inDesc: mustParseFile(`
  586. syntax: "proto3"
  587. name: "test.proto"
  588. package: ""
  589. message_type: [{name:"M" enum_type:[{
  590. name: "E"
  591. value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
  592. }]}]
  593. `),
  594. wantErr: `enum "M.E" using proto3 semantics has conflict: "fOo" with "e_Foo"`,
  595. }, {
  596. label: "proto2 enum has name prefix check",
  597. inDesc: mustParseFile(`
  598. name: "test.proto"
  599. package: ""
  600. message_type: [{name:"M" enum_type:[{
  601. name: "E"
  602. value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
  603. }]}]
  604. `),
  605. }, {
  606. label: "proto3 enum same name prefix with number conflict",
  607. inDesc: mustParseFile(`
  608. syntax: "proto3"
  609. name: "test.proto"
  610. package: ""
  611. message_type: [{name:"M" enum_type:[{
  612. name: "E"
  613. value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
  614. }]}]
  615. `),
  616. wantErr: `enum "M.E" has conflicting non-aliased values on number 0: "fOo" with "e_Foo"`,
  617. }, {
  618. label: "proto3 enum same name prefix with alias numbers",
  619. inDesc: mustParseFile(`
  620. syntax: "proto3"
  621. name: "test.proto"
  622. package: ""
  623. message_type: [{name:"M" enum_type:[{
  624. name: "E"
  625. value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
  626. options: {allow_alias: true}
  627. }]}]
  628. `),
  629. }, {
  630. label: "invalid message reserved names",
  631. inDesc: mustParseFile(`
  632. name: "test.proto"
  633. package: ""
  634. message_type: [{name:"M" nested_type:[{
  635. name: "M"
  636. reserved_name: ["$"]
  637. }]}]
  638. `),
  639. // NOTE: In theory this should be an error.
  640. // See https://github.com/protocolbuffers/protobuf/issues/6335.
  641. /*wantErr: `message "M.M" reserved names has invalid name: "$"`,*/
  642. }, {
  643. label: "valid message reserved names",
  644. inDesc: mustParseFile(`
  645. name: "test.proto"
  646. package: ""
  647. message_type: [{name:"M" nested_type:[{
  648. name: "M"
  649. reserved_name: ["foo", "bar"]
  650. field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  651. }]}]
  652. `),
  653. wantErr: `message field "M.M.foo" must not use reserved name`,
  654. }, {
  655. label: "valid message reserved names",
  656. inDesc: mustParseFile(`
  657. name: "test.proto"
  658. package: ""
  659. message_type: [{name:"M" nested_type:[{
  660. name: "M"
  661. reserved_name: ["foo", "bar"]
  662. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
  663. oneof_decl: [{name:"foo"}] # not affected by reserved_name
  664. }]}]
  665. `),
  666. }, {
  667. label: "invalid reserved number",
  668. inDesc: mustParseFile(`
  669. name: "test.proto"
  670. package: ""
  671. message_type: [{name:"M" nested_type:[{
  672. name: "M"
  673. reserved_range: [{start:1 end:1}]
  674. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  675. }]}]
  676. `),
  677. wantErr: `message "M.M" reserved ranges has invalid field number: 0`,
  678. }, {
  679. label: "invalid reserved ranges",
  680. inDesc: mustParseFile(`
  681. name: "test.proto"
  682. package: ""
  683. message_type: [{name:"M" nested_type:[{
  684. name: "M"
  685. reserved_range: [{start:2 end:2}]
  686. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  687. }]}]
  688. `),
  689. wantErr: `message "M.M" reserved ranges has invalid range: 2 to 1`,
  690. }, {
  691. label: "overlapping reserved ranges",
  692. inDesc: mustParseFile(`
  693. name: "test.proto"
  694. package: ""
  695. message_type: [{name:"M" nested_type:[{
  696. name: "M"
  697. reserved_range: [{start:1 end:10}, {start:2 end:9}]
  698. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  699. }]}]
  700. `),
  701. wantErr: `message "M.M" reserved ranges has overlapping ranges: 1 to 9 with 2 to 8`,
  702. }, {
  703. label: "use of reserved message field number",
  704. inDesc: mustParseFile(`
  705. name: "test.proto"
  706. package: ""
  707. message_type: [{name:"M" nested_type:[{
  708. name: "M"
  709. reserved_range: [{start:10 end:20}, {start:20 end:30}, {start:30 end:31}]
  710. field: [{name:"baz" number:30 label:LABEL_OPTIONAL type:TYPE_STRING}]
  711. }]}]
  712. `),
  713. wantErr: `message field "M.M.baz" must not use reserved number 30`,
  714. }, {
  715. label: "invalid extension ranges",
  716. inDesc: mustParseFile(`
  717. name: "test.proto"
  718. package: ""
  719. message_type: [{name:"M" nested_type:[{
  720. name: "M"
  721. extension_range: [{start:-500 end:2}]
  722. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  723. }]}]
  724. `),
  725. wantErr: `message "M.M" extension ranges has invalid field number: -500`,
  726. }, {
  727. label: "overlapping reserved and extension ranges",
  728. inDesc: mustParseFile(`
  729. name: "test.proto"
  730. package: ""
  731. message_type: [{name:"M" nested_type:[{
  732. name: "M"
  733. reserved_range: [{start:15 end:20}, {start:1 end:3}, {start:7 end:10}]
  734. extension_range: [{start:8 end:9}, {start:3 end:5}]
  735. }]}]
  736. `),
  737. wantErr: `message "M.M" reserved and extension ranges has overlapping ranges: 7 to 9 with 8`,
  738. }, {
  739. label: "message field conflicting number",
  740. inDesc: mustParseFile(`
  741. name: "test.proto"
  742. package: ""
  743. message_type: [{name:"M" nested_type:[{
  744. name: "M"
  745. field: [
  746. {name:"one" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  747. {name:"One" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}
  748. ]
  749. }]}]
  750. `),
  751. wantErr: `message "M.M" has conflicting fields: "One" with "one"`,
  752. }, {
  753. label: "invalid MessageSet",
  754. inDesc: mustParseFile(`
  755. syntax: "proto3"
  756. name: "test.proto"
  757. package: ""
  758. message_type: [{name:"M" nested_type:[{
  759. name: "M"
  760. options: {message_set_wire_format:true}
  761. }]}]
  762. `),
  763. wantErr: func() string {
  764. if flags.ProtoLegacy {
  765. return `message "M.M" is an invalid proto1 MessageSet`
  766. } else {
  767. return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
  768. }
  769. }(),
  770. }, {
  771. label: "valid MessageSet",
  772. inDesc: mustParseFile(`
  773. name: "test.proto"
  774. package: ""
  775. message_type: [{name:"M" nested_type:[{
  776. name: "M"
  777. extension_range: [{start:1 end:100000}]
  778. options: {message_set_wire_format:true}
  779. }]}]
  780. `),
  781. wantErr: func() string {
  782. if flags.ProtoLegacy {
  783. return ""
  784. } else {
  785. return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
  786. }
  787. }(),
  788. }, {
  789. label: "invalid extension ranges in proto3",
  790. inDesc: mustParseFile(`
  791. syntax: "proto3"
  792. name: "test.proto"
  793. package: ""
  794. message_type: [{name:"M" nested_type:[{
  795. name: "M"
  796. extension_range: [{start:1 end:100000}]
  797. }]}]
  798. `),
  799. wantErr: `message "M.M" using proto3 semantics cannot have extension ranges`,
  800. }, {
  801. label: "proto3 message fields conflict",
  802. inDesc: mustParseFile(`
  803. syntax: "proto3"
  804. name: "test.proto"
  805. package: ""
  806. message_type: [{name:"M" nested_type:[{
  807. name: "M"
  808. field: [
  809. {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  810. {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
  811. ]
  812. }]}]
  813. `),
  814. wantErr: `message "M.M" using proto3 semantics has conflict: "baz" with "_b_a_z_"`,
  815. }, {
  816. label: "proto3 message fields",
  817. inDesc: mustParseFile(`
  818. syntax: "proto3"
  819. name: "test.proto"
  820. package: ""
  821. message_type: [{name:"M" nested_type:[{
  822. name: "M"
  823. field: [{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
  824. oneof_decl: [{name:"baz"}] # proto3 name conflict logic does not include oneof
  825. }]}]
  826. `),
  827. }, {
  828. label: "proto2 message fields with no conflict",
  829. inDesc: mustParseFile(`
  830. name: "test.proto"
  831. package: ""
  832. message_type: [{name:"M" nested_type:[{
  833. name: "M"
  834. field: [
  835. {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  836. {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
  837. ]
  838. }]}]
  839. `),
  840. }, {
  841. label: "proto3 message with unresolved enum",
  842. inDesc: mustParseFile(`
  843. name: "test.proto"
  844. package: ""
  845. syntax: "proto3"
  846. message_type: [{
  847. name: "M"
  848. field: [
  849. {name:"enum" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.Enum"}
  850. ]
  851. }]
  852. `),
  853. inOpts: []option{allowUnresolvable()},
  854. // TODO: Test field and oneof handling in validateMessageDeclarations
  855. // TODO: Test unmarshalDefault
  856. // TODO: Test validateExtensionDeclarations
  857. // TODO: Test checkValidGroup
  858. // TODO: Test checkValidMap
  859. }, {
  860. label: "empty service",
  861. inDesc: mustParseFile(`
  862. name: "test.proto"
  863. package: ""
  864. service: [{name:"service"}]
  865. `),
  866. }, {
  867. label: "service with method with unresolved",
  868. inDesc: mustParseFile(`
  869. name: "test.proto"
  870. package: ""
  871. service: [{
  872. name: "service"
  873. method: [{
  874. name:"method"
  875. input_type:"foo"
  876. output_type:".foo.bar.baz"
  877. }]
  878. }]
  879. `),
  880. inOpts: []option{allowUnresolvable()},
  881. }, {
  882. label: "service with wrong reference type",
  883. inDeps: []*descriptorpb.FileDescriptorProto{
  884. cloneFile(proto3Message),
  885. cloneFile(proto2Enum),
  886. },
  887. inDesc: mustParseFile(`
  888. name: "test.proto"
  889. package: ""
  890. dependency: ["proto2_enum.proto", "proto3_message.proto"]
  891. service: [{
  892. name: "service"
  893. method: [{
  894. name: "method"
  895. input_type: ".test.proto2.Enum",
  896. output_type: ".test.proto3.Message"
  897. }]
  898. }]
  899. `),
  900. wantErr: `service method "service.method" cannot resolve input: resolved "test.proto2.Enum", but it is not an message`,
  901. }}
  902. for _, tt := range tests {
  903. t.Run(tt.label, func(t *testing.T) {
  904. r := new(protoregistry.Files)
  905. for i, dep := range tt.inDeps {
  906. f, err := newFile(dep, r)
  907. if err != nil {
  908. t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
  909. }
  910. if err := r.Register(f); err != nil {
  911. t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
  912. }
  913. }
  914. var gotDesc *descriptorpb.FileDescriptorProto
  915. if tt.wantErr == "" && tt.wantDesc == nil {
  916. tt.wantDesc = cloneFile(tt.inDesc)
  917. }
  918. gotFile, err := newFile(tt.inDesc, r, tt.inOpts...)
  919. if gotFile != nil {
  920. gotDesc = ToFileDescriptorProto(gotFile)
  921. }
  922. if !proto.Equal(gotDesc, tt.wantDesc) {
  923. t.Errorf("NewFile() mismatch:\ngot %v\nwant %v", gotDesc, tt.wantDesc)
  924. }
  925. if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
  926. t.Errorf("NewFile() error:\ngot: %v\nwant: %v", err, tt.wantErr)
  927. }
  928. })
  929. }
  930. }