file_test.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  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/protoreflect"
  13. "google.golang.org/protobuf/reflect/protoregistry"
  14. "google.golang.org/protobuf/types/descriptorpb"
  15. )
  16. func mustParseFile(s string) *descriptorpb.FileDescriptorProto {
  17. pb := new(descriptorpb.FileDescriptorProto)
  18. if err := prototext.Unmarshal([]byte(s), pb); err != nil {
  19. panic(err)
  20. }
  21. return pb
  22. }
  23. func cloneFile(in *descriptorpb.FileDescriptorProto) *descriptorpb.FileDescriptorProto {
  24. return proto.Clone(in).(*descriptorpb.FileDescriptorProto)
  25. }
  26. var (
  27. proto2Enum = mustParseFile(`
  28. syntax: "proto2"
  29. name: "proto2_enum.proto"
  30. package: "test.proto2"
  31. enum_type: [{name:"Enum" value:[{name:"ONE" number:1}]}]
  32. `)
  33. proto3Message = mustParseFile(`
  34. syntax: "proto3"
  35. name: "proto3_message.proto"
  36. package: "test.proto3"
  37. message_type: [{
  38. name: "Message"
  39. field: [
  40. {name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  41. {name:"bar" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
  42. ]
  43. }]
  44. `)
  45. extendableMessage = mustParseFile(`
  46. syntax: "proto2"
  47. name: "extendable_message.proto"
  48. package: "test.proto2"
  49. message_type: [{name:"Message" extension_range:[{start:1 end:1000}]}]
  50. `)
  51. importPublicFile1 = mustParseFile(`
  52. syntax: "proto3"
  53. name: "import_public1.proto"
  54. dependency: ["proto2_enum.proto", "proto3_message.proto", "extendable_message.proto"]
  55. message_type: [{name:"Public1"}]
  56. `)
  57. importPublicFile2 = mustParseFile(`
  58. syntax: "proto3"
  59. name: "import_public2.proto"
  60. dependency: ["import_public1.proto"]
  61. public_dependency: [0]
  62. message_type: [{name:"Public2"}]
  63. `)
  64. importPublicFile3 = mustParseFile(`
  65. syntax: "proto3"
  66. name: "import_public3.proto"
  67. dependency: ["import_public2.proto", "extendable_message.proto"]
  68. public_dependency: [0]
  69. message_type: [{name:"Public3"}]
  70. `)
  71. importPublicFile4 = mustParseFile(`
  72. syntax: "proto3"
  73. name: "import_public4.proto"
  74. dependency: ["import_public2.proto", "import_public3.proto", "proto2_enum.proto"]
  75. public_dependency: [0, 1]
  76. message_type: [{name:"Public4"}]
  77. `)
  78. )
  79. func TestNewFile(t *testing.T) {
  80. tests := []struct {
  81. label string
  82. inDeps []*descriptorpb.FileDescriptorProto
  83. inDesc *descriptorpb.FileDescriptorProto
  84. inOpts FileOptions
  85. wantDesc *descriptorpb.FileDescriptorProto
  86. wantErr string
  87. }{{
  88. label: "empty path",
  89. inDesc: mustParseFile(``),
  90. wantErr: `path must be populated`,
  91. }, {
  92. label: "empty package and syntax",
  93. inDesc: mustParseFile(`name:"weird"`),
  94. }, {
  95. label: "invalid syntax",
  96. inDesc: mustParseFile(`name:"weird" syntax:"proto9"`),
  97. wantErr: `invalid syntax: "proto9"`,
  98. }, {
  99. label: "bad package",
  100. inDesc: mustParseFile(`name:"weird" package:"$"`),
  101. wantErr: `invalid package: "$"`,
  102. }, {
  103. label: "unresolvable import",
  104. inDesc: mustParseFile(`
  105. name: "test.proto"
  106. dependency: "dep.proto"
  107. `),
  108. wantErr: `could not resolve import "dep.proto": not found`,
  109. }, {
  110. label: "unresolvable import but allowed",
  111. inDesc: mustParseFile(`
  112. name: "test.proto"
  113. dependency: "dep.proto"
  114. `),
  115. inOpts: FileOptions{AllowUnresolvable: true},
  116. }, {
  117. label: "duplicate import",
  118. inDesc: mustParseFile(`
  119. name: "test.proto"
  120. dependency: ["dep.proto", "dep.proto"]
  121. `),
  122. inOpts: FileOptions{AllowUnresolvable: true},
  123. wantErr: `already imported "dep.proto"`,
  124. }, {
  125. label: "invalid weak import",
  126. inDesc: mustParseFile(`
  127. name: "test.proto"
  128. dependency: "dep.proto"
  129. weak_dependency: [-23]
  130. `),
  131. inOpts: FileOptions{AllowUnresolvable: true},
  132. wantErr: `invalid or duplicate weak import index: -23`,
  133. }, {
  134. label: "normal weak and public import",
  135. inDesc: mustParseFile(`
  136. name: "test.proto"
  137. dependency: "dep.proto"
  138. weak_dependency: [0]
  139. public_dependency: [0]
  140. `),
  141. inOpts: FileOptions{AllowUnresolvable: true},
  142. }, {
  143. label: "import public indirect dependency duplicate",
  144. inDeps: []*descriptorpb.FileDescriptorProto{
  145. mustParseFile(`name:"leaf.proto"`),
  146. mustParseFile(`name:"public.proto" dependency:"leaf.proto" public_dependency:0`),
  147. },
  148. inDesc: mustParseFile(`
  149. name: "test.proto"
  150. dependency: ["public.proto", "leaf.proto"]
  151. `),
  152. }, {
  153. label: "import public graph",
  154. inDeps: []*descriptorpb.FileDescriptorProto{
  155. cloneFile(proto2Enum),
  156. cloneFile(proto3Message),
  157. cloneFile(extendableMessage),
  158. cloneFile(importPublicFile1),
  159. cloneFile(importPublicFile2),
  160. cloneFile(importPublicFile3),
  161. cloneFile(importPublicFile4),
  162. },
  163. inDesc: mustParseFile(`
  164. name: "test.proto"
  165. package: "test.graph"
  166. dependency: ["import_public4.proto"],
  167. `),
  168. // TODO: Test import public
  169. }, {
  170. label: "preserve source code locations",
  171. inDesc: mustParseFile(`
  172. name: "test.proto"
  173. package: "fizz.buzz"
  174. source_code_info: {location: [{
  175. span: [39,0,882,1]
  176. }, {
  177. path: [12]
  178. span: [39,0,18]
  179. leading_detached_comments: [" foo\n"," bar\n"]
  180. }, {
  181. path: [8,9]
  182. span: [51,0,28]
  183. leading_comments: " Comment\n"
  184. }]}
  185. `),
  186. }, {
  187. label: "invalid source code span",
  188. inDesc: mustParseFile(`
  189. name: "test.proto"
  190. package: "fizz.buzz"
  191. source_code_info: {location: [{
  192. span: [39]
  193. }]}
  194. `),
  195. wantErr: `invalid span: [39]`,
  196. }, {
  197. label: "resolve relative reference",
  198. inDesc: mustParseFile(`
  199. name: "test.proto"
  200. package: "fizz.buzz"
  201. message_type: [{
  202. name: "A"
  203. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"B.C"}]
  204. nested_type: [{name: "B"}]
  205. }, {
  206. name: "B"
  207. nested_type: [{name: "C"}]
  208. }]
  209. `),
  210. wantDesc: mustParseFile(`
  211. name: "test.proto"
  212. package: "fizz.buzz"
  213. message_type: [{
  214. name: "A"
  215. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.buzz.B.C"}]
  216. nested_type: [{name: "B"}]
  217. }, {
  218. name: "B"
  219. nested_type: [{name: "C"}]
  220. }]
  221. `),
  222. }, {
  223. label: "resolve the wrong type",
  224. inDesc: mustParseFile(`
  225. name: "test.proto"
  226. message_type: [{
  227. name: "M"
  228. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"E"}]
  229. enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
  230. }]
  231. `),
  232. wantErr: `message field "M.F" cannot resolve type: resolved "M.E", but it is not an message`,
  233. }, {
  234. label: "auto-resolve unknown kind",
  235. inDesc: mustParseFile(`
  236. name: "test.proto"
  237. message_type: [{
  238. name: "M"
  239. field: [{name:"F" number:1 label:LABEL_OPTIONAL type_name:"E"}]
  240. enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
  241. }]
  242. `),
  243. wantDesc: mustParseFile(`
  244. name: "test.proto"
  245. message_type: [{
  246. name: "M"
  247. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".M.E"}]
  248. enum_type: [{name: "E" value: [{name:"V0" number:0}, {name:"V1" number:1}]}]
  249. }]
  250. `),
  251. }, {
  252. label: "unresolved import",
  253. inDesc: mustParseFile(`
  254. name: "test.proto"
  255. package: "fizz.buzz"
  256. dependency: "remote.proto"
  257. `),
  258. wantErr: `could not resolve import "remote.proto": not found`,
  259. }, {
  260. label: "unresolved message field",
  261. inDesc: mustParseFile(`
  262. name: "test.proto"
  263. package: "fizz.buzz"
  264. message_type: [{
  265. name: "M"
  266. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"some.other.enum" default_value:"UNKNOWN"}]
  267. }]
  268. `),
  269. wantErr: `message field "fizz.buzz.M.F1" cannot resolve type: "*.some.other.enum" not found`,
  270. }, {
  271. label: "unresolved default enum value",
  272. inDesc: mustParseFile(`
  273. name: "test.proto"
  274. package: "fizz.buzz"
  275. message_type: [{
  276. name: "M"
  277. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:"E" default_value:"UNKNOWN"}]
  278. enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
  279. }]
  280. `),
  281. wantErr: `message field "fizz.buzz.M.F1" has invalid default: could not parse value for enum: "UNKNOWN"`,
  282. }, {
  283. label: "allowed unresolved default enum value",
  284. inDesc: mustParseFile(`
  285. name: "test.proto"
  286. package: "fizz.buzz"
  287. message_type: [{
  288. name: "M"
  289. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.M.E" default_value:"UNKNOWN"}]
  290. enum_type: [{name:"E" value:[{name:"V0" number:0}]}]
  291. }]
  292. `),
  293. inOpts: FileOptions{AllowUnresolvable: true},
  294. }, {
  295. label: "unresolved extendee",
  296. inDesc: mustParseFile(`
  297. name: "test.proto"
  298. package: "fizz.buzz"
  299. extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
  300. `),
  301. wantErr: `extension field "fizz.buzz.X" cannot resolve extendee: "*.some.extended.message" not found`,
  302. }, {
  303. label: "unresolved method input",
  304. inDesc: mustParseFile(`
  305. name: "test.proto"
  306. package: "fizz.buzz"
  307. service: [{
  308. name: "S"
  309. method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
  310. }]
  311. `),
  312. wantErr: `service method "fizz.buzz.S.M" cannot resolve input: "*.foo.bar.input" not found`,
  313. }, {
  314. label: "allowed unresolved references",
  315. inDesc: mustParseFile(`
  316. name: "test.proto"
  317. package: "fizz.buzz"
  318. dependency: "remote.proto"
  319. message_type: [{
  320. name: "M"
  321. field: [{name:"F1" number:1 label:LABEL_OPTIONAL type_name:"some.other.enum" default_value:"UNKNOWN"}]
  322. }]
  323. extension: [{name:"X" number:1 label:LABEL_OPTIONAL extendee:"some.extended.message" type:TYPE_MESSAGE type_name:"some.other.message"}]
  324. service: [{
  325. name: "S"
  326. method: [{name:"M" input_type:"foo.bar.input" output_type:".absolute.foo.bar.output"}]
  327. }]
  328. `),
  329. inOpts: FileOptions{AllowUnresolvable: true},
  330. }, {
  331. label: "resolved but not imported",
  332. inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
  333. name: "dep.proto"
  334. package: "fizz"
  335. message_type: [{name:"M" nested_type:[{name:"M"}]}]
  336. `)},
  337. inDesc: mustParseFile(`
  338. name: "test.proto"
  339. package: "fizz.buzz"
  340. message_type: [{
  341. name: "M"
  342. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
  343. }]
  344. `),
  345. wantErr: `message field "fizz.buzz.M.F" cannot resolve type: resolved "fizz.M.M", but "dep.proto" is not imported`,
  346. }, {
  347. label: "resolved from remote import",
  348. inDeps: []*descriptorpb.FileDescriptorProto{mustParseFile(`
  349. name: "dep.proto"
  350. package: "fizz"
  351. message_type: [{name:"M" nested_type:[{name:"M"}]}]
  352. `)},
  353. inDesc: mustParseFile(`
  354. name: "test.proto"
  355. package: "fizz.buzz"
  356. dependency: "dep.proto"
  357. message_type: [{
  358. name: "M"
  359. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M.M"}]
  360. }]
  361. `),
  362. wantDesc: mustParseFile(`
  363. name: "test.proto"
  364. package: "fizz.buzz"
  365. dependency: "dep.proto"
  366. message_type: [{
  367. name: "M"
  368. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".fizz.M.M"}]
  369. }]
  370. `),
  371. }, {
  372. label: "namespace conflict on enum value",
  373. inDesc: mustParseFile(`
  374. name: "test.proto"
  375. enum_type: [{
  376. name: "foo"
  377. value: [{name:"foo" number:0}]
  378. }]
  379. `),
  380. wantErr: `descriptor "foo" already declared`,
  381. }, {
  382. label: "no namespace conflict on message field",
  383. inDesc: mustParseFile(`
  384. name: "test.proto"
  385. message_type: [{
  386. name: "foo"
  387. field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  388. }]
  389. `),
  390. }, {
  391. label: "invalid name",
  392. inDesc: mustParseFile(`
  393. name: "test.proto"
  394. message_type: [{name: "$"}]
  395. `),
  396. wantErr: `descriptor "" has an invalid nested name: "$"`,
  397. }, {
  398. label: "invalid empty enum",
  399. inDesc: mustParseFile(`
  400. name: "test.proto"
  401. message_type: [{name:"M" enum_type:[{name:"E"}]}]
  402. `),
  403. wantErr: `enum "M.E" must contain at least one value declaration`,
  404. }, {
  405. label: "invalid enum value without number",
  406. inDesc: mustParseFile(`
  407. name: "test.proto"
  408. message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one"}]}]}]
  409. `),
  410. wantErr: `enum value "M.one" must have a specified number`,
  411. }, {
  412. label: "valid enum",
  413. inDesc: mustParseFile(`
  414. name: "test.proto"
  415. message_type: [{name:"M" enum_type:[{name:"E" value:[{name:"one" number:1}]}]}]
  416. `),
  417. }, {
  418. label: "invalid enum reserved names",
  419. inDesc: mustParseFile(`
  420. name: "test.proto"
  421. message_type: [{name:"M" enum_type:[{
  422. name: "E"
  423. reserved_name: [""]
  424. value: [{name:"V" number:0}]
  425. }]}]
  426. `),
  427. // NOTE: In theory this should be an error.
  428. // See https://github.com/protocolbuffers/protobuf/issues/6335.
  429. /*wantErr: `enum "M.E" reserved names has invalid name: ""`,*/
  430. }, {
  431. label: "duplicate enum reserved names",
  432. inDesc: mustParseFile(`
  433. name: "test.proto"
  434. message_type: [{name:"M" enum_type:[{
  435. name: "E"
  436. reserved_name: ["foo", "foo"]
  437. }]}]
  438. `),
  439. wantErr: `enum "M.E" reserved names has duplicate name: "foo"`,
  440. }, {
  441. label: "valid enum reserved names",
  442. inDesc: mustParseFile(`
  443. name: "test.proto"
  444. message_type: [{name:"M" enum_type:[{
  445. name: "E"
  446. reserved_name: ["foo", "bar"]
  447. value: [{name:"baz" number:1}]
  448. }]}]
  449. `),
  450. }, {
  451. label: "use of enum reserved names",
  452. inDesc: mustParseFile(`
  453. name: "test.proto"
  454. message_type: [{name:"M" enum_type:[{
  455. name: "E"
  456. reserved_name: ["foo", "bar"]
  457. value: [{name:"foo" number:1}]
  458. }]}]
  459. `),
  460. wantErr: `enum value "M.foo" must not use reserved name`,
  461. }, {
  462. label: "invalid enum reserved ranges",
  463. inDesc: mustParseFile(`
  464. name: "test.proto"
  465. message_type: [{name:"M" enum_type:[{
  466. name: "E"
  467. reserved_range: [{start:5 end:4}]
  468. }]}]
  469. `),
  470. wantErr: `enum "M.E" reserved ranges has invalid range: 5 to 4`,
  471. }, {
  472. label: "overlapping enum reserved ranges",
  473. inDesc: mustParseFile(`
  474. name: "test.proto"
  475. message_type: [{name:"M" enum_type:[{
  476. name: "E"
  477. reserved_range: [{start:1 end:1000}, {start:10 end:100}]
  478. }]}]
  479. `),
  480. wantErr: `enum "M.E" reserved ranges has overlapping ranges: 1 to 1000 with 10 to 100`,
  481. }, {
  482. label: "valid enum reserved names",
  483. inDesc: mustParseFile(`
  484. name: "test.proto"
  485. message_type: [{name:"M" enum_type:[{
  486. name: "E"
  487. reserved_range: [{start:1 end:10}, {start:100 end:1000}]
  488. value: [{name:"baz" number:50}]
  489. }]}]
  490. `),
  491. }, {
  492. label: "use of enum reserved range",
  493. inDesc: mustParseFile(`
  494. name: "test.proto"
  495. message_type: [{name:"M" enum_type:[{
  496. name: "E"
  497. reserved_range: [{start:1 end:10}, {start:100 end:1000}]
  498. value: [{name:"baz" number:500}]
  499. }]}]
  500. `),
  501. wantErr: `enum value "M.baz" must not use reserved number 500`,
  502. }, {
  503. label: "unused enum alias feature",
  504. inDesc: mustParseFile(`
  505. name: "test.proto"
  506. message_type: [{name:"M" enum_type:[{
  507. name: "E"
  508. value: [{name:"baz" number:500}]
  509. options: {allow_alias:true}
  510. }]}]
  511. `),
  512. wantErr: `enum "M.E" allows aliases, but none were found`,
  513. }, {
  514. label: "enum number conflicts",
  515. inDesc: mustParseFile(`
  516. name: "test.proto"
  517. message_type: [{name:"M" enum_type:[{
  518. name: "E"
  519. value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
  520. }]}]
  521. `),
  522. wantErr: `enum "M.E" has conflicting non-aliased values on number 1: "baz" with "bar"`,
  523. }, {
  524. label: "aliased enum numbers",
  525. inDesc: mustParseFile(`
  526. name: "test.proto"
  527. message_type: [{name:"M" enum_type:[{
  528. name: "E"
  529. value: [{name:"foo" number:0}, {name:"bar" number:1}, {name:"baz" number:1}]
  530. options: {allow_alias:true}
  531. }]}]
  532. `),
  533. }, {
  534. label: "invalid proto3 enum",
  535. inDesc: mustParseFile(`
  536. syntax: "proto3"
  537. name: "test.proto"
  538. message_type: [{name:"M" enum_type:[{
  539. name: "E"
  540. value: [{name:"baz" number:500}]
  541. }]}]
  542. `),
  543. wantErr: `enum "M.baz" using proto3 semantics must have zero number for the first value`,
  544. }, {
  545. label: "valid proto3 enum",
  546. inDesc: mustParseFile(`
  547. syntax: "proto3"
  548. name: "test.proto"
  549. message_type: [{name:"M" enum_type:[{
  550. name: "E"
  551. value: [{name:"baz" number:0}]
  552. }]}]
  553. `),
  554. }, {
  555. label: "proto3 enum name prefix conflict",
  556. inDesc: mustParseFile(`
  557. syntax: "proto3"
  558. name: "test.proto"
  559. message_type: [{name:"M" enum_type:[{
  560. name: "E"
  561. value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
  562. }]}]
  563. `),
  564. wantErr: `enum "M.E" using proto3 semantics has conflict: "fOo" with "e_Foo"`,
  565. }, {
  566. label: "proto2 enum has name prefix check",
  567. inDesc: mustParseFile(`
  568. name: "test.proto"
  569. message_type: [{name:"M" enum_type:[{
  570. name: "E"
  571. value: [{name:"e_Foo" number:0}, {name:"fOo" number:1}]
  572. }]}]
  573. `),
  574. }, {
  575. label: "proto3 enum same name prefix with number conflict",
  576. inDesc: mustParseFile(`
  577. syntax: "proto3"
  578. name: "test.proto"
  579. message_type: [{name:"M" enum_type:[{
  580. name: "E"
  581. value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
  582. }]}]
  583. `),
  584. wantErr: `enum "M.E" has conflicting non-aliased values on number 0: "fOo" with "e_Foo"`,
  585. }, {
  586. label: "proto3 enum same name prefix with alias numbers",
  587. inDesc: mustParseFile(`
  588. syntax: "proto3"
  589. name: "test.proto"
  590. message_type: [{name:"M" enum_type:[{
  591. name: "E"
  592. value: [{name:"e_Foo" number:0}, {name:"fOo" number:0}]
  593. options: {allow_alias: true}
  594. }]}]
  595. `),
  596. }, {
  597. label: "invalid message reserved names",
  598. inDesc: mustParseFile(`
  599. name: "test.proto"
  600. message_type: [{name:"M" nested_type:[{
  601. name: "M"
  602. reserved_name: ["$"]
  603. }]}]
  604. `),
  605. // NOTE: In theory this should be an error.
  606. // See https://github.com/protocolbuffers/protobuf/issues/6335.
  607. /*wantErr: `message "M.M" reserved names has invalid name: "$"`,*/
  608. }, {
  609. label: "valid message reserved names",
  610. inDesc: mustParseFile(`
  611. name: "test.proto"
  612. message_type: [{name:"M" nested_type:[{
  613. name: "M"
  614. reserved_name: ["foo", "bar"]
  615. field: [{name:"foo" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  616. }]}]
  617. `),
  618. wantErr: `message field "M.M.foo" must not use reserved name`,
  619. }, {
  620. label: "valid message reserved names",
  621. inDesc: mustParseFile(`
  622. name: "test.proto"
  623. message_type: [{name:"M" nested_type:[{
  624. name: "M"
  625. reserved_name: ["foo", "bar"]
  626. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
  627. oneof_decl: [{name:"foo"}] # not affected by reserved_name
  628. }]}]
  629. `),
  630. }, {
  631. label: "invalid reserved number",
  632. inDesc: mustParseFile(`
  633. name: "test.proto"
  634. message_type: [{name:"M" nested_type:[{
  635. name: "M"
  636. reserved_range: [{start:1 end:1}]
  637. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  638. }]}]
  639. `),
  640. wantErr: `message "M.M" reserved ranges has invalid field number: 0`,
  641. }, {
  642. label: "invalid reserved ranges",
  643. inDesc: mustParseFile(`
  644. name: "test.proto"
  645. message_type: [{name:"M" nested_type:[{
  646. name: "M"
  647. reserved_range: [{start:2 end:2}]
  648. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  649. }]}]
  650. `),
  651. wantErr: `message "M.M" reserved ranges has invalid range: 2 to 1`,
  652. }, {
  653. label: "overlapping reserved ranges",
  654. inDesc: mustParseFile(`
  655. name: "test.proto"
  656. message_type: [{name:"M" nested_type:[{
  657. name: "M"
  658. reserved_range: [{start:1 end:10}, {start:2 end:9}]
  659. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  660. }]}]
  661. `),
  662. wantErr: `message "M.M" reserved ranges has overlapping ranges: 1 to 9 with 2 to 8`,
  663. }, {
  664. label: "use of reserved message field number",
  665. inDesc: mustParseFile(`
  666. name: "test.proto"
  667. message_type: [{name:"M" nested_type:[{
  668. name: "M"
  669. reserved_range: [{start:10 end:20}, {start:20 end:30}, {start:30 end:31}]
  670. field: [{name:"baz" number:30 label:LABEL_OPTIONAL type:TYPE_STRING}]
  671. }]}]
  672. `),
  673. wantErr: `message field "M.M.baz" must not use reserved number 30`,
  674. }, {
  675. label: "invalid extension ranges",
  676. inDesc: mustParseFile(`
  677. name: "test.proto"
  678. message_type: [{name:"M" nested_type:[{
  679. name: "M"
  680. extension_range: [{start:-500 end:2}]
  681. field: [{name:"baz" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]
  682. }]}]
  683. `),
  684. wantErr: `message "M.M" extension ranges has invalid field number: -500`,
  685. }, {
  686. label: "overlapping reserved and extension ranges",
  687. inDesc: mustParseFile(`
  688. name: "test.proto"
  689. message_type: [{name:"M" nested_type:[{
  690. name: "M"
  691. reserved_range: [{start:15 end:20}, {start:1 end:3}, {start:7 end:10}]
  692. extension_range: [{start:8 end:9}, {start:3 end:5}]
  693. }]}]
  694. `),
  695. wantErr: `message "M.M" reserved and extension ranges has overlapping ranges: 7 to 9 with 8`,
  696. }, {
  697. label: "message field conflicting number",
  698. inDesc: mustParseFile(`
  699. name: "test.proto"
  700. message_type: [{name:"M" nested_type:[{
  701. name: "M"
  702. field: [
  703. {name:"one" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  704. {name:"One" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}
  705. ]
  706. }]}]
  707. `),
  708. wantErr: `message "M.M" has conflicting fields: "One" with "one"`,
  709. }, {
  710. label: "invalid MessageSet",
  711. inDesc: mustParseFile(`
  712. syntax: "proto3"
  713. name: "test.proto"
  714. message_type: [{name:"M" nested_type:[{
  715. name: "M"
  716. options: {message_set_wire_format:true}
  717. }]}]
  718. `),
  719. wantErr: func() string {
  720. if flags.ProtoLegacy {
  721. return `message "M.M" is an invalid proto1 MessageSet`
  722. } else {
  723. return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
  724. }
  725. }(),
  726. }, {
  727. label: "valid MessageSet",
  728. inDesc: mustParseFile(`
  729. name: "test.proto"
  730. message_type: [{name:"M" nested_type:[{
  731. name: "M"
  732. extension_range: [{start:1 end:100000}]
  733. options: {message_set_wire_format:true}
  734. }]}]
  735. `),
  736. wantErr: func() string {
  737. if flags.ProtoLegacy {
  738. return ""
  739. } else {
  740. return `message "M.M" is a MessageSet, which is a legacy proto1 feature that is no longer supported`
  741. }
  742. }(),
  743. }, {
  744. label: "invalid extension ranges in proto3",
  745. inDesc: mustParseFile(`
  746. syntax: "proto3"
  747. name: "test.proto"
  748. message_type: [{name:"M" nested_type:[{
  749. name: "M"
  750. extension_range: [{start:1 end:100000}]
  751. }]}]
  752. `),
  753. wantErr: `message "M.M" using proto3 semantics cannot have extension ranges`,
  754. }, {
  755. label: "proto3 message fields conflict",
  756. inDesc: mustParseFile(`
  757. syntax: "proto3"
  758. name: "test.proto"
  759. message_type: [{name:"M" nested_type:[{
  760. name: "M"
  761. field: [
  762. {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  763. {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
  764. ]
  765. }]}]
  766. `),
  767. wantErr: `message "M.M" using proto3 semantics has conflict: "baz" with "_b_a_z_"`,
  768. }, {
  769. label: "proto3 message fields",
  770. inDesc: mustParseFile(`
  771. syntax: "proto3"
  772. name: "test.proto"
  773. message_type: [{name:"M" nested_type:[{
  774. name: "M"
  775. field: [{name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}]
  776. oneof_decl: [{name:"baz"}] # proto3 name conflict logic does not include oneof
  777. }]}]
  778. `),
  779. }, {
  780. label: "proto2 message fields with no conflict",
  781. inDesc: mustParseFile(`
  782. name: "test.proto"
  783. message_type: [{name:"M" nested_type:[{
  784. name: "M"
  785. field: [
  786. {name:"_b_a_z_" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  787. {name:"baz" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}
  788. ]
  789. }]}]
  790. `),
  791. }, {
  792. label: "proto3 message with unresolved enum",
  793. inDesc: mustParseFile(`
  794. name: "test.proto"
  795. syntax: "proto3"
  796. message_type: [{
  797. name: "M"
  798. field: [
  799. {name:"enum" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".fizz.buzz.Enum"}
  800. ]
  801. }]
  802. `),
  803. inOpts: FileOptions{AllowUnresolvable: true},
  804. // TODO: Test field and oneof handling in validateMessageDeclarations
  805. // TODO: Test unmarshalDefault
  806. // TODO: Test validateExtensionDeclarations
  807. // TODO: Test checkValidGroup
  808. // TODO: Test checkValidMap
  809. }, {
  810. label: "empty service",
  811. inDesc: mustParseFile(`
  812. name: "test.proto"
  813. service: [{name:"service"}]
  814. `),
  815. }, {
  816. label: "service with method with unresolved",
  817. inDesc: mustParseFile(`
  818. name: "test.proto"
  819. service: [{
  820. name: "service"
  821. method: [{
  822. name:"method"
  823. input_type:"foo"
  824. output_type:".foo.bar.baz"
  825. }]
  826. }]
  827. `),
  828. inOpts: FileOptions{AllowUnresolvable: true},
  829. }, {
  830. label: "service with wrong reference type",
  831. inDeps: []*descriptorpb.FileDescriptorProto{
  832. cloneFile(proto3Message),
  833. cloneFile(proto2Enum),
  834. },
  835. inDesc: mustParseFile(`
  836. name: "test.proto"
  837. dependency: ["proto2_enum.proto", "proto3_message.proto"]
  838. service: [{
  839. name: "service"
  840. method: [{
  841. name: "method"
  842. input_type: ".test.proto2.Enum",
  843. output_type: ".test.proto3.Message"
  844. }]
  845. }]
  846. `),
  847. wantErr: `service method "service.method" cannot resolve input: resolved "test.proto2.Enum", but it is not an message`,
  848. }}
  849. for _, tt := range tests {
  850. t.Run(tt.label, func(t *testing.T) {
  851. r := new(protoregistry.Files)
  852. for i, dep := range tt.inDeps {
  853. f, err := tt.inOpts.New(dep, r)
  854. if err != nil {
  855. t.Fatalf("dependency %d: unexpected NewFile() error: %v", i, err)
  856. }
  857. if err := r.RegisterFile(f); err != nil {
  858. t.Fatalf("dependency %d: unexpected Register() error: %v", i, err)
  859. }
  860. }
  861. var gotDesc *descriptorpb.FileDescriptorProto
  862. if tt.wantErr == "" && tt.wantDesc == nil {
  863. tt.wantDesc = cloneFile(tt.inDesc)
  864. }
  865. gotFile, err := tt.inOpts.New(tt.inDesc, r)
  866. if gotFile != nil {
  867. gotDesc = ToFileDescriptorProto(gotFile)
  868. }
  869. if !proto.Equal(gotDesc, tt.wantDesc) {
  870. t.Errorf("NewFile() mismatch:\ngot %v\nwant %v", gotDesc, tt.wantDesc)
  871. }
  872. if ((err == nil) != (tt.wantErr == "")) || !strings.Contains(fmt.Sprint(err), tt.wantErr) {
  873. t.Errorf("NewFile() error:\ngot: %v\nwant: %v", err, tt.wantErr)
  874. }
  875. })
  876. }
  877. }
  878. func TestNewFiles(t *testing.T) {
  879. fdset := &descriptorpb.FileDescriptorSet{
  880. File: []*descriptorpb.FileDescriptorProto{
  881. mustParseFile(`
  882. name: "test.proto"
  883. package: "fizz"
  884. dependency: "dep.proto"
  885. message_type: [{
  886. name: "M2"
  887. field: [{name:"F" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"M1"}]
  888. }]
  889. `),
  890. // Inputs deliberately out of order.
  891. mustParseFile(`
  892. name: "dep.proto"
  893. package: "fizz"
  894. message_type: [{name:"M1"}]
  895. `),
  896. },
  897. }
  898. f, err := NewFiles(fdset)
  899. if err != nil {
  900. t.Fatal(err)
  901. }
  902. m1, err := f.FindDescriptorByName("fizz.M1")
  903. if err != nil {
  904. t.Fatalf(`f.FindDescriptorByName("fizz.M1") = %v`, err)
  905. }
  906. m2, err := f.FindDescriptorByName("fizz.M2")
  907. if err != nil {
  908. t.Fatalf(`f.FindDescriptorByName("fizz.M2") = %v`, err)
  909. }
  910. if m2.(protoreflect.MessageDescriptor).Fields().ByName("F").Message() != m1 {
  911. t.Fatalf(`m1.Fields().ByName("F").Message() != m2`)
  912. }
  913. }
  914. func TestNewFilesImportCycle(t *testing.T) {
  915. fdset := &descriptorpb.FileDescriptorSet{
  916. File: []*descriptorpb.FileDescriptorProto{
  917. mustParseFile(`
  918. name: "test.proto"
  919. package: "fizz"
  920. dependency: "dep.proto"
  921. `),
  922. mustParseFile(`
  923. name: "dep.proto"
  924. package: "fizz"
  925. dependency: "test.proto"
  926. `),
  927. },
  928. }
  929. _, err := NewFiles(fdset)
  930. if err == nil {
  931. t.Fatal("NewFiles with import cycle: success, want error")
  932. }
  933. }
  934. func TestSourceLocations(t *testing.T) {
  935. fd := mustParseFile(`
  936. name: "comments.proto"
  937. message_type: [{
  938. name: "Message1"
  939. field: [
  940. {name:"field1" number:1 label:LABEL_OPTIONAL type:TYPE_STRING},
  941. {name:"field2" number:2 label:LABEL_OPTIONAL type:TYPE_STRING},
  942. {name:"field3" number:3 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0},
  943. {name:"field4" number:4 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0},
  944. {name:"field5" number:5 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:1},
  945. {name:"field6" number:6 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:1}
  946. ]
  947. extension: [
  948. {name:"extension1" number:100 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"},
  949. {name:"extension2" number:101 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"}
  950. ]
  951. nested_type: [{name:"Message1"}, {name:"Message2"}]
  952. extension_range: {start:100 end:536870912}
  953. oneof_decl: [{name:"oneof1"}, {name:"oneof2"}]
  954. }, {
  955. name: "Message2"
  956. enum_type: {
  957. name: "Enum1"
  958. value: [
  959. {name: "FOO", number: 0},
  960. {name: "BAR", number: 1}
  961. ]
  962. }
  963. }]
  964. enum_type: {
  965. name: "Enum1"
  966. value: [
  967. {name: "FOO", number: 0},
  968. {name: "BAR", number: 1}
  969. ]
  970. }
  971. service: {
  972. name: "Service1"
  973. method: [
  974. {name:"Method1" input_type:".Message1" output_type:".Message1"},
  975. {name:"Method2" input_type:".Message2" output_type:".Message2"}
  976. ]
  977. }
  978. extension: [
  979. {name:"extension1" number:102 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"},
  980. {name:"extension2" number:103 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"}
  981. ]
  982. source_code_info: {
  983. location: [
  984. {span:[0,0,69,1]},
  985. {path:[12] span:[0,0,18]},
  986. {path:[5,0] span:[3,0,8,1] leading_comments:" Enum1\r\n"},
  987. {path:[5,0,1] span:[3,5,10]},
  988. {path:[5,0,2,0] span:[5,2,10] leading_comments:" FOO\r\n"},
  989. {path:[5,0,2,0,1] span:[5,2,5]},
  990. {path:[5,0,2,0,2] span:[5,8,9]},
  991. {path:[5,0,2,1] span:[7,2,10] leading_comments:" BAR\r\n"},
  992. {path:[5,0,2,1,1] span:[7,2,5]},
  993. {path:[5,0,2,1,2] span:[7,8,9]},
  994. {path:[4,0] span:[11,0,43,1] leading_comments:" Message1\r\n"},
  995. {path:[4,0,1] span:[11,8,16]},
  996. {path:[4,0,3,0] span:[13,2,21] leading_comments:" Message1.Message1\r\n"},
  997. {path:[4,0,3,0,1] span:[13,10,18]},
  998. {path:[4,0,3,1] span:[15,2,21] leading_comments:" Message1.Message2\r\n"},
  999. {path:[4,0,3,1,1] span:[15,10,18]},
  1000. {path:[4,0,2,0] span:[18,2,29] leading_comments:" Message1.field1\r\n"},
  1001. {path:[4,0,2,0,4] span:[18,2,10]},
  1002. {path:[4,0,2,0,5] span:[18,11,17]},
  1003. {path:[4,0,2,0,1] span:[18,18,24]},
  1004. {path:[4,0,2,0,3] span:[18,27,28]},
  1005. {path:[4,0,2,1] span:[20,2,29] leading_comments:" Message1.field2\r\n"},
  1006. {path:[4,0,2,1,4] span:[20,2,10]},
  1007. {path:[4,0,2,1,5] span:[20,11,17]},
  1008. {path:[4,0,2,1,1] span:[20,18,24]},
  1009. {path:[4,0,2,1,3] span:[20,27,28]},
  1010. {path:[4,0,8,0] span:[22,2,27,3] leading_comments:" Message1.oneof1\r\n"},
  1011. {path:[4,0,8,0,1] span:[22,8,14]},
  1012. {path:[4,0,2,2] span:[24,4,22] leading_comments:" Message1.field3\r\n"},
  1013. {path:[4,0,2,2,5] span:[24,4,10]},
  1014. {path:[4,0,2,2,1] span:[24,11,17]},
  1015. {path:[4,0,2,2,3] span:[24,20,21]},
  1016. {path:[4,0,2,3] span:[26,4,22] leading_comments:" Message1.field4\r\n"},
  1017. {path:[4,0,2,3,5] span:[26,4,10]},
  1018. {path:[4,0,2,3,1] span:[26,11,17]},
  1019. {path:[4,0,2,3,3] span:[26,20,21]},
  1020. {path:[4,0,8,1] span:[29,2,34,3] leading_comments:" Message1.oneof2\r\n"},
  1021. {path:[4,0,8,1,1] span:[29,8,14]},
  1022. {path:[4,0,2,4] span:[31,4,22] leading_comments:" Message1.field5\r\n"},
  1023. {path:[4,0,2,4,5] span:[31,4,10]},
  1024. {path:[4,0,2,4,1] span:[31,11,17]},
  1025. {path:[4,0,2,4,3] span:[31,20,21]},
  1026. {path:[4,0,2,5] span:[33,4,22] leading_comments:" Message1.field6\r\n"},
  1027. {path:[4,0,2,5,5] span:[33,4,10]},
  1028. {path:[4,0,2,5,1] span:[33,11,17]},
  1029. {path:[4,0,2,5,3] span:[33,20,21]},
  1030. {path:[4,0,5] span:[36,2,24]},
  1031. {path:[4,0,5,0] span:[36,13,23]},
  1032. {path:[4,0,5,0,1] span:[36,13,16]},
  1033. {path:[4,0,5,0,2] span:[36,20,23]},
  1034. {path:[4,0,6] span:[37,2,42,3]},
  1035. {path:[4,0,6,0] span:[39,4,37] leading_comments:" Message1.extension1\r\n"},
  1036. {path:[4,0,6,0,2] span:[37,9,18]},
  1037. {path:[4,0,6,0,4] span:[39,4,12]},
  1038. {path:[4,0,6,0,5] span:[39,13,19]},
  1039. {path:[4,0,6,0,1] span:[39,20,30]},
  1040. {path:[4,0,6,0,3] span:[39,33,36]},
  1041. {path:[4,0,6,1] span:[41,4,37] leading_comments:" Message1.extension2\r\n"},
  1042. {path:[4,0,6,1,2] span:[37,9,18]},
  1043. {path:[4,0,6,1,4] span:[41,4,12]},
  1044. {path:[4,0,6,1,5] span:[41,13,19]},
  1045. {path:[4,0,6,1,1] span:[41,20,30]},
  1046. {path:[4,0,6,1,3] span:[41,33,36]},
  1047. {path:[7] span:[45,0,50,1]},
  1048. {path:[7,0] span:[47,2,35] leading_comments:" extension1\r\n"},
  1049. {path:[7,0,2] span:[45,7,15]},
  1050. {path:[7,0,4] span:[47,2,10]},
  1051. {path:[7,0,5] span:[47,11,17]},
  1052. {path:[7,0,1] span:[47,18,28]},
  1053. {path:[7,0,3] span:[47,31,34]},
  1054. {path:[7,1] span:[49,2,35] leading_comments:" extension2\r\n"},
  1055. {path:[7,1,2] span:[45,7,15]},
  1056. {path:[7,1,4] span:[49,2,10]},
  1057. {path:[7,1,5] span:[49,11,17]},
  1058. {path:[7,1,1] span:[49,18,28]},
  1059. {path:[7,1,3] span:[49,31,34]},
  1060. {path:[4,1] span:[53,0,61,1] leading_comments:" Message2\r\n"},
  1061. {path:[4,1,1] span:[53,8,16]},
  1062. {path:[4,1,4,0] span:[55,2,60,3] leading_comments:" Message2.Enum1\r\n"},
  1063. {path:[4,1,4,0,1] span:[55,7,12]},
  1064. {path:[4,1,4,0,2,0] span:[57,4,12] leading_comments:" Message2.FOO\r\n"},
  1065. {path:[4,1,4,0,2,0,1] span:[57,4,7]},
  1066. {path:[4,1,4,0,2,0,2] span:[57,10,11]},
  1067. {path:[4,1,4,0,2,1] span:[59,4,12] leading_comments:" Message2.BAR\r\n"},
  1068. {path:[4,1,4,0,2,1,1] span:[59,4,7]},
  1069. {path:[4,1,4,0,2,1,2] span:[59,10,11]},
  1070. {path:[6,0] span:[64,0,69,1] leading_comments:" Service1\r\n"},
  1071. {path:[6,0,1] span:[64,8,16]},
  1072. {path:[6,0,2,0] span:[66,2,43] leading_comments:" Service1.Method1\r\n"},
  1073. {path:[6,0,2,0,1] span:[66,6,13]},
  1074. {path:[6,0,2,0,2] span:[66,14,22]},
  1075. {path:[6,0,2,0,3] span:[66,33,41]},
  1076. {path:[6,0,2,1] span:[68,2,43] leading_comments:" Service1.Method2\r\n"},
  1077. {path:[6,0,2,1,1] span:[68,6,13]},
  1078. {path:[6,0,2,1,2] span:[68,14,22]},
  1079. {path:[6,0,2,1,3] span:[68,33,41]}
  1080. ]
  1081. }
  1082. `)
  1083. fileDesc, err := NewFile(fd, nil)
  1084. if err != nil {
  1085. t.Fatalf("NewFile error: %v", err)
  1086. }
  1087. var walkDescs func(protoreflect.Descriptor, func(protoreflect.Descriptor))
  1088. walkDescs = func(d protoreflect.Descriptor, f func(protoreflect.Descriptor)) {
  1089. f(d)
  1090. if d, ok := d.(interface {
  1091. Enums() protoreflect.EnumDescriptors
  1092. }); ok {
  1093. eds := d.Enums()
  1094. for i := 0; i < eds.Len(); i++ {
  1095. walkDescs(eds.Get(i), f)
  1096. }
  1097. }
  1098. if d, ok := d.(interface {
  1099. Values() protoreflect.EnumValueDescriptors
  1100. }); ok {
  1101. vds := d.Values()
  1102. for i := 0; i < vds.Len(); i++ {
  1103. walkDescs(vds.Get(i), f)
  1104. }
  1105. }
  1106. if d, ok := d.(interface {
  1107. Messages() protoreflect.MessageDescriptors
  1108. }); ok {
  1109. mds := d.Messages()
  1110. for i := 0; i < mds.Len(); i++ {
  1111. walkDescs(mds.Get(i), f)
  1112. }
  1113. }
  1114. if d, ok := d.(interface {
  1115. Fields() protoreflect.FieldDescriptors
  1116. }); ok {
  1117. fds := d.Fields()
  1118. for i := 0; i < fds.Len(); i++ {
  1119. walkDescs(fds.Get(i), f)
  1120. }
  1121. }
  1122. if d, ok := d.(interface {
  1123. Oneofs() protoreflect.OneofDescriptors
  1124. }); ok {
  1125. ods := d.Oneofs()
  1126. for i := 0; i < ods.Len(); i++ {
  1127. walkDescs(ods.Get(i), f)
  1128. }
  1129. }
  1130. if d, ok := d.(interface {
  1131. Extensions() protoreflect.ExtensionDescriptors
  1132. }); ok {
  1133. xds := d.Extensions()
  1134. for i := 0; i < xds.Len(); i++ {
  1135. walkDescs(xds.Get(i), f)
  1136. }
  1137. }
  1138. if d, ok := d.(interface {
  1139. Services() protoreflect.ServiceDescriptors
  1140. }); ok {
  1141. sds := d.Services()
  1142. for i := 0; i < sds.Len(); i++ {
  1143. walkDescs(sds.Get(i), f)
  1144. }
  1145. }
  1146. if d, ok := d.(interface {
  1147. Methods() protoreflect.MethodDescriptors
  1148. }); ok {
  1149. mds := d.Methods()
  1150. for i := 0; i < mds.Len(); i++ {
  1151. walkDescs(mds.Get(i), f)
  1152. }
  1153. }
  1154. }
  1155. var numDescs int
  1156. walkDescs(fileDesc, func(d protoreflect.Descriptor) {
  1157. // The comment for every descriptor should be the full name itself.
  1158. got := strings.TrimSpace(fileDesc.SourceLocations().ByDescriptor(d).LeadingComments)
  1159. want := string(d.FullName())
  1160. if got != want {
  1161. t.Errorf("comment mismatch: got %v, want %v", got, want)
  1162. }
  1163. numDescs++
  1164. })
  1165. if numDescs != 30 {
  1166. t.Errorf("visited %d descriptor, expected 30", numDescs)
  1167. }
  1168. }