trpc.group/trpc-go/trpc-cmdline@v1.0.9/cmd/create/create_test.go (about)

     1  // Tencent is pleased to support the open source community by making tRPC available.
     2  //
     3  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     4  // All rights reserved.
     5  //
     6  // If you have downloaded a copy of the tRPC source code from Tencent,
     7  // please note that tRPC source code is licensed under the  Apache 2.0 License,
     8  // A copy of the Apache 2.0 License is included in this file.
     9  
    10  package create
    11  
    12  import (
    13  	"errors"
    14  	"fmt"
    15  
    16  	"math/rand"
    17  	"os"
    18  	"path/filepath"
    19  	"reflect"
    20  	"testing"
    21  	"unsafe"
    22  
    23  	"github.com/agiledragon/gomonkey"
    24  	"github.com/spf13/cobra"
    25  	"github.com/spf13/pflag"
    26  	"github.com/stretchr/testify/require"
    27  
    28  	"trpc.group/trpc-go/trpc-cmdline/config"
    29  	"trpc.group/trpc-go/trpc-cmdline/descriptor"
    30  	"trpc.group/trpc-go/trpc-cmdline/params"
    31  	"trpc.group/trpc-go/trpc-cmdline/parser"
    32  	"trpc.group/trpc-go/trpc-cmdline/plugin"
    33  	"trpc.group/trpc-go/trpc-cmdline/tpl"
    34  	"trpc.group/trpc-go/trpc-cmdline/util/pb"
    35  )
    36  
    37  func TestMain(m *testing.M) {
    38  	if _, err := config.Init(); err != nil {
    39  		panic(err)
    40  	}
    41  	if err := setup(nil); err != nil {
    42  		panic(err)
    43  	}
    44  	os.Exit(m.Run())
    45  }
    46  
    47  // prepare testcases
    48  type testcase struct {
    49  	name          string
    50  	pbdir         string
    51  	pbfile        string
    52  	rpconly       bool
    53  	splitByMethod bool
    54  	alias         bool
    55  	lang          string
    56  	wantErr       bool
    57  	opts          []string
    58  }
    59  
    60  func Test_CreateCmd(t *testing.T) {
    61  	wd, _ := os.Getwd()
    62  	defer os.Chdir(wd)
    63  
    64  	pd := filepath.Dir(wd)
    65  	pd = filepath.Dir(pd)
    66  	testdir := filepath.Join(pd, "testcase/create")
    67  	testcases := []testcase{
    68  		{
    69  			name:   "1.1-without-import",
    70  			pbdir:  "1-without-import",
    71  			pbfile: "helloworld.proto",
    72  		}, {
    73  			name:   "1.2-without-import (alias)",
    74  			pbdir:  "1-without-import",
    75  			pbfile: "helloworld.proto",
    76  			alias:  true,
    77  		}, {
    78  			name:    "1.3-without-import (rpconly)",
    79  			pbdir:   "1-without-import",
    80  			pbfile:  "helloworld.proto",
    81  			rpconly: true,
    82  		}, {
    83  			name:          "1.5-without-import (split by method)",
    84  			pbdir:         "1-without-import",
    85  			pbfile:        "helloworld.proto",
    86  			splitByMethod: true,
    87  		}, {
    88  			name:   "2-multi-pb-same-package",
    89  			pbdir:  "2-multi-pb-same-package",
    90  			pbfile: "hello.proto",
    91  		}, {
    92  			name:   "3-multi-pb-diff-package",
    93  			pbdir:  "3-multi-pb-diff-package",
    94  			pbfile: "helloworld.proto",
    95  		}, {
    96  			name:   "4.1-multi-pb-same-package-diff-protodir",
    97  			pbdir:  "4.1-multi-pb-same-package-diff-protodir",
    98  			pbfile: "helloworld.proto",
    99  		}, {
   100  			name:   "4.2-multi-pb-same-package-diff-protodir",
   101  			pbdir:  "4.2-multi-pb-same-package-diff-protodir",
   102  			pbfile: "helloworld.proto",
   103  		}, {
   104  			name:   "5-multi-pb-same-pkgdirective-diff-gopkgoption",
   105  			pbdir:  "5-multi-pb-same-pkgdirective-diff-gopkgoption",
   106  			pbfile: "helloworld.proto",
   107  		}, {
   108  			name:          "5.1-multi-pb-same-pkgdirective-diff-gopkgoption(split by method)",
   109  			pbdir:         "5-multi-pb-same-pkgdirective-diff-gopkgoption",
   110  			pbfile:        "helloworld.proto",
   111  			splitByMethod: true,
   112  		}, {
   113  			name:   "6.1-other-scene google/protobuf/any",
   114  			pbdir:  "6-other-scene/google",
   115  			pbfile: "google.proto",
   116  		}, {
   117  			name:   "6.2-other-scene hello_service",
   118  			pbdir:  "6-other-scene/hello_service",
   119  			pbfile: "hello.proto",
   120  		}, {
   121  			name:    "8-service-not-existed",
   122  			pbdir:   "8-service-not-existed",
   123  			pbfile:  "helloworld.proto",
   124  			rpconly: true,
   125  		}, {
   126  			name:   "9.1-restful (service)",
   127  			pbdir:  "9-restful",
   128  			pbfile: "helloworld.proto",
   129  		}, {
   130  			name:    "9.2-restful (rpconly)",
   131  			pbdir:   "9-restful",
   132  			pbfile:  "helloworld.proto",
   133  			rpconly: true,
   134  		}, {
   135  			name:   "10-validate-pgv",
   136  			pbdir:  "10-validate-pgv",
   137  			pbfile: "helloworld.proto",
   138  			opts:   []string{"--validate"},
   139  		},
   140  	}
   141  
   142  	tmp := filepath.Join(os.TempDir(), "create/generated")
   143  	os.RemoveAll(tmp)
   144  	// Reset plugin configuration.
   145  	// First, run create_idl_non_test.go, then execute setNonProtocolTypeOption,
   146  	// which modifies the global variables plugin.Plugins and plugin.PluginsExt.
   147  	// Finally, when running create_test.go test, it does not reset, causing the mockgan plugin not to run.
   148  	resetPlugin()
   149  
   150  	// run createCmd
   151  	for _, tt := range testcases {
   152  		tt := tt
   153  		t.Run("CreateCmd/"+tt.name, func(t *testing.T) {
   154  			any := filepath.Join(testdir, tt.pbdir)
   155  			if err := os.Chdir(any); err != nil {
   156  				panic(err)
   157  			}
   158  
   159  			dirs := []string{}
   160  			err := filepath.Walk(any, func(path string, info os.FileInfo, _ error) error {
   161  				if info.IsDir() {
   162  					dirs = append(dirs, path)
   163  				}
   164  				return nil
   165  			})
   166  			if err != nil {
   167  				panic("walk testcase error")
   168  			}
   169  
   170  			opts := tt.opts
   171  			for _, d := range dirs {
   172  				opts = append(opts, "--protodir", d)
   173  			}
   174  			out := filepath.Join(tmp, tt.name)
   175  			opts = append(opts, "--protofile", tt.pbfile)
   176  			opts = append(opts, "-o", out)
   177  			opts = append(opts, "--check-update", "true")
   178  			opts = append(opts, "-v")
   179  
   180  			if tt.rpconly {
   181  				opts = append(opts, "--rpconly")
   182  			}
   183  			if tt.splitByMethod {
   184  				opts = append(opts, "-s")
   185  			}
   186  			if tt.alias {
   187  				opts = append(opts, "--alias")
   188  			}
   189  			if tt.lang != "" {
   190  				opts = append(opts, "--lang", tt.lang)
   191  			}
   192  			resetFlags(createCmd)
   193  			runCreateCmd(t, tt.name, opts, out, tt.wantErr)
   194  		})
   195  	}
   196  }
   197  
   198  var createCmd *cobra.Command
   199  
   200  func init() {
   201  	c := New(func() error {
   202  		return nil
   203  	})
   204  	createCmd = &cobra.Command{
   205  		Use:   "create",
   206  		Short: "指定 pb 文件快速创建工程或 rpcstub",
   207  		Long: `指定 pb 文件快速创建工程或 rpcstub, 
   208  
   209  'trpc create' 有两种模式:
   210  - 生成一个完整的服务工程
   211  - 生成被调服务的 rpcstub, 需指定'--rpconly'选项
   212  `,
   213  		PreRunE:  c.PreRunE,
   214  		RunE:     c.RunE,
   215  		PostRunE: c.PostRunE,
   216  	}
   217  	var (
   218  		cfgFile           string
   219  		defaultConfigFile string
   220  		verboseFlag       bool
   221  		checkUpdateFlag   bool
   222  	)
   223  	createCmd.PersistentFlags().StringVar(&cfgFile, "config", defaultConfigFile, "配置文件路径 (自动计算)")
   224  	createCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "显示详细日志信息")
   225  	createCmd.PersistentFlags().BoolVar(&checkUpdateFlag, "check-update", false, "是否检查版本更新")
   226  	AddCreateFlags(createCmd)
   227  }
   228  
   229  func resetPlugin() {
   230  	// Public plugin chain.
   231  	plugin.Plugins = []plugin.Plugin{
   232  		&plugin.Swagger{},  // swagger apidoc
   233  		&plugin.OpenAPI{},  // openapi apidoc
   234  		&plugin.Validate{}, // protoc-gen-secv
   235  	}
   236  	// Language-specific plugin chain.
   237  	plugin.PluginsExt = map[string][]plugin.Plugin{
   238  		"go": {
   239  			&plugin.GoImports{}, // goimports,  runs before mockgen, to eliminate `package import but unused` errors
   240  			&plugin.Formatter{}, // gofmt
   241  			&plugin.GoMock{},    // gomock
   242  			&plugin.GoTag{},     // custom go tag by proto field options
   243  		},
   244  	}
   245  }
   246  
   247  func runCreateCmd(t *testing.T, name string, opts []string, out string, wantErr bool) {
   248  	if err := createCmd.ParseFlags(opts); err != nil {
   249  		t.Fatalf("TEST CreateCmd/%s ParseFlags error = %v", name, err)
   250  	}
   251  	if err := createCmd.PreRunE(createCmd, opts); (err != nil) != wantErr {
   252  		t.Fatalf("TEST CreateCmd/%s PreRunE() error = %v, wantErr %v", name, err, wantErr)
   253  	}
   254  	if err := createCmd.RunE(createCmd, opts); (err != nil) != wantErr {
   255  		t.Fatalf("TEST CreateCmd/%s RunE() error = %v, wantErr %v", name, err, wantErr)
   256  	}
   257  
   258  	if _, err := os.Lstat(out); err != nil {
   259  		t.Fatalf("TEST CreateCmd/%s RunE() didn't generate output", name)
   260  	}
   261  	if err := createCmd.PostRunE(createCmd, opts); (err != nil) != wantErr {
   262  		t.Fatalf("TEST CreateCmd/%s PostRunE() error = %v, wantErr %v", name, err, wantErr)
   263  	}
   264  	if err := os.Chdir(out); (err != nil) != wantErr {
   265  		t.Fatalf("TEST CreateCmd/%s Chdir() error = %v, wantErr %v", name, err, wantErr)
   266  	}
   267  }
   268  
   269  func resetFlags(cmd *cobra.Command) {
   270  	cmd.Flags().VisitAll(func(flag *pflag.Flag) {
   271  		if flag.Value.Type() == "stringSlice" {
   272  			// XXX: unfortunately, flag.Value.Set() appends to original
   273  			// slice, not resets it, so we retrieve pointer to the slice here
   274  			// and set it to new empty slice manually
   275  			value := reflect.ValueOf(flag.Value).Elem().FieldByName("value")
   276  			ptr := (*[]string)(unsafe.Pointer(value.Pointer()))
   277  			*ptr = make([]string, 0)
   278  		}
   279  		if flag.Value.Type() == "stringArray" {
   280  			// XXX: unfortunately, flag.Value.Set() appends to original
   281  			// slice, not resets it, so we retrieve pointer to the slice here
   282  			// and set it to new empty slice manually
   283  			value := reflect.ValueOf(flag.Value).Elem().FieldByName("value")
   284  			ptr := (*[]string)(unsafe.Pointer(value.Pointer()))
   285  			*ptr = make([]string, 0)
   286  		}
   287  		_ = flag.Value.Set(flag.DefValue)
   288  	})
   289  	for _, cmd := range cmd.Commands() {
   290  		resetFlags(cmd)
   291  	}
   292  }
   293  
   294  func TestCmd_Create_Exception(t *testing.T) {
   295  	// prepare workdir
   296  	pwd, err := os.Getwd()
   297  	require.Nil(t, err)
   298  
   299  	dir := filepath.Dir(filepath.Dir(pwd))
   300  	dir = filepath.Join(dir, "testcase/create/1-without-import")
   301  	if err := os.Chdir(dir); err != nil {
   302  		panic(err)
   303  	}
   304  	defer os.Chdir(pwd)
   305  
   306  	// case1: invalid loadCreateOption (invalid --protofile)
   307  	out := "helloworld"
   308  	flags := map[string]string{
   309  		"protodir":  dir,
   310  		"protofile": "",
   311  		"mock":      "false",
   312  		"output":    out,
   313  	}
   314  	defer os.RemoveAll(filepath.Join(dir, out))
   315  
   316  	createCmd.ResetFlags()
   317  	_, err = runAndWatch(createCmd, flags, nil)
   318  	require.NotNil(t, err)
   319  
   320  	// case2: proto parse error
   321  	p := gomonkey.ApplyFunc(parser.ParseProtoFile, func(string, []string, ...parser.Option) (*descriptor.FileDescriptor, error) {
   322  		return nil, errors.New("parse error")
   323  	})
   324  
   325  	flags = map[string]string{
   326  		"protodir":  dir,
   327  		"protofile": "helloworld.proto",
   328  		"mock":      "false",
   329  	}
   330  
   331  	_, err = runAndWatch(createCmd, flags, nil)
   332  	p.Reset()
   333  
   334  	require.NotNil(t, err)
   335  }
   336  
   337  func TestCreate_Exception(t *testing.T) {
   338  	t.Run("outputdir error", func(t *testing.T) {
   339  		p := gomonkey.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   340  			return "", errors.New("get outputdir error")
   341  		})
   342  		require.NotNil(t, (&Create{}).createByProtocolType())
   343  		p.Reset()
   344  	})
   345  
   346  	t.Run("isCleanDir error", func(t *testing.T) {
   347  		p := gomonkey.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   348  			return "xxxxx", nil
   349  		})
   350  		p.ApplyFunc(isCleanDir, func(a string, idlType config.IDLType, c string) bool {
   351  			return false
   352  		})
   353  		defer p.Reset()
   354  		require.NotNil(t, (&Create{options: &params.Option{Language: "go"}}).createFullProject())
   355  	})
   356  
   357  	t.Run("generate files error", func(t *testing.T) {
   358  		p := gomonkey.NewPatches()
   359  		p.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   360  			return "xxxxx", nil
   361  		})
   362  		p.ApplyFunc(isCleanDir, func(a string, b config.IDLType, c string) bool {
   363  			return true
   364  		})
   365  		p.ApplyFunc(tpl.GenerateFiles, func(*descriptor.FileDescriptor, string, *params.Option) error {
   366  			return errors.New("generate files error")
   367  		})
   368  		defer p.Reset()
   369  		require.NotNil(t, (&Create{options: &params.Option{Language: "go"}}).createFullProject())
   370  	})
   371  
   372  	t.Run("prepare outputdir error", func(t *testing.T) {
   373  		p := gomonkey.NewPatches()
   374  		p.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   375  			return "xxxxx", nil
   376  		})
   377  		p.ApplyFunc(isCleanDir, func(a string, b config.IDLType, c string) bool {
   378  			return true
   379  		})
   380  		p.ApplyFunc(tpl.GenerateFiles, func(*descriptor.FileDescriptor, string, *params.Option) error {
   381  			return nil
   382  		})
   383  		p.ApplyFunc(prepareOutputStub, func(string) (string, error) {
   384  			return "", errors.New("prepare outputdir error")
   385  		})
   386  		defer p.Reset()
   387  		require.NotNil(t, (&Create{options: &params.Option{Language: "go"}}).createFullProject())
   388  	})
   389  
   390  	t.Run("get package error", func(t *testing.T) {
   391  		p := gomonkey.NewPatches()
   392  		p.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   393  			return "xxxxx", nil
   394  		})
   395  		p.ApplyFunc(isCleanDir, func(a string, b config.IDLType, c string) bool {
   396  			return true
   397  		})
   398  		p.ApplyFunc(tpl.GenerateFiles, func(*descriptor.FileDescriptor, string, *params.Option) error {
   399  			return nil
   400  		})
   401  		p.ApplyFunc(prepareOutputStub, func(string) (string, error) {
   402  			return "xxxx", nil
   403  		})
   404  		p.ApplyFunc(parser.GetPackage, func(*descriptor.FileDescriptor, string) (string, error) {
   405  			return "", errors.New("get package error")
   406  		})
   407  		defer p.Reset()
   408  		require.NotNil(t, (&Create{options: &params.Option{Language: "go"}}).createFullProject())
   409  	})
   410  
   411  	t.Run("mkdir all error", func(t *testing.T) {
   412  		p := gomonkey.NewPatches()
   413  		p.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   414  			return "xxxxx", nil
   415  		})
   416  		p.ApplyFunc(isCleanDir, func(a string, b config.IDLType, c string) bool {
   417  			return true
   418  		})
   419  		p.ApplyFunc(tpl.GenerateFiles, func(*descriptor.FileDescriptor, string, *params.Option) error {
   420  			return nil
   421  		})
   422  		p.ApplyFunc(prepareOutputStub, func(string) (string, error) {
   423  			return "xxxx", nil
   424  		})
   425  		p.ApplyFunc(parser.GetPackage, func(*descriptor.FileDescriptor, string) (string, error) {
   426  			return "xxxx", nil
   427  		})
   428  		p.ApplyFunc(os.MkdirAll, func(string, os.FileMode) error {
   429  			return errors.New("mkdirall error")
   430  		})
   431  		defer p.Reset()
   432  		require.NotNil(t, (&Create{options: &params.Option{Language: "go"}}).createFullProject())
   433  	})
   434  
   435  	t.Run("run protoc error", func(t *testing.T) {
   436  		p := gomonkey.NewPatches()
   437  		p.ApplyFunc(getOutputDir, func(option *params.Option) (string, error) {
   438  			return "xxxxx", nil
   439  		})
   440  		p.ApplyFunc(isCleanDir, func(a string, b config.IDLType, c string) bool {
   441  			return true
   442  		})
   443  		p.ApplyFunc(tpl.GenerateFiles, func(*descriptor.FileDescriptor, string, *params.Option) error {
   444  			return nil
   445  		})
   446  		p.ApplyFunc(prepareOutputStub, func(string) (string, error) {
   447  			return "xxxx", nil
   448  		})
   449  		p.ApplyFunc(parser.GetPackage, func(*descriptor.FileDescriptor, string) (string, error) {
   450  			return "xxxx", nil
   451  		})
   452  		p.ApplyFunc(os.MkdirAll, func(string, os.FileMode) error {
   453  			return nil
   454  		})
   455  		p.ApplyFunc(pb.Protoc, func(protodirs []string, protofile, lang, outputdir string, opts ...pb.Option) error {
   456  			return errors.New("run protoc error")
   457  		})
   458  		defer p.Reset()
   459  		require.NotNil(t, (&Create{
   460  			fileDescriptor: &descriptor.FileDescriptor{},
   461  			options:        &params.Option{Language: "go"},
   462  		}).createFullProject())
   463  	})
   464  }
   465  
   466  func Test_isCleanDir(t *testing.T) {
   467  	t.Run("dirty directory", func(t *testing.T) {
   468  		wd, err := os.Getwd()
   469  		if err != nil {
   470  			panic(err)
   471  		}
   472  		v := isCleanDir(wd, config.IDLTypeProtobuf, "go")
   473  		require.False(t, v)
   474  	})
   475  
   476  	t.Run("clean directory", func(t *testing.T) {
   477  		wd := os.TempDir()
   478  		tmp := filepath.Join(wd, "trpc")
   479  		os.RemoveAll(tmp)
   480  		os.MkdirAll(tmp, os.ModePerm)
   481  		defer os.RemoveAll(tmp)
   482  		v := isCleanDir(tmp, config.IDLTypeProtobuf, "go")
   483  		require.True(t, v)
   484  	})
   485  }
   486  
   487  func Test_removeSafe(t *testing.T) {
   488  	tmp := filepath.Join(os.TempDir(), "trpc")
   489  
   490  	p := gomonkey.NewPatches()
   491  	p.ApplyFunc(os.Getwd, func() (dir string, err error) {
   492  		return tmp, nil
   493  	})
   494  
   495  	rm, err := removeSafe(tmp)
   496  	require.Nil(t, err)
   497  	require.False(t, rm)
   498  
   499  	p.Reset()
   500  	rm, err = removeSafe(tmp)
   501  	require.Nil(t, err)
   502  	require.True(t, rm)
   503  }
   504  
   505  func Test_prepareOutputDir(t *testing.T) {
   506  	a := os.TempDir()
   507  	b := "b"
   508  	c := "hello"
   509  
   510  	p := gomonkey.ApplyFunc(os.MkdirAll, func(string, os.FileMode) error {
   511  		return nil
   512  	})
   513  	defer p.Reset()
   514  	t.Run("prepare go outputdir", func(t *testing.T) {
   515  		d, err := prepareOutputDir(a, b, "go", c)
   516  		require.Nil(t, err)
   517  		require.Equal(t, filepath.Join(a, b), d)
   518  	})
   519  }
   520  
   521  func Test_prepareOutputStubDir(t *testing.T) {
   522  	tmp := os.TempDir()
   523  	stub := filepath.Join(tmp, "stub")
   524  
   525  	t.Run("stub notExist, create", func(t *testing.T) {
   526  		p := gomonkey.ApplyFunc(os.MkdirAll, func(string, os.FileMode) error {
   527  			return nil
   528  		})
   529  		defer p.Reset()
   530  
   531  		dir, err := prepareOutputStub(tmp)
   532  		require.Nil(t, err)
   533  		require.Equal(t, stub, dir)
   534  	})
   535  
   536  	t.Run("stub exist, return", func(t *testing.T) {
   537  		os.MkdirAll(stub, os.ModePerm)
   538  		defer os.RemoveAll(stub)
   539  
   540  		dir, err := prepareOutputStub(tmp)
   541  		require.Nil(t, err)
   542  		require.Equal(t, stub, dir)
   543  	})
   544  
   545  	t.Run("stub lstat error", func(t *testing.T) {
   546  		p := gomonkey.ApplyFunc(os.Lstat, func(string) (os.FileInfo, error) {
   547  			return nil, errors.New("lstat error")
   548  		})
   549  		_, err := prepareOutputStub(tmp)
   550  		require.NotNil(t, err)
   551  		p.Reset()
   552  	})
   553  }
   554  
   555  func TestOutputDir(t *testing.T) {
   556  
   557  	t.Run("case os.Getwd error", func(t *testing.T) {
   558  		p := gomonkey.ApplyFunc(os.Getwd, func() (dir string, err error) {
   559  			return "", errors.New("fake error")
   560  		})
   561  		defer p.Reset()
   562  		opts := &params.Option{}
   563  		dir, err := getOutputDir(opts)
   564  		require.NotNil(t, err)
   565  		require.Empty(t, dir)
   566  	})
   567  
   568  	t.Run("case rpconly abs", func(t *testing.T) {
   569  		p := gomonkey.ApplyFunc(os.Getwd, func() (dir string, err error) {
   570  			return "wd", nil
   571  		})
   572  		defer p.Reset()
   573  		opts := &params.Option{
   574  			RPCOnly:   true,
   575  			OutputDir: "/is_abs",
   576  		}
   577  		dir, err := getOutputDir(opts)
   578  		require.Nil(t, err)
   579  		require.Equal(t, "/is_abs", dir)
   580  	})
   581  
   582  	t.Run("case rpconly not abs", func(t *testing.T) {
   583  		p := gomonkey.ApplyFunc(os.Getwd, func() (dir string, err error) {
   584  			return "wd", nil
   585  		})
   586  		defer p.Reset()
   587  		opts := &params.Option{
   588  			RPCOnly:   true,
   589  			OutputDir: "is_not_abs",
   590  		}
   591  		dir, err := getOutputDir(opts)
   592  		require.Nil(t, err)
   593  		require.Equal(t, "wd/is_not_abs", dir)
   594  	})
   595  
   596  	t.Run("case not rpconly with -o", func(t *testing.T) {
   597  		p := gomonkey.ApplyFunc(os.Getwd, func() (dir string, err error) {
   598  			return "wd", nil
   599  		})
   600  		defer p.Reset()
   601  		opts := &params.Option{
   602  			RPCOnly:   false,
   603  			OutputDir: "outputdir",
   604  		}
   605  		dir, err := getOutputDir(opts)
   606  		require.Nil(t, err)
   607  		require.Equal(t, "wd/outputdir", dir)
   608  	})
   609  
   610  	t.Run("case not rpconly with mod", func(t *testing.T) {
   611  		p := gomonkey.ApplyFunc(os.Getwd, func() (dir string, err error) {
   612  			return "wd", nil
   613  		})
   614  		defer p.Reset()
   615  		opts := &params.Option{
   616  			RPCOnly:   false,
   617  			GoModEx:   "mod",
   618  			GoMod:     "mod",
   619  			OutputDir: "",
   620  		}
   621  		dir, err := getOutputDir(opts)
   622  		require.Nil(t, err)
   623  		require.Equal(t, "wd", dir)
   624  	})
   625  
   626  	t.Run("case not rpconly with mod", func(t *testing.T) {
   627  		p := gomonkey.ApplyFunc(os.Getwd, func() (dir string, err error) {
   628  			return "wd", nil
   629  		})
   630  		defer p.Reset()
   631  		opts := &params.Option{
   632  			RPCOnly:   false,
   633  			GoModEx:   "",
   634  			GoMod:     "",
   635  			OutputDir: "",
   636  			Protofile: "filename.proto",
   637  			IDLType:   config.IDLTypeProtobuf,
   638  		}
   639  		dir, err := getOutputDir(opts)
   640  		require.Nil(t, err)
   641  		require.Equal(t, "wd/filename", dir)
   642  	})
   643  }
   644  
   645  func runAndWatch(cmd *cobra.Command, flags map[string]string, args []string) (string, error) {
   646  	n := rand.Int() % 65535
   647  	tmp := os.TempDir()
   648  	tmpd := filepath.Join(tmp, "trpc")
   649  	tmpf := filepath.Join(tmpd, fmt.Sprintf("cmd_output-%d", n))
   650  
   651  	os.MkdirAll(tmpd, os.ModePerm)
   652  	defer os.RemoveAll(tmpd)
   653  
   654  	f, err := os.OpenFile(tmpf, os.O_CREATE|os.O_RDWR, 0666)
   655  	if err != nil {
   656  		panic(err)
   657  	}
   658  	defer os.RemoveAll(tmpf)
   659  
   660  	sout := os.Stdout
   661  	serr := os.Stderr
   662  
   663  	os.Stdout = f
   664  	os.Stderr = f
   665  	defer func() {
   666  		os.Stdout = sout
   667  		os.Stderr = serr
   668  	}()
   669  
   670  	for k, v := range flags {
   671  		cmd.Flags().Set(k, v)
   672  	}
   673  
   674  	// PreRun
   675  	if cmd.PreRunE != nil {
   676  		err = cmd.PreRunE(cmd, args)
   677  		if err != nil {
   678  			return "", err
   679  		}
   680  	} else if cmd.PreRun != nil {
   681  		cmd.PreRun(cmd, args)
   682  	}
   683  
   684  	// Run
   685  	if cmd.RunE != nil {
   686  		err = cmd.RunE(cmd, args)
   687  		if err != nil {
   688  			return "", err
   689  		}
   690  	} else if cmd.Run != nil {
   691  		cmd.Run(cmd, args)
   692  	}
   693  
   694  	// PostRun
   695  	if cmd.PostRunE != nil {
   696  		err = cmd.PostRunE(cmd, args)
   697  		if err != nil {
   698  			return "", err
   699  		}
   700  	} else if cmd.PostRun != nil {
   701  		cmd.PostRun(cmd, args)
   702  	}
   703  
   704  	f.Close()
   705  
   706  	b, err := os.ReadFile(tmpf)
   707  	if err != nil {
   708  		return "", err
   709  	}
   710  	return string(b), nil
   711  }