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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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 := ¶ms.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 := ¶ms.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 := ¶ms.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 := ¶ms.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 := ¶ms.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 := ¶ms.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 }