github.com/cuberat/protoc-gen-docjson@v1.1.1-0.20230510142946-907a0bd92b78/tests/proto1_test.go (about) 1 package proto1_test 2 3 import ( 4 // Built-in/core modules. 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 exec "os/exec" 10 "path" 11 "reflect" 12 "sort" 13 "testing" 14 // Generated code. 15 // First-party modules. 16 ) 17 18 func TestComments(t *testing.T) { 19 data, ok := do_setup(t) 20 if !ok { 21 return 22 } 23 24 if !t.Run("len check", 25 func(st *testing.T) { 26 do_len_checks(st, data) 27 }, 28 ) { 29 t.Error("Len checks failed. Bailing out of tests.") 30 return 31 } 32 33 t.Run("syntax data check", 34 func(st *testing.T) { 35 do_check_syntax(st, data) 36 }, 37 ) 38 39 t.Run("svc check", 40 func(st *testing.T) { 41 do_check_services(st, data) 42 }, 43 ) 44 45 t.Run("file check", 46 func(st *testing.T) { 47 do_check_files(st, data) 48 }, 49 ) 50 51 t.Run("msg check", 52 func(st *testing.T) { 53 do_check_messages(st, data) 54 }, 55 ) 56 57 t.Run("enum check", 58 func(st *testing.T) { 59 do_check_enums(st, data) 60 }, 61 ) 62 63 t.Run("service_deps check", 64 func(st *testing.T) { 65 do_check_service_deps(st, data) 66 }, 67 ) 68 69 t.Run("service_file_dep check", 70 func(st *testing.T) { 71 do_check_service_file_deps(st, data) 72 }, 73 ) 74 } 75 76 func do_check_service_file_deps(t *testing.T, data map[string]any) { 77 svc_file_deps, ok := data["service_file_deps"].(map[string]any) 78 if !ok { 79 t.Errorf("wrong type for service_file_deps: got %T, "+ 80 "expected map[string]any", data["service_file_deps"]) 81 return 82 } 83 84 if len(svc_file_deps) != 1 { 85 t.Errorf("wrong number of items in service_file_deps: got %d, "+ 86 "expected 1", len(svc_file_deps)) 87 return 88 } 89 90 svc_name := "MyServices.Service.Tester" 91 expected_deps := []any{"tester.proto"} 92 deps, ok := svc_file_deps[svc_name].([]any) 93 if !ok { 94 t.Errorf("wrong type for svc dep list %q: got %T, expected []any", 95 svc_name, svc_file_deps[svc_name]) 96 return 97 } 98 if !reflect.DeepEqual(deps, expected_deps) { 99 t.Errorf("wrong list of dependencies for svc %q: got %v, expected %v", 100 svc_name, deps, expected_deps) 101 return 102 } 103 } 104 105 func do_check_service_deps(t *testing.T, data map[string]any) { 106 svc_deps, ok := data["service_deps"].(map[string]any) 107 if !ok { 108 t.Errorf("wrong type for service_deps: got %T, "+ 109 "expected map[string]any", data["service_deps"]) 110 return 111 } 112 113 if len(svc_deps) != 1 { 114 t.Errorf("wrong number of items in service_deps: got %d, "+ 115 "expected 1", len(svc_deps)) 116 return 117 } 118 119 svc_name := "MyServices.Service.Tester" 120 this_svc_deps, ok := svc_deps[svc_name].([]any) 121 if !ok { 122 t.Errorf("wrong type for dep %q dep list: got %T, "+ 123 "expected []any", svc_name, svc_deps[svc_name]) 124 return 125 } 126 127 expected_deps := []any{ 128 "MyServices.Tester.TesterRequest", 129 "MyServices.Tester.ClientInfo", 130 "MyServices.Tester.TesterResponse", 131 "MyServices.Tester.TesterResponse.ResponseThing", 132 "MyServices.Tester.TesterResponse.EmbeddedTester", 133 } 134 if !reflect.DeepEqual(this_svc_deps, expected_deps) { 135 t.Errorf("wrong service dep list for %q: got %v, expected %v", 136 svc_name, this_svc_deps, expected_deps) 137 return 138 } 139 } 140 141 func do_check_services(t *testing.T, data map[string]any) { 142 svc_map, ok := data["service_map"].(map[string]any) 143 if !ok { 144 t.Errorf("Wrong type for service_map: %T", data["service_map"]) 145 return 146 } 147 148 svc_name := "MyServices.Service.Tester" 149 svc, ok := svc_map[svc_name].(map[string]any) 150 if !ok { 151 t.Errorf("Wrong type for service: %T", svc_map["service_name"]) 152 return 153 } 154 155 label := fmt.Sprintf("service %s", svc["name"].(string)) 156 157 exp_desc := "Tester service. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Suspendisse a cursus mauris. Proin porta mi nisl, vel iaculis leo mattis\n ut. Maecenas lacus urna, dapibus sit amet leo id, rutrum fermentum justo.\n Cras porta, nulla vel euismod maximus, lacus magna ultrices metus, sit amet\n eleifend lacus libero et lacus. Cras a facilisis est. Praesent augue nisl,\n tincidunt vel ex mattis, efficitur fermentum sem. Ut congue tellus ut\n accumsan condimentum. Sed quis leo nec turpis maximus molestie quis sit\n amet erat." 158 159 desc := svc["description"].(string) 160 if desc != exp_desc { 161 t.Errorf("incorrect description for service %s. Got %q, expected %q", 162 svc_name, desc, exp_desc) 163 return 164 } 165 166 leading_comments := svc["leading_comments"].(string) 167 if leading_comments != exp_desc { 168 t.Errorf("incorrect leading_comments for service %s. "+ 169 "Got %q, expected %q", svc_name, leading_comments, exp_desc) 170 return 171 } 172 173 leading_detached_comments := svc["leading_detached_comments"].([]any) 174 if len(leading_detached_comments) != 0 { 175 t.Errorf("got %d leading_detached_comments when 0 were expected", 176 len(leading_detached_comments)) 177 return 178 } 179 180 check_methods(t, svc) 181 182 test_spec := map[string]any{ 183 "options": map[string]any{ 184 "deprecated": false, 185 }, 186 "custom_options": map[string]any{ 187 "service_not_implemented": true, 188 }, 189 } 190 191 t.Run("options", 192 func(st *testing.T) { 193 do_check_options(st, svc, test_spec, label) 194 }, 195 ) 196 } 197 198 func check_one_method( 199 t *testing.T, 200 this_method map[string]any, 201 test_spec map[string]any, 202 label string, 203 ) { 204 method_name := this_method["name"].(string) 205 method_full_name := this_method["full_name"].(string) 206 207 exp_name := test_spec["name"].(string) 208 if method_name != exp_name { 209 t.Errorf("got method name %q, expected %q", method_name, exp_name) 210 } 211 exp_full_name := test_spec["full_name"] 212 if method_full_name != exp_full_name { 213 t.Errorf("got method full name %q, expected %q", method_full_name, 214 exp_full_name) 215 } 216 217 desc := this_method["description"].(string) 218 exp_desc := test_spec["desc"] 219 220 if desc != exp_desc { 221 t.Errorf("incorrect description for service method %s: got %q, "+ 222 "expected %q", method_name, desc, exp_desc) 223 } 224 225 t.Run("request_response", 226 func(st *testing.T) { 227 check_req_resp(st, this_method, test_spec, label) 228 }, 229 ) 230 231 t.Run("options", 232 func(st *testing.T) { 233 do_check_options(st, this_method, test_spec, label) 234 }, 235 ) 236 237 defined_in := test_spec["defined_in"].(string) 238 if file_name := this_method["defined_in"].(string); file_name != defined_in { 239 t.Errorf("incorrect file name in `defined_in` field: got %q, "+ 240 "expected %q", this_method["defined_in"].(string), defined_in) 241 } 242 243 } 244 245 func check_req_resp( 246 t *testing.T, 247 this_method map[string]any, 248 test_spec map[string]any, 249 label string, 250 ) { 251 exp_request_type := test_spec["request_type"].(string) 252 got_request_type := this_method["request_type"].(string) 253 if got_request_type != exp_request_type { 254 t.Errorf("incorrect request type for %s: got %q, expected %q", label, 255 got_request_type, exp_request_type) 256 } 257 exp_request_type = test_spec["request_full_type"].(string) 258 got_request_type = this_method["request_full_type"].(string) 259 if got_request_type != exp_request_type { 260 t.Errorf("incorrect request full type for %s: got %q, expected %q", 261 label, got_request_type, exp_request_type) 262 } 263 264 exp_response_type := test_spec["response_type"].(string) 265 got_response_type := this_method["response_type"].(string) 266 if got_response_type != exp_response_type { 267 t.Errorf("incorrect response type for %s: got %q, expected %q", 268 label, got_response_type, exp_response_type) 269 } 270 271 exp_response_type = test_spec["response_full_type"].(string) 272 got_response_type = this_method["response_full_type"].(string) 273 if got_response_type != exp_response_type { 274 t.Errorf("incorrect response full type for %s: got %q, expected %q", 275 label, got_response_type, exp_response_type) 276 } 277 } 278 279 // Performs unit tests for service methods. Returns true if all tests pass. 280 // Returns false, otherwise. 281 func check_methods(t *testing.T, svc map[string]any) { 282 methods := svc["methods"].([]any) 283 if len(methods) != 2 { 284 t.Fatalf("got %d methods, expected 2", len(methods)) 285 } 286 287 method_test_spec := []map[string]any{ 288 { 289 "name": "RunTestV2", 290 "full_name": "MyServices.Service.Tester.RunTestV2", 291 "desc": "Leading comment for the RunTestV2 method which is marked not_implemented\n via a custom option method_not_implemented.", 292 "defined_in": "service-tester.proto", 293 "options": map[string]any{ 294 "deprecated": false, 295 }, 296 "custom_options": map[string]any{ 297 "method_not_implemented": true, 298 }, 299 "request_type": "TesterRequest", 300 "request_full_type": "MyServices.Tester.TesterRequest", 301 "response_type": "TesterResponse", 302 "response_full_type": "MyServices.Tester.TesterResponse", 303 }, 304 305 { 306 "name": "RunTest", 307 "full_name": "MyServices.Service.Tester.RunTest", 308 "desc": "Leading comment for the RunTest method is which marked deprecated.", 309 "defined_in": "service-tester.proto", 310 "options": map[string]any{ 311 "deprecated": true, 312 }, 313 "custom_options": map[string]any{}, 314 "request_type": "TesterRequest", 315 "request_full_type": "MyServices.Tester.TesterRequest", 316 "response_type": "TesterResponse", 317 "response_full_type": "MyServices.Tester.TesterResponse", 318 }, 319 } 320 321 for i, this_method_any := range methods { 322 this_method := this_method_any.(map[string]any) 323 this_test_spec := method_test_spec[i] 324 this_method_label := fmt.Sprintf("method %s", 325 this_method["name"].(string)) 326 327 t.Run(this_method_label, func(st *testing.T) { 328 check_one_method(st, this_method, this_test_spec, this_method_label) 329 }) 330 } 331 } 332 333 func do_check_options( 334 t *testing.T, 335 data map[string]any, 336 test_spec map[string]any, 337 label string, 338 ) { 339 // Check standard options. 340 std_options := data["options"].(map[string]any) 341 std_options_test := test_spec["options"].(map[string]any) 342 for _, opt_name := range get_sorted_keys(std_options_test) { 343 opt_val_exp := std_options_test[opt_name] 344 opt_val_got := std_options[opt_name] 345 if !reflect.DeepEqual(opt_val_exp, opt_val_got) { 346 t.Logf("incorrect value for standard option %q in %s: "+ 347 "got %v, expected %v", label, opt_name, opt_val_got, 348 opt_val_exp) 349 } 350 } 351 352 // Check custom options. 353 cust_options := data["custom_options"].(map[string]any) 354 cust_options_test := test_spec["custom_options"].(map[string]any) 355 for _, opt_name := range get_sorted_keys(cust_options_test) { 356 opt_val_exp := cust_options_test[opt_name] 357 opt_val_got := cust_options[opt_name] 358 if !reflect.DeepEqual(opt_val_exp, opt_val_got) { 359 t.Logf("incorrect value for custom option %q in %s: "+ 360 "got %v, expected %v", label, opt_name, opt_val_got, 361 opt_val_exp) 362 } 363 } 364 } 365 366 func check_one_file( 367 t *testing.T, 368 this_file, test_spec map[string]any, 369 label string, 370 ) { 371 check_extensions(t, this_file, test_spec, label) 372 do_check_options(t, this_file, test_spec, label) 373 field_check_list := []string{ 374 "version", "name", "package", "defined_in", 375 } 376 check_fields_equal(t, this_file, test_spec, label, field_check_list) 377 } 378 379 func do_check_files(t *testing.T, data map[string]any) { 380 file_test_spec := map[string]any{ 381 "service-tester.proto": map[string]any{ 382 "extensions": []map[string]any{}, 383 "name": "service-tester.proto", 384 "package": "MyServices.Service", 385 "messages": []map[string]any{}, 386 "enums": []map[string]any{}, 387 "options": map[string]any{ 388 "java_package": "myservices.service", 389 "java_outer_classname": "", 390 "java_multiple_files": false, 391 "java_string_check_utf8": false, 392 "go_package": "myservices/grpc/service", 393 "deprecated": false, 394 "cc_enable_arenas": true, 395 "objc_class_prefix": "", 396 "csharp_namespace": "", 397 "swift_prefix": "", 398 "php_class_prefix": "", 399 "php_namespace": "", 400 "php_metadata_namespace": "", 401 "ruby_package": "", 402 }, 403 "custom_options": map[string]any{}, 404 }, 405 "tester.proto": map[string]any{ 406 "description": "Tester main structure, TesterRequest.", 407 "leading_comments": "Tester main structure, TesterRequest.", 408 "trailing_comments": "", 409 "leading_detached_comments": map[string]any{}, 410 "name": "tester.proto", 411 "package": "MyServices.Tester", 412 "options": map[string]any{ 413 "java_package": "Myservices.tester", 414 "java_outer_classname": "", 415 "java_multiple_files": false, 416 "java_string_check_utf8": false, 417 "go_package": "Myservices/grpc/tester", 418 "deprecated": false, 419 "cc_enable_arenas": true, 420 "objc_class_prefix": "", 421 "csharp_namespace": "", 422 "swift_prefix": "", 423 "php_class_prefix": "", 424 "php_namespace": "", 425 "php_metadata_namespace": "", 426 "ruby_package": "", 427 }, 428 "custom_options": map[string]any{ 429 "file_deprecated": true, 430 "file_double": float64(5643343.423), 431 "file_float": float64(0.569), 432 "file_int64": float64(-343434343), 433 "file_mnemonic": "some random name", 434 }, 435 "extensions": []map[string]any{ 436 { 437 "description": "Leading comment for custom option field_required.", 438 "leading_comments": "Leading comment for custom option field_required.", 439 "trailing_comments": "", 440 "leading_detached_comments": []any{}, 441 "name": "field_required", 442 "full_name": "MyServices.Tester.field_required", 443 "field_number": float64(51234), 444 "type": "bool", 445 "extendee": ".google.protobuf.FieldOptions", 446 "defined_in": "tester.proto", 447 }, 448 { 449 "description": "Trailing for method_not_implemented.", 450 "leading_comments": "", 451 "trailing_comments": "Trailing for method_not_implemented.", 452 "leading_detached_comments": []any{}, 453 "name": "method_not_implemented", 454 "full_name": "MyServices.Tester.method_not_implemented", 455 "field_number": float64(51235), 456 "type": "bool", 457 "extendee": ".google.protobuf.MethodOptions", 458 "defined_in": "tester.proto", 459 }, 460 { 461 "description": "", 462 "leading_comments": "", 463 "trailing_comments": "", 464 "leading_detached_comments": []any{}, 465 "name": "message_not_implemented", 466 "full_name": "MyServices.Tester.message_not_implemented", 467 "field_number": float64(51236), 468 "type": "bool", 469 "extendee": ".google.protobuf.MessageOptions", 470 "defined_in": "tester.proto", 471 }, 472 { 473 "description": "", 474 "leading_comments": "", 475 "trailing_comments": "", 476 "leading_detached_comments": []any{}, 477 "name": "service_not_implemented", 478 "full_name": "MyServices.Tester.service_not_implemented", 479 "field_number": float64(51237), 480 "type": "bool", 481 "extendee": ".google.protobuf.ServiceOptions", 482 "defined_in": "tester.proto", 483 }, 484 { 485 "description": "", 486 "leading_comments": "", 487 "trailing_comments": "", 488 "leading_detached_comments": []any{}, 489 "name": "file_deprecated", 490 "full_name": "MyServices.Tester.file_deprecated", 491 "field_number": float64(51238), 492 "type": "bool", 493 "extendee": ".google.protobuf.FileOptions", 494 "defined_in": "tester.proto", 495 }, 496 { 497 "description": "", 498 "leading_comments": "", 499 "trailing_comments": "", 500 "leading_detached_comments": []any{}, 501 "name": "file_mnemonic", 502 "full_name": "MyServices.Tester.file_mnemonic", 503 "field_number": float64(51240), 504 "type": "string", 505 "extendee": ".google.protobuf.FileOptions", 506 "defined_in": "tester.proto", 507 }, 508 { 509 "description": "", 510 "leading_comments": "", 511 "trailing_comments": "", 512 "leading_detached_comments": []any{}, 513 "name": "file_double", 514 "full_name": "MyServices.Tester.file_double", 515 "field_number": float64(51241), 516 "type": "float", 517 "extendee": ".google.protobuf.FileOptions", 518 "defined_in": "tester.proto", 519 }, 520 { 521 "description": "", 522 "leading_comments": "", 523 "trailing_comments": "", 524 "leading_detached_comments": []any{}, 525 "name": "file_float", 526 "full_name": "MyServices.Tester.file_float", 527 "field_number": float64(51242), 528 "type": "float", 529 "extendee": ".google.protobuf.FileOptions", 530 "defined_in": "tester.proto", 531 }, 532 { 533 "description": "", 534 "leading_comments": "", 535 "trailing_comments": "", 536 "leading_detached_comments": []any{}, 537 "name": "file_int64", 538 "full_name": "MyServices.Tester.file_int64", 539 "field_number": float64(51243), 540 "type": "float", 541 "extendee": ".google.protobuf.FileOptions", 542 "defined_in": "tester.proto", 543 }, 544 { 545 "description": "", 546 "leading_comments": "", 547 "trailing_comments": "", 548 "leading_detached_comments": []any{}, 549 "name": "enum_deprecated", 550 "full_name": "MyServices.Tester.enum_deprecated", 551 "field_number": float64(51239), 552 "type": "bool", 553 "extendee": ".google.protobuf.EnumOptions", 554 "defined_in": "tester.proto", 555 }, 556 }, 557 }, 558 "subdir/docstuff.proto": map[string]any{ 559 "extensions": []map[string]any{}, 560 "name": "subdir/docstuff.proto", 561 "package": "MyServices.Tester.Subdir", 562 "options": map[string]any{ 563 "java_package": "", 564 "java_outer_classname": "", 565 "java_multiple_files": false, 566 "java_string_check_utf8": false, 567 "go_package": "", 568 "deprecated": false, 569 "cc_enable_arenas": true, 570 "objc_class_prefix": "", 571 "csharp_namespace": "", 572 "swift_prefix": "", 573 "php_class_prefix": "", 574 "php_namespace": "", 575 "php_metadata_namespace": "", 576 "ruby_package": "", 577 }, 578 "custom_options": map[string]any{}, 579 "declared_custom_options": map[string]any{}, 580 }, 581 } 582 583 file_map := data["file_map"].(map[string]any) 584 file_names := get_sorted_keys(file_map) 585 t.Logf("file_names: %v", file_names) 586 for _, file_name := range file_names { 587 t.Logf("file_name: %s", file_name) 588 file_data := file_map[file_name].(map[string]any) 589 label := fmt.Sprintf("file %s", file_name) 590 t.Run(label, func(st *testing.T) { 591 check_one_file(st, file_data, 592 file_test_spec[file_name].(map[string]any), label) 593 }) 594 } 595 } 596 597 func do_check_messages(t *testing.T, data map[string]any) { 598 msg_map := data["message_map"].(map[string]any) 599 msg_full_name := "MyServices.Tester.TesterRequest" 600 msg := msg_map[msg_full_name].(map[string]any) 601 fields := msg["fields"].([]any) 602 603 exp_msg_comments := map[string]any{ 604 "description": "Tester main structure, TesterRequest.", 605 "leading_comments": "Tester main structure, TesterRequest.", 606 "trailing_comments": "", 607 "leading_detached_comments": []string{}, 608 } 609 610 check_comments(t, msg, exp_msg_comments, 611 fmt.Sprintf("msg %s", msg_full_name)) 612 613 field := fields[0].(map[string]any) 614 615 options := field["options"] 616 custom_options := field["custom_options"] 617 exp_field_comments := map[string]any{ 618 "description": "Leading comment for the client_info field.", 619 "leading_comments": "Leading comment for the client_info field.", 620 "trailing_comments": "", 621 "leading_detached_comments": []string{}, 622 } 623 624 check_comments(t, field, exp_field_comments, 625 fmt.Sprintf("field number %d of %s", 626 field["field_number"], msg_full_name)) 627 628 exp_options := map[string]any{ 629 "ctype": 0, 630 "packed": false, 631 "jstype": 0, 632 "lazy": false, 633 "deprecated": false, 634 } 635 exp_custom_options := map[string]any{ 636 "field_required": true, 637 } 638 639 if !reflect.DeepEqual(options, exp_options) { 640 t.Logf("options in first field of %s incorrect: "+ 641 "got %v, expected %v", msg_full_name, options, exp_options) 642 } 643 644 if !reflect.DeepEqual(custom_options, exp_custom_options) { 645 t.Logf("custom options in first field of %s incorrect: "+ 646 "got %v, expected %v", msg_full_name, custom_options, 647 exp_custom_options) 648 } 649 } 650 651 func check_extensions( 652 t *testing.T, data map[string]any, 653 test_spec map[string]any, 654 label string, 655 ) { 656 if data["extensions"] == nil { 657 return 658 } 659 extension_list_any := data["extensions"].([]any) 660 ext_spec_list := test_spec["extensions"].([]map[string]any) 661 662 extension_map := make(map[string]map[string]any, len(extension_list_any)) 663 for _, ext_any := range extension_list_any { 664 ext := ext_any.(map[string]any) 665 name := ext["full_name"].(string) 666 extension_map[name] = ext 667 } 668 669 if len(extension_map) != len(ext_spec_list) { 670 t.Fatalf("mismatch in number of found extensions vs expected in %s: "+ 671 "found %d, expected %d", label, len(extension_map), 672 len(ext_spec_list)) 673 } 674 675 for _, ext_spec := range ext_spec_list { 676 ext_name := ext_spec["full_name"].(string) 677 this_label := fmt.Sprintf("extension %s in %s", ext_name, label) 678 ext := extension_map[ext_name] 679 if ext == nil { 680 t.Fatalf("missing %s", this_label) 681 } 682 683 t.Run(this_label, func(st *testing.T) { 684 check_one_extension(st, ext, ext_spec, this_label) 685 }) 686 687 } 688 } 689 690 func check_one_extension( 691 t *testing.T, 692 ext, ext_test_spec map[string]any, 693 label string, 694 ) { 695 check_fields_equal(t, ext, ext_test_spec, label, nil) 696 } 697 698 func check_fields_equal( 699 t *testing.T, 700 data map[string]any, 701 test_spec map[string]any, 702 label string, 703 field_list []string) { 704 705 if len(field_list) == 0 { 706 field_list = get_sorted_keys(test_spec) 707 } 708 709 for _, field := range field_list { 710 if !reflect.DeepEqual(data[field], test_spec[field]) { 711 t.Errorf("discrepancy with field %s: got %v (%T), expected %v (%T)", 712 field, data[field], data[field], test_spec[field], test_spec[field]) 713 } 714 } 715 } 716 717 func do_check_enums(t *testing.T, data map[string]any) { 718 expected := map[string]any{ 719 "leading_comments": "Leading comment for enum TesterError.", 720 "trailing_comments": "", 721 "leading_detached_comments": []string{ 722 "Detached leading comment for enum TesterError", 723 }, 724 "name": "TesterError", 725 "full_name": "MyServices.Tester.TesterError", 726 "description": "Leading comment for enum TesterError.", 727 "values": []map[string]any{ 728 { 729 "description": "Leading comment for enum value NONE.", 730 "leading_comments": "Leading comment for enum value NONE.", 731 "trailing_comments": "", 732 "leading_detached_comments": []string{}, 733 "Name": "NONE", 734 "Number": 0, 735 "options": map[string]any{ 736 "deprecated": false, 737 }, 738 "custom_options": map[string]any{}, 739 }, 740 }, 741 "options": map[string]any{ 742 "allow_alias": false, 743 "deprecated": false, 744 }, 745 "custom_options": map[string]any{ 746 "enum_deprecated": true, 747 }, 748 "defined_in": "tester.proto", 749 } 750 751 enum_map := data["enum_map"].(map[string]any) 752 this_enum := enum_map["MyServices.Tester.TesterError"].(map[string]any) 753 754 check_comments(t, this_enum, expected, "enum MyServices.Tester.TesterError") 755 756 enum_vals := this_enum["values"].([]any) 757 if len(enum_vals) != 3 { 758 t.Errorf("incorrect number of values for enum "+ 759 "MyServices.Tester.TesterError: got %d, expected 3", 760 len(enum_vals)) 761 } 762 763 enum_val_0 := enum_vals[0].(map[string]any) 764 expected_enum_vals := expected["values"].([]map[string]any) 765 exp_val_0 := expected_enum_vals[0] 766 767 check_comments(t, enum_val_0, exp_val_0, 768 "val 0 from enum MyServices.Tester.TesterError") 769 770 do_check_options(t, this_enum, expected, 771 "options for enum MyServices.Tester.TesterError") 772 773 } 774 775 func do_check_syntax(t *testing.T, data map[string]any) { 776 file_map, ok := data["file_map"].(map[string]any) 777 if !ok { 778 t.Errorf("Wrong type for file_map: %T", file_map) 779 return 780 } 781 782 file_name := "tester.proto" 783 tester_file, ok := file_map[file_name].(map[string]any) 784 if !ok { 785 t.Errorf("Wrong type for %q info: %T", file_name, file_map[file_name]) 786 return 787 } 788 789 syntax_data, ok := tester_file["syntax"].(map[string]any) 790 if !ok { 791 t.Errorf("Wrong type for syntax_data: %T", tester_file["syntax"]) 792 return 793 } 794 795 field_desc := fmt.Sprintf("syntax (in %q)", file_name) 796 797 test_spec_map := map[string]any{ 798 "leading_comments": "This is the syntax statement leading comment.", 799 "trailing_comments": "This is a trailing comment for syntax.", 800 "description": "This is the syntax statement leading comment. This is a trailing comment for syntax.", 801 "leading_detached_comments": []string{"Leading detached comment for the syntax statement.\n A second line."}, 802 } 803 804 check_comments(t, syntax_data, test_spec_map, field_desc) 805 } 806 807 func check_comments( 808 t *testing.T, 809 data, test_spec map[string]any, 810 label string, 811 ) { 812 check_comment_field(t, data, label, "leading_comments", 813 test_spec) 814 check_comment_field(t, data, label, "trailing_comments", 815 test_spec) 816 check_comment_field(t, data, label, "description", 817 test_spec) 818 check_comment_list(t, data, label, "leading_detached_comments", 819 test_spec) 820 } 821 822 func check_comment_list( 823 t *testing.T, data map[string]any, 824 field_desc, field_name string, 825 test_spec map[string]any, 826 ) { 827 comment_list, ok := data[field_name].([]any) 828 if !ok { 829 t.Errorf("Wrong type for %s %s field: %T", field_desc, field_name, 830 data[field_name]) 831 } 832 expected := test_spec[field_name].([]string) 833 if len(expected) != len(comment_list) { 834 t.Errorf("Wrong length for %s %s list: got %d, expected %d", 835 field_desc, field_name, len(comment_list), len(expected)) 836 return 837 } 838 839 for i, exp_val := range expected { 840 comment, ok := comment_list[i].(string) 841 if !ok { 842 t.Errorf("Wrong type for comment list index %d for %s %s: %T", 843 i, field_desc, field_name, comment_list[i]) 844 return 845 } 846 847 if comment != exp_val { 848 t.Errorf("Incorrect comment at index %d for %s %s: got %q, "+ 849 "expected %q", i, field_desc, field_name, comment, exp_val) 850 } 851 } 852 } 853 854 func check_comment_field( 855 t *testing.T, 856 data map[string]any, 857 label, field_name string, 858 test_spec map[string]any, 859 ) { 860 comment, ok := data[field_name].(string) 861 if !ok { 862 t.Errorf("Wrong type for %s %s field: %T", label, 863 field_name, data[field_name]) 864 return 865 } 866 867 expected := test_spec[field_name].(string) 868 if comment != expected { 869 t.Errorf("comment did not match for %s %s field. Got %q, expected %q.", 870 label, field_name, comment, expected) 871 return 872 } 873 } 874 875 func do_len_checks(t *testing.T, data map[string]any) { 876 if !check_length(t, data, "file_name_list", "file_map", 3) { 877 return 878 } 879 if !check_length(t, data, "message_name_list", "message_map", 7) { 880 return 881 } 882 if !check_length(t, data, "service_name_list", "service_map", 1) { 883 return 884 } 885 if !check_length(t, data, "enum_name_list", "enum_map", 2) { 886 return 887 } 888 if !check_length(t, data, "extension_name_list", "extension_map", 10) { 889 return 890 } 891 892 } 893 894 func get_sorted_keys(data map[string]any) []string { 895 keys := make([]string, 0, len(data)) 896 for key := range data { 897 keys = append(keys, key) 898 } 899 900 sort.Strings(keys) 901 return keys 902 } 903 904 func check_length( 905 t *testing.T, 906 data map[string]any, 907 list_field_name, map_field_name string, 908 expected_len int, 909 ) bool { 910 name_list, ok := data[list_field_name].([]any) 911 if !ok { 912 t.Errorf("wrong type for %s: %T", list_field_name, 913 data[list_field_name]) 914 return false 915 } 916 if len(name_list) != expected_len { 917 t.Errorf("wrong length for %s: got %d, expected %d: list=%v", 918 list_field_name, len(name_list), expected_len, name_list) 919 return false 920 } 921 922 name_map, ok := data[map_field_name].(map[string]any) 923 if !ok { 924 t.Errorf("wrong type for %s: %T", map_field_name, data[map_field_name]) 925 return false 926 } 927 if len(name_map) != len(name_list) { 928 t.Errorf("length of %s does not match that of %s: %T", 929 list_field_name, map_field_name, len(name_map)) 930 return false 931 } 932 933 return true 934 } 935 936 func do_setup(t *testing.T) (map[string]any, bool) { 937 cur_dir, err := os.Getwd() 938 if err != nil { 939 t.Errorf("couldn't get working directory: %s", err) 940 return nil, false 941 } 942 defer os.Chdir(cur_dir) 943 944 work_dir := path.Join(cur_dir, "data/proto1") 945 proto_dir := work_dir 946 bin_dir := path.Join(cur_dir, "../cmd/protoc-gen-docjson") 947 out_file_name := "docs.json" 948 949 // Could also use t.TempDir() here, which would get cleaned up automatically 950 // once the test is complete. 951 json_out_dir, err := os.MkdirTemp("", "test_protoc-gen-docjson_*") 952 if err != nil { 953 t.Errorf("couldn't create temporary directory: %s", err) 954 return nil, false 955 } 956 defer os.RemoveAll(json_out_dir) 957 958 json_out_path := path.Join(json_out_dir, out_file_name) 959 960 cmd := "/usr/bin/env" 961 args := []string{ 962 fmt.Sprintf("PATH=%s:%s", bin_dir, os.Getenv("PATH")), 963 "protoc", 964 fmt.Sprintf("--docjson_out=%s", json_out_dir), 965 fmt.Sprintf("--docjson_opt=outfile=%s,proto=%s", 966 out_file_name, proto_dir), 967 fmt.Sprintf("-I%s", proto_dir), 968 "tester.proto", "service-tester.proto", "subdir/docstuff.proto", 969 } 970 971 os.Chdir(work_dir) 972 t.Logf("running cmd %s %s", cmd, args) 973 if err := exec.Command(cmd, args...).Run(); err != nil { 974 t.Errorf("protobuf compiler failed: %s", err) 975 return nil, false 976 } 977 978 in_fh, err := os.Open(json_out_path) 979 if err != nil { 980 t.Errorf("couldn't open JSON file %q: %s", json_out_path, err) 981 return nil, false 982 } 983 defer in_fh.Close() 984 985 json_bytes, err := io.ReadAll(in_fh) 986 if err != nil { 987 t.Errorf("couldn't read JSON data from %q: %s", json_out_path, err) 988 return nil, false 989 } 990 991 // Checkout JSON against expected output. 992 data := make(map[string]any) 993 err = json.Unmarshal(json_bytes, &data) 994 if err != nil { 995 t.Errorf("couldn't unmarshal JSON file into data structure: %s", err) 996 return nil, false 997 } 998 999 return data, true 1000 }