github.com/Zenithar/prototool@v1.3.0/internal/cmd/cmd_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package cmd 22 23 import ( 24 "bytes" 25 "context" 26 "fmt" 27 "io" 28 "io/ioutil" 29 "net" 30 "os" 31 "path/filepath" 32 "regexp" 33 "sort" 34 "strings" 35 "sync" 36 "testing" 37 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40 "github.com/uber/prototool/internal/cmd/testdata/grpc/gen/grpcpb" 41 "github.com/uber/prototool/internal/lint" 42 "github.com/uber/prototool/internal/settings" 43 "github.com/uber/prototool/internal/vars" 44 "google.golang.org/grpc" 45 ) 46 47 var ( 48 // testLock is to lock around prototool download in testDownload 49 testLock sync.Mutex 50 ) 51 52 func TestCompile(t *testing.T) { 53 t.Parallel() 54 assertDoCompileFiles( 55 t, 56 false, 57 false, 58 `testdata/compile/errors_on_import/dep_errors.proto:6:1:Expected ";".`, 59 "testdata/compile/errors_on_import/dep_errors.proto", 60 ) 61 assertDoCompileFiles( 62 t, 63 false, 64 false, 65 `testdata/compile/errors_on_import/dep_errors.proto:6:1:Expected ";".`, 66 "testdata/compile/errors_on_import", 67 ) 68 assertDoCompileFiles( 69 t, 70 false, 71 false, 72 `testdata/compile/extra_import/extra_import.proto:1:1:Import "dep.proto" was not used.`, 73 "testdata/compile/extra_import/extra_import.proto", 74 ) 75 assertDoCompileFiles( 76 t, 77 false, 78 false, 79 `testdata/compile/json/json_camel_case_conflict.proto:1:1:The JSON camel-case name of field "helloworld" conflicts with field "helloWorld". This is not allowed in proto3.`, 80 "testdata/compile/json/json_camel_case_conflict.proto", 81 ) 82 assertDoCompileFiles( 83 t, 84 false, 85 false, 86 `testdata/compile/semicolon/missing_package_semicolon.proto:5:1:Expected ";".`, 87 "testdata/compile/semicolon/missing_package_semicolon.proto", 88 ) 89 assertDoCompileFiles( 90 t, 91 false, 92 false, 93 `testdata/compile/syntax/missing_syntax.proto:1:1:No syntax specified. Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. 94 testdata/compile/syntax/missing_syntax.proto:4:3:Expected "required", "optional", or "repeated".`, 95 "testdata/compile/syntax/missing_syntax.proto", 96 ) 97 assertDoCompileFiles( 98 t, 99 true, 100 false, 101 ``, 102 "testdata/compile/proto2/syntax_proto2.proto", 103 ) 104 assertDoCompileFiles( 105 t, 106 false, 107 false, 108 `testdata/compile/notimported/not_imported.proto:11:3:"foo.Dep" seems to be defined in "dep.proto", which is not imported by "not_imported.proto". To use it here, please add the necessary import.`, 109 "testdata/compile/notimported/not_imported.proto", 110 ) 111 assertDoCompileFiles( 112 t, 113 false, 114 true, 115 `{"filename":"testdata/compile/errors_on_import/dep_errors.proto","line":6,"column":1,"message":"Expected \";\"."}`, 116 "testdata/compile/errors_on_import/dep_errors.proto", 117 ) 118 } 119 120 func TestInit(t *testing.T) { 121 t.Parallel() 122 123 tmpDir, err := ioutil.TempDir("", "") 124 require.NoError(t, err) 125 require.NotEmpty(t, tmpDir) 126 defer func() { 127 _ = os.RemoveAll(tmpDir) 128 }() 129 130 assertDo(t, 0, "", "config", "init", tmpDir) 131 assertDo(t, 1, fmt.Sprintf("%s already exists", filepath.Join(tmpDir, settings.DefaultConfigFilename)), "config", "init", tmpDir) 132 } 133 134 func TestLint(t *testing.T) { 135 t.Parallel() 136 assertDoLintFile( 137 t, 138 true, 139 "", 140 "testdata/foo/success.proto", 141 ) 142 assertDoLintFile( 143 t, 144 false, 145 "1:1:SYNTAX_PROTO3", 146 "testdata/lint/syntaxproto2/syntax_proto2.proto", 147 ) 148 assertDoLintFile( 149 t, 150 false, 151 "11:1:MESSAGE_NAMES_CAPITALIZED", 152 "testdata/lint/capitalized/message_name_not_capitalized.proto", 153 ) 154 assertDoLintFile( 155 t, 156 false, 157 `1:1:FILE_OPTIONS_REQUIRE_GO_PACKAGE 158 1:1:FILE_OPTIONS_REQUIRE_JAVA_MULTIPLE_FILES 159 1:1:FILE_OPTIONS_REQUIRE_JAVA_OUTER_CLASSNAME 160 1:1:FILE_OPTIONS_REQUIRE_JAVA_PACKAGE`, 161 "testdata/lint/required/file_options_required.proto", 162 ) 163 assertDoLintFile( 164 t, 165 false, 166 `1:1:FILE_OPTIONS_REQUIRE_GO_PACKAGE 167 1:1:FILE_OPTIONS_REQUIRE_JAVA_MULTIPLE_FILES 168 1:1:FILE_OPTIONS_REQUIRE_JAVA_OUTER_CLASSNAME 169 1:1:FILE_OPTIONS_REQUIRE_JAVA_PACKAGE 170 1:1:PACKAGE_IS_DECLARED`, 171 "testdata/lint/base/base_file.proto", 172 ) 173 assertDoLintFile( 174 t, 175 false, 176 `5:1:FILE_OPTIONS_EQUAL_GO_PACKAGE_PB_SUFFIX 177 6:1:FILE_OPTIONS_EQUAL_JAVA_MULTIPLE_FILES_TRUE 178 7:1:FILE_OPTIONS_EQUAL_JAVA_OUTER_CLASSNAME_PROTO_SUFFIX 179 8:1:FILE_OPTIONS_EQUAL_JAVA_PACKAGE_COM_PREFIX`, 180 "testdata/lint/fileoptions/file_options_incorrect.proto", 181 ) 182 assertDoLintFiles( 183 t, 184 false, 185 `testdata/lint/samedir/bar1.proto:1:1:PACKAGES_SAME_IN_DIR 186 testdata/lint/samedir/foo1.proto:1:1:PACKAGES_SAME_IN_DIR 187 testdata/lint/samedir/foo2.proto:1:1:PACKAGES_SAME_IN_DIR`, 188 "testdata/lint/samedir", 189 ) 190 assertDoLintFiles( 191 t, 192 false, 193 `testdata/lint/samedirgopkg/bar1.proto:1:1:FILE_OPTIONS_GO_PACKAGE_SAME_IN_DIR 194 testdata/lint/samedirgopkg/foo1.proto:1:1:FILE_OPTIONS_GO_PACKAGE_SAME_IN_DIR 195 testdata/lint/samedirgopkg/foo2.proto:1:1:FILE_OPTIONS_GO_PACKAGE_SAME_IN_DIR`, 196 "testdata/lint/samedirgopkg", 197 ) 198 assertDoLintFiles( 199 t, 200 false, 201 `testdata/lint/samedirjavapkg/bar1.proto:1:1:FILE_OPTIONS_JAVA_PACKAGE_SAME_IN_DIR 202 testdata/lint/samedirjavapkg/foo1.proto:1:1:FILE_OPTIONS_JAVA_PACKAGE_SAME_IN_DIR 203 testdata/lint/samedirjavapkg/foo2.proto:1:1:FILE_OPTIONS_JAVA_PACKAGE_SAME_IN_DIR`, 204 "testdata/lint/samedirjavapkg", 205 ) 206 assertDoLintFile( 207 t, 208 false, 209 `1:1:FILE_OPTIONS_REQUIRE_GO_PACKAGE 210 1:1:FILE_OPTIONS_REQUIRE_JAVA_MULTIPLE_FILES 211 1:1:FILE_OPTIONS_REQUIRE_JAVA_OUTER_CLASSNAME 212 1:1:FILE_OPTIONS_REQUIRE_JAVA_PACKAGE 213 3:1:PACKAGE_LOWER_SNAKE_CASE 214 7:1:MESSAGE_NAMES_CAPITALIZED 215 9:1:MESSAGE_NAMES_CAMEL_CASE 216 12:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 217 13:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 218 14:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 219 15:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 220 22:3:COMMENTS_NO_C_STYLE 221 23:3:COMMENTS_NO_C_STYLE 222 26:1:SERVICE_NAMES_CAPITALIZED 223 28:1:SERVICE_NAMES_CAMEL_CASE 224 46:3:REQUEST_RESPONSE_TYPES_UNIQUE 225 46:3:REQUEST_RESPONSE_TYPES_UNIQUE 226 47:3:REQUEST_RESPONSE_TYPES_UNIQUE 227 47:3:REQUEST_RESPONSE_TYPES_UNIQUE 228 48:3:RPC_NAMES_CAPITALIZED 229 49:3:REQUEST_RESPONSE_TYPES_IN_SAME_FILE 230 49:3:REQUEST_RESPONSE_TYPES_UNIQUE 231 50:3:REQUEST_RESPONSE_TYPES_IN_SAME_FILE 232 50:3:REQUEST_RESPONSE_TYPES_IN_SAME_FILE 233 50:3:REQUEST_RESPONSE_TYPES_UNIQUE 234 58:3:ENUM_FIELD_PREFIXES 235 64:7:ENUM_FIELD_PREFIXES 236 64:7:ENUM_ZERO_VALUES_INVALID 237 67:7:ENUM_ZERO_VALUES_INVALID 238 73:3:ENUM_ZERO_VALUES_INVALID 239 76:1:COMMENTS_NO_C_STYLE 240 80:3:COMMENTS_NO_C_STYLE 241 82:3:COMMENTS_NO_C_STYLE 242 84:5:COMMENTS_NO_C_STYLE 243 90:3:ENUM_FIELD_NAMES_UPPER_SNAKE_CASE 244 93:1:ENUM_NAMES_CAMEL_CASE 245 98:3:ENUMS_NO_ALLOW_ALIAS 246 108:5:ENUMS_NO_ALLOW_ALIAS 247 `, 248 "testdata/lint/lots/lots.proto", 249 ) 250 assertDoLintFile( 251 t, 252 false, 253 `1:1:FILE_OPTIONS_REQUIRE_GO_PACKAGE 254 1:1:FILE_OPTIONS_REQUIRE_JAVA_MULTIPLE_FILES 255 1:1:FILE_OPTIONS_REQUIRE_JAVA_OUTER_CLASSNAME 256 1:1:FILE_OPTIONS_REQUIRE_JAVA_PACKAGE 257 3:1:PACKAGE_LOWER_SNAKE_CASE 258 7:1:MESSAGES_HAVE_COMMENTS 259 7:1:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 260 7:1:MESSAGE_NAMES_CAPITALIZED 261 9:1:MESSAGES_HAVE_COMMENTS 262 9:1:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 263 9:1:MESSAGE_NAMES_CAMEL_CASE 264 11:1:MESSAGES_HAVE_COMMENTS 265 11:1:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 266 12:3:MESSAGE_FIELD_NAMES_LOWERCASE 267 12:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 268 13:3:MESSAGE_FIELD_NAMES_LOWERCASE 269 13:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 270 14:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 271 15:3:MESSAGE_FIELD_NAMES_LOWER_SNAKE_CASE 272 22:3:COMMENTS_NO_C_STYLE 273 23:3:COMMENTS_NO_C_STYLE 274 26:1:SERVICES_HAVE_COMMENTS 275 26:1:SERVICE_NAMES_CAPITALIZED 276 28:1:SERVICES_HAVE_COMMENTS 277 28:1:SERVICE_NAMES_CAMEL_CASE 278 30:1:MESSAGES_HAVE_COMMENTS 279 31:1:MESSAGES_HAVE_COMMENTS 280 34:1:MESSAGES_HAVE_COMMENTS 281 36:5:MESSAGES_HAVE_COMMENTS 282 36:5:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 283 38:1:MESSAGES_HAVE_COMMENTS 284 40:1:MESSAGES_HAVE_COMMENTS 285 40:1:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 286 41:3:MESSAGES_HAVE_COMMENTS 287 44:1:SERVICES_HAVE_COMMENTS 288 45:3:RPCS_HAVE_COMMENTS 289 46:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 290 46:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 291 46:3:REQUEST_RESPONSE_TYPES_UNIQUE 292 46:3:REQUEST_RESPONSE_TYPES_UNIQUE 293 46:3:RPCS_HAVE_COMMENTS 294 47:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 295 47:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 296 47:3:REQUEST_RESPONSE_TYPES_UNIQUE 297 47:3:REQUEST_RESPONSE_TYPES_UNIQUE 298 47:3:RPCS_HAVE_COMMENTS 299 48:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 300 48:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 301 48:3:RPCS_HAVE_COMMENTS 302 48:3:RPC_NAMES_CAPITALIZED 303 49:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 304 49:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 305 49:3:REQUEST_RESPONSE_TYPES_IN_SAME_FILE 306 49:3:REQUEST_RESPONSE_TYPES_UNIQUE 307 49:3:RPCS_HAVE_COMMENTS 308 50:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 309 50:3:REQUEST_RESPONSE_NAMES_MATCH_RPC 310 50:3:REQUEST_RESPONSE_TYPES_IN_SAME_FILE 311 50:3:REQUEST_RESPONSE_TYPES_IN_SAME_FILE 312 50:3:REQUEST_RESPONSE_TYPES_UNIQUE 313 50:3:RPCS_HAVE_COMMENTS 314 53:1:ENUMS_HAVE_COMMENTS 315 58:3:ENUM_FIELD_PREFIXES 316 61:1:MESSAGES_HAVE_COMMENTS 317 61:1:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 318 62:3:MESSAGES_HAVE_COMMENTS 319 62:3:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 320 63:5:ENUMS_HAVE_COMMENTS 321 64:7:ENUM_FIELD_PREFIXES 322 64:7:ENUM_ZERO_VALUES_INVALID 323 66:5:ENUMS_HAVE_COMMENTS 324 67:7:ENUM_ZERO_VALUES_INVALID 325 72:1:ENUMS_HAVE_COMMENTS 326 73:3:ENUM_ZERO_VALUES_INVALID 327 76:1:COMMENTS_NO_C_STYLE 328 78:1:MESSAGES_HAVE_COMMENTS 329 78:1:MESSAGES_HAVE_COMMENTS_EXCEPT_REQUEST_RESPONSE_TYPES 330 80:3:COMMENTS_NO_C_STYLE 331 82:3:COMMENTS_NO_C_STYLE 332 84:5:COMMENTS_NO_C_STYLE 333 88:1:ENUMS_HAVE_COMMENTS 334 90:3:ENUM_FIELD_NAMES_UPPERCASE 335 90:3:ENUM_FIELD_NAMES_UPPER_SNAKE_CASE 336 93:1:ENUMS_HAVE_COMMENTS 337 93:1:ENUM_NAMES_CAMEL_CASE`, 338 "testdata/lint/allgroup/lots.proto", 339 ) 340 assertDoLintFile( 341 t, 342 false, 343 `1:1:FILE_OPTIONS_REQUIRE_GO_PACKAGE 344 1:1:FILE_OPTIONS_REQUIRE_JAVA_MULTIPLE_FILES 345 1:1:FILE_OPTIONS_REQUIRE_JAVA_OUTER_CLASSNAME 346 1:1:FILE_OPTIONS_REQUIRE_JAVA_PACKAGE`, 347 "testdata/lint/keyword/package_starts_with_keyword.proto", 348 ) 349 assertDoLintFile( 350 t, 351 false, 352 `5:1:FILE_OPTIONS_GO_PACKAGE_NOT_LONG_FORM`, 353 "testdata/lint/gopackagelongform/gopackagelongform.proto", 354 ) 355 } 356 357 func TestLintConfigDataOverride(t *testing.T) { 358 cwd, err := os.Getwd() 359 require.NoError(t, err) 360 require.NoError(t, os.Chdir("testdata/lint/gopackagelongform")) 361 defer func() { 362 require.NoError(t, os.Chdir(cwd)) 363 }() 364 assertDoLintFile( 365 t, 366 false, 367 `5:1:FILE_OPTIONS_GO_PACKAGE_NOT_LONG_FORM`, 368 "gopackagelongform.proto", 369 "--config-data", 370 `{"lint":{"rules":{"remove":["FILE_OPTIONS_EQUAL_GO_PACKAGE_PB_SUFFIX"]}}}`, 371 ) 372 assertDoLintFile( 373 t, 374 false, 375 `5:1:FILE_OPTIONS_EQUAL_GO_PACKAGE_PB_SUFFIX`, 376 "gopackagelongform.proto", 377 "--config-data", 378 `{"lint":{"rules":{"remove":["FILE_OPTIONS_GO_PACKAGE_NOT_LONG_FORM"]}}}`, 379 ) 380 assertDoLintFile( 381 t, 382 false, 383 `5:1:FILE_OPTIONS_EQUAL_GO_PACKAGE_PB_SUFFIX 384 5:1:FILE_OPTIONS_GO_PACKAGE_NOT_LONG_FORM`, 385 "gopackagelongform.proto", 386 "--config-data", 387 `{}`, 388 ) 389 assertExact( 390 t, 391 1, 392 `json: unknown field "unknown_key"`, 393 "lint", 394 "gopackagelongform.proto", 395 "--config-data", 396 `{"unknown_key":"foo"}`, 397 ) 398 } 399 400 func TestGoldenFormat(t *testing.T) { 401 t.Parallel() 402 assertGoldenFormat(t, false, false, "testdata/format/proto3/foo/bar/bar.proto") 403 assertGoldenFormat(t, false, false, "testdata/format/proto2/foo/bar/bar_proto2.proto") 404 assertGoldenFormat(t, false, false, "testdata/format/proto3/foo/foo.proto") 405 assertGoldenFormat(t, false, false, "testdata/format/proto2/foo/foo_proto2.proto") 406 assertGoldenFormat(t, false, true, "testdata/format-fix/foo.proto") 407 } 408 409 func TestJSONToBinaryToJSON(t *testing.T) { 410 t.Parallel() 411 assertJSONToBinaryToJSON(t, "testdata/foo/success.proto", "foo.Baz", `{"hello":100}`) 412 } 413 414 func TestCreate(t *testing.T) { 415 t.Parallel() 416 // package override with also matching shorter override "a" 417 // make sure uses "a/b" 418 assertDoCreateFile( 419 t, 420 true, 421 true, 422 "testdata/create/one/a/b/bar/baz.proto", 423 "", 424 `syntax = "proto3"; 425 426 package foo.bar; 427 428 option go_package = "barpb"; 429 option java_multiple_files = true; 430 option java_outer_classname = "BazProto"; 431 option java_package = "com.foo.bar";`, 432 ) 433 // create same file again but do not remove, should fail 434 assertDoCreateFile( 435 t, 436 false, // do not expect success 437 false, // do not remove 438 "testdata/create/one/a/b/bar/baz.proto", 439 "", 440 ``, 441 ) 442 // use the --package flag 443 assertDoCreateFile( 444 t, 445 true, 446 true, 447 "testdata/create/one/a/b/bar/baz.proto", 448 "bat", // --package value 449 `syntax = "proto3"; 450 451 package bat; 452 453 option go_package = "batpb"; 454 option java_multiple_files = true; 455 option java_outer_classname = "BazProto"; 456 option java_package = "com.bat";`, 457 ) 458 // package override but a shorter one "a" 459 assertDoCreateFile( 460 t, 461 true, 462 true, 463 "testdata/create/one/a/c/bar/baz.proto", 464 "", 465 `syntax = "proto3"; 466 467 package foobar.c.bar; 468 469 option go_package = "barpb"; 470 option java_multiple_files = true; 471 option java_outer_classname = "BazProto"; 472 option java_package = "com.foobar.c.bar";`, 473 ) 474 // no package override, do default b.c.bar 475 assertDoCreateFile( 476 t, 477 true, 478 true, 479 "testdata/create/one/b/c/bar/baz.proto", 480 "", 481 `syntax = "proto3"; 482 483 package b.c.bar; 484 485 option go_package = "barpb"; 486 option java_multiple_files = true; 487 option java_outer_classname = "BazProto"; 488 option java_package = "com.b.c.bar";`, 489 ) 490 // in dir with prototool.yaml, use default package 491 assertDoCreateFile( 492 t, 493 true, 494 true, 495 "testdata/create/one/baz.proto", 496 "", 497 `syntax = "proto3"; 498 499 package uber.prototool.generated; 500 501 option go_package = "generatedpb"; 502 option java_multiple_files = true; 503 option java_outer_classname = "BazProto"; 504 option java_package = "com.uber.prototool.generated";`, 505 ) 506 // in dir with prototool.yaml with override 507 assertDoCreateFile( 508 t, 509 true, 510 true, 511 "testdata/create/two/baz.proto", 512 "", 513 `syntax = "proto3"; 514 515 package foo; 516 517 option go_package = "foopb"; 518 option java_multiple_files = true; 519 option java_outer_classname = "BazProto"; 520 option java_package = "com.foo";`, 521 ) 522 } 523 524 func TestGRPC(t *testing.T) { 525 t.Parallel() 526 assertGRPC(t, 527 0, 528 ` 529 { 530 "value": "hello!" 531 } 532 `, 533 "testdata/grpc/grpc.proto", 534 "grpc.ExcitedService/Exclamation", 535 `{"value":"hello"}`, 536 ) 537 assertGRPC(t, 538 0, 539 ` 540 { 541 "value": "hellosalutations!" 542 } 543 `, 544 "testdata/grpc/grpc.proto", 545 "grpc.ExcitedService/ExclamationClientStream", 546 `{"value":"hello"} 547 {"value":"salutations"}`, 548 ) 549 assertGRPC(t, 550 0, 551 ` 552 { 553 "value": "h" 554 } 555 { 556 "value": "e" 557 } 558 { 559 "value": "l" 560 } 561 { 562 "value": "l" 563 } 564 { 565 "value": "o" 566 } 567 { 568 "value": "!" 569 } 570 `, 571 "testdata/grpc/grpc.proto", 572 "grpc.ExcitedService/ExclamationServerStream", 573 `{"value":"hello"}`, 574 ) 575 assertGRPC(t, 576 0, 577 ` 578 { 579 "value": "hello!" 580 } 581 { 582 "value": "salutations!" 583 } 584 `, 585 "testdata/grpc/grpc.proto", 586 "grpc.ExcitedService/ExclamationBidiStream", 587 `{"value":"hello"} 588 {"value":"salutations"}`, 589 ) 590 } 591 592 func TestVersion(t *testing.T) { 593 assertRegexp(t, 0, fmt.Sprintf("Version:.*%s\nDefault protoc version:.*%s\n", vars.Version, vars.DefaultProtocVersion), "version") 594 } 595 596 func TestVersionJSON(t *testing.T) { 597 assertRegexp(t, 0, fmt.Sprintf(`(?s){.*"version":.*"%s",.*"default_protoc_version":.*"%s".*}`, vars.Version, vars.DefaultProtocVersion), "version", "--json") 598 } 599 600 func TestListAllLintGroups(t *testing.T) { 601 assertExact(t, 0, "all\ndefault", "list-all-lint-groups") 602 } 603 604 func TestDescriptorProto(t *testing.T) { 605 assertExact( 606 t, 607 0, 608 `{ 609 "name": "Baz", 610 "field": [ 611 { 612 "name": "hello", 613 "number": 1, 614 "label": "LABEL_OPTIONAL", 615 "type": "TYPE_INT64", 616 "jsonName": "hello" 617 }, 618 { 619 "name": "dep", 620 "number": 2, 621 "label": "LABEL_OPTIONAL", 622 "type": "TYPE_MESSAGE", 623 "typeName": ".bar.Dep", 624 "jsonName": "dep" 625 }, 626 { 627 "name": "timestamp", 628 "number": 3, 629 "label": "LABEL_OPTIONAL", 630 "type": "TYPE_MESSAGE", 631 "typeName": ".google.protobuf.Timestamp", 632 "jsonName": "timestamp" 633 } 634 ] 635 }`, 636 "descriptor-proto", "testdata/foo/success.proto", "foo.Baz", 637 ) 638 } 639 640 func TestFieldDescriptorProto(t *testing.T) { 641 assertExact( 642 t, 643 0, 644 `{ 645 "name": "dep", 646 "number": 2, 647 "label": "LABEL_OPTIONAL", 648 "type": "TYPE_MESSAGE", 649 "typeName": ".bar.Dep", 650 "jsonName": "dep" 651 }`, 652 "field-descriptor-proto", "testdata/foo/success.proto", "foo.Baz.dep", 653 ) 654 } 655 656 func TestServiceDescriptorProto(t *testing.T) { 657 assertExact( 658 t, 659 0, 660 `{ 661 "name": "ExcitedService", 662 "method": [ 663 { 664 "name": "Exclamation", 665 "inputType": ".grpc.ExclamationRequest", 666 "outputType": ".grpc.ExclamationResponse", 667 "options": { 668 669 } 670 }, 671 { 672 "name": "ExclamationClientStream", 673 "inputType": ".grpc.ExclamationRequest", 674 "outputType": ".grpc.ExclamationResponse", 675 "options": { 676 677 }, 678 "clientStreaming": true 679 }, 680 { 681 "name": "ExclamationServerStream", 682 "inputType": ".grpc.ExclamationRequest", 683 "outputType": ".grpc.ExclamationResponse", 684 "options": { 685 686 }, 687 "serverStreaming": true 688 }, 689 { 690 "name": "ExclamationBidiStream", 691 "inputType": ".grpc.ExclamationRequest", 692 "outputType": ".grpc.ExclamationResponse", 693 "options": { 694 695 }, 696 "clientStreaming": true, 697 "serverStreaming": true 698 } 699 ] 700 }`, 701 "service-descriptor-proto", "testdata/grpc", "grpc.ExcitedService", 702 ) 703 } 704 705 func TestListLinters(t *testing.T) { 706 assertLinters(t, lint.DefaultLinters, "lint", "--list-linters") 707 } 708 709 func TestListAllLinters(t *testing.T) { 710 assertLinters(t, lint.AllLinters, "lint", "--list-all-linters") 711 } 712 713 func assertLinters(t *testing.T, linters []lint.Linter, args ...string) { 714 linterIDs := make([]string, 0, len(linters)) 715 for _, linter := range linters { 716 linterIDs = append(linterIDs, linter.ID()) 717 } 718 sort.Strings(linterIDs) 719 assertDo(t, 0, strings.Join(linterIDs, "\n"), args...) 720 } 721 722 func assertDoCompileFiles(t *testing.T, expectSuccess bool, asJSON bool, expectedLinePrefixes string, filePaths ...string) { 723 lines := getCleanLines(expectedLinePrefixes) 724 expectedExitCode := 0 725 if !expectSuccess { 726 expectedExitCode = 255 727 } 728 cmd := []string{"compile"} 729 if asJSON { 730 cmd = append(cmd, "--json") 731 } 732 assertDo(t, expectedExitCode, strings.Join(lines, "\n"), append(cmd, filePaths...)...) 733 } 734 735 func assertDoCreateFile(t *testing.T, expectSuccess bool, remove bool, filePath string, pkgOverride string, expectedFileData string) { 736 assert.NoError(t, os.MkdirAll(filepath.Dir(filePath), 0755)) 737 if remove { 738 _ = os.Remove(filePath) 739 } 740 args := []string{"create", filePath} 741 if pkgOverride != "" { 742 args = append(args, "--package", pkgOverride) 743 } 744 _, exitCode := testDo(t, args...) 745 if expectSuccess { 746 assert.Equal(t, 0, exitCode) 747 fileData, err := ioutil.ReadFile(filePath) 748 assert.NoError(t, err) 749 assert.Equal(t, expectedFileData, string(fileData)) 750 } else { 751 assert.NotEqual(t, 0, exitCode) 752 } 753 } 754 755 func assertDoLintFile(t *testing.T, expectSuccess bool, expectedLinePrefixesWithoutFile string, filePath string, args ...string) { 756 lines := getCleanLines(expectedLinePrefixesWithoutFile) 757 for i, line := range lines { 758 lines[i] = filePath + ":" + line 759 } 760 expectedExitCode := 0 761 if !expectSuccess { 762 expectedExitCode = 255 763 } 764 assertDo(t, expectedExitCode, strings.Join(lines, "\n"), append([]string{"lint", filePath}, args...)...) 765 } 766 767 func assertDoLintFiles(t *testing.T, expectSuccess bool, expectedLinePrefixes string, filePaths ...string) { 768 lines := getCleanLines(expectedLinePrefixes) 769 expectedExitCode := 0 770 if !expectSuccess { 771 expectedExitCode = 255 772 } 773 assertDo(t, expectedExitCode, strings.Join(lines, "\n"), append([]string{"lint"}, filePaths...)...) 774 } 775 776 func assertGoldenFormat(t *testing.T, expectSuccess bool, fix bool, filePath string) { 777 args := []string{"format"} 778 if fix { 779 args = append(args, "--fix") 780 } 781 args = append(args, filePath) 782 output, exitCode := testDo(t, args...) 783 expectedExitCode := 0 784 if !expectSuccess { 785 expectedExitCode = 255 786 } 787 assert.Equal(t, expectedExitCode, exitCode) 788 golden, err := ioutil.ReadFile(filePath + ".golden") 789 assert.NoError(t, err) 790 assert.Equal(t, strings.TrimSpace(string(golden)), output) 791 } 792 793 func assertJSONToBinaryToJSON(t *testing.T, filePath string, messagePath string, jsonData string) { 794 stdout, exitCode := testDo(t, "json-to-binary", filePath, messagePath, jsonData) 795 assert.Equal(t, 0, exitCode) 796 stdout, exitCode = testDo(t, "binary-to-json", filePath, messagePath, stdout) 797 assert.Equal(t, 0, exitCode) 798 assert.Equal(t, jsonData, stdout) 799 } 800 801 func assertGRPC(t *testing.T, expectedExitCode int, expectedLinePrefixes string, filePath string, method string, jsonData string) { 802 excitedTestCase := startExcitedTestCase(t) 803 defer excitedTestCase.Close() 804 assertDoStdin(t, strings.NewReader(jsonData), expectedExitCode, expectedLinePrefixes, "grpc", filePath, "--address", excitedTestCase.Address(), "--method", method, "--stdin") 805 } 806 807 func assertRegexp(t *testing.T, expectedExitCode int, expectedRegexp string, args ...string) { 808 stdout, exitCode := testDo(t, args...) 809 assert.Equal(t, expectedExitCode, exitCode) 810 matched, err := regexp.MatchString(expectedRegexp, stdout) 811 assert.NoError(t, err) 812 assert.True(t, matched, "Expected regex %s but got %s", expectedRegexp, stdout) 813 } 814 815 func assertExact(t *testing.T, expectedExitCode int, expectedStdout string, args ...string) { 816 stdout, exitCode := testDo(t, args...) 817 assert.Equal(t, expectedExitCode, exitCode) 818 assert.Equal(t, expectedStdout, stdout) 819 } 820 821 func assertDoStdin(t *testing.T, stdin io.Reader, expectedExitCode int, expectedLinePrefixes string, args ...string) { 822 assertDoInternal(t, stdin, expectedExitCode, expectedLinePrefixes, args...) 823 } 824 825 func assertDo(t *testing.T, expectedExitCode int, expectedLinePrefixes string, args ...string) { 826 assertDoInternal(t, nil, expectedExitCode, expectedLinePrefixes, args...) 827 } 828 829 func testDoStdin(t *testing.T, stdin io.Reader, args ...string) (string, int) { 830 testDownload(t) 831 return testDoInternal(stdin, args...) 832 } 833 834 func testDo(t *testing.T, args ...string) (string, int) { 835 testDownload(t) 836 return testDoInternal(nil, args...) 837 } 838 839 func getCleanLines(output string) []string { 840 var lines []string 841 for _, line := range strings.Split(strings.TrimSpace(output), "\n") { 842 line = strings.TrimSpace(line) 843 if line == "" { 844 continue 845 } 846 lines = append(lines, line) 847 } 848 return lines 849 } 850 851 type excitedTestCase struct { 852 listener net.Listener 853 grpcServer *grpc.Server 854 excitedServer *excitedServer 855 } 856 857 func startExcitedTestCase(t *testing.T) *excitedTestCase { 858 listener, err := getFreeListener() 859 require.NoError(t, err) 860 grpcServer := grpc.NewServer() 861 excitedServer := newExcitedServer() 862 grpcpb.RegisterExcitedServiceServer(grpcServer, excitedServer) 863 go func() { _ = grpcServer.Serve(listener) }() 864 return &excitedTestCase{ 865 listener: listener, 866 grpcServer: grpcServer, 867 excitedServer: excitedServer, 868 } 869 } 870 871 func (c *excitedTestCase) Address() string { 872 if c.listener == nil { 873 return "" 874 } 875 return c.listener.Addr().String() 876 } 877 878 func (c *excitedTestCase) Close() { 879 if c.grpcServer != nil { 880 c.grpcServer.Stop() 881 } 882 } 883 884 type excitedServer struct{} 885 886 func newExcitedServer() *excitedServer { 887 return &excitedServer{} 888 } 889 890 func (s *excitedServer) Exclamation(ctx context.Context, request *grpcpb.ExclamationRequest) (*grpcpb.ExclamationResponse, error) { 891 return &grpcpb.ExclamationResponse{ 892 Value: request.Value + "!", 893 }, nil 894 } 895 896 func (s *excitedServer) ExclamationClientStream(streamServer grpcpb.ExcitedService_ExclamationClientStreamServer) error { 897 value := "" 898 for request, err := streamServer.Recv(); err != io.EOF; request, err = streamServer.Recv() { 899 if err != nil { 900 return err 901 } 902 value += request.Value 903 } 904 return streamServer.SendAndClose(&grpcpb.ExclamationResponse{ 905 Value: value + "!", 906 }) 907 } 908 909 func (s *excitedServer) ExclamationServerStream(request *grpcpb.ExclamationRequest, streamServer grpcpb.ExcitedService_ExclamationServerStreamServer) error { 910 for _, c := range request.Value { 911 if err := streamServer.Send(&grpcpb.ExclamationResponse{ 912 Value: string(c), 913 }); err != nil { 914 return err 915 } 916 } 917 return streamServer.Send(&grpcpb.ExclamationResponse{ 918 Value: "!", 919 }) 920 } 921 922 func (s *excitedServer) ExclamationBidiStream(streamServer grpcpb.ExcitedService_ExclamationBidiStreamServer) error { 923 for request, err := streamServer.Recv(); err != io.EOF; request, err = streamServer.Recv() { 924 if err != nil { 925 return err 926 } 927 if err := streamServer.Send(&grpcpb.ExclamationResponse{ 928 Value: request.Value + "!", 929 }); err != nil { 930 return err 931 } 932 } 933 return nil 934 } 935 936 // do not use these in tests 937 938 func assertDoInternal(t *testing.T, stdin io.Reader, expectedExitCode int, expectedLinePrefixes string, args ...string) { 939 stdout, exitCode := testDoStdin(t, stdin, args...) 940 outputSplit := getCleanLines(stdout) 941 assert.Equal(t, expectedExitCode, exitCode, strings.Join(outputSplit, "\n")) 942 expectedLinePrefixesSplit := getCleanLines(expectedLinePrefixes) 943 require.Equal(t, len(expectedLinePrefixesSplit), len(outputSplit), strings.Join(outputSplit, "\n")) 944 for i, expectedLinePrefix := range expectedLinePrefixesSplit { 945 assert.True(t, strings.HasPrefix(outputSplit[i], expectedLinePrefix), "%s %d %s", expectedLinePrefix, i, strings.Join(outputSplit, "\n")) 946 } 947 } 948 949 func testDownload(t *testing.T) { 950 testLock.Lock() 951 defer testLock.Unlock() 952 // download checks if protoc is already downloaded to the cache location 953 // if it is, then this is effectively a no-op 954 // if it isn't, then this downloads to the cache 955 stdout, exitCode := testDoInternal(nil, "download") 956 require.Equal(t, 0, exitCode, "had non-zero exit code when downloading: %s", stdout) 957 } 958 959 func testDoInternal(stdin io.Reader, args ...string) (string, int) { 960 args = append(args, 961 "--print-fields", "filename:line:column:id:message", 962 ) 963 if stdin == nil { 964 stdin = os.Stdin 965 } 966 buffer := bytes.NewBuffer(nil) 967 // develMode is on, so we have access to all commands 968 exitCode := do(true, args, stdin, buffer, buffer) 969 return strings.TrimSpace(buffer.String()), exitCode 970 } 971 972 func getFreeListener() (net.Listener, error) { 973 address, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") 974 if err != nil { 975 return nil, err 976 } 977 return net.ListenTCP("tcp", address) 978 }