github.com/psrajat/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  }