github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/repo/fork/fork_test.go (about) 1 package fork 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "net/url" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/ungtb10d/cli/v2/context" 13 "github.com/ungtb10d/cli/v2/git" 14 "github.com/ungtb10d/cli/v2/internal/config" 15 "github.com/ungtb10d/cli/v2/internal/ghrepo" 16 "github.com/ungtb10d/cli/v2/internal/run" 17 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 18 "github.com/ungtb10d/cli/v2/pkg/httpmock" 19 "github.com/ungtb10d/cli/v2/pkg/iostreams" 20 "github.com/ungtb10d/cli/v2/pkg/prompt" 21 "github.com/google/shlex" 22 "github.com/stretchr/testify/assert" 23 ) 24 25 func TestNewCmdFork(t *testing.T) { 26 tests := []struct { 27 name string 28 cli string 29 tty bool 30 wants ForkOptions 31 wantErr bool 32 errMsg string 33 }{ 34 { 35 name: "repo with git args", 36 cli: "foo/bar -- --foo=bar", 37 wants: ForkOptions{ 38 Repository: "foo/bar", 39 GitArgs: []string{"--foo=bar"}, 40 RemoteName: "origin", 41 Rename: true, 42 }, 43 }, 44 { 45 name: "git args without repo", 46 cli: "-- --foo bar", 47 wantErr: true, 48 errMsg: "repository argument required when passing git clone flags", 49 }, 50 { 51 name: "repo", 52 cli: "foo/bar", 53 wants: ForkOptions{ 54 Repository: "foo/bar", 55 RemoteName: "origin", 56 Rename: true, 57 GitArgs: []string{}, 58 }, 59 }, 60 { 61 name: "blank remote name", 62 cli: "--remote --remote-name=''", 63 wantErr: true, 64 errMsg: "--remote-name cannot be blank", 65 }, 66 { 67 name: "remote name", 68 cli: "--remote --remote-name=foo", 69 wants: ForkOptions{ 70 RemoteName: "foo", 71 Rename: false, 72 Remote: true, 73 }, 74 }, 75 { 76 name: "blank nontty", 77 cli: "", 78 wants: ForkOptions{ 79 RemoteName: "origin", 80 Rename: true, 81 Organization: "", 82 }, 83 }, 84 { 85 name: "blank tty", 86 cli: "", 87 tty: true, 88 wants: ForkOptions{ 89 RemoteName: "origin", 90 PromptClone: true, 91 PromptRemote: true, 92 Rename: true, 93 Organization: "", 94 }, 95 }, 96 { 97 name: "clone", 98 cli: "--clone", 99 wants: ForkOptions{ 100 RemoteName: "origin", 101 Rename: true, 102 }, 103 }, 104 { 105 name: "remote", 106 cli: "--remote", 107 wants: ForkOptions{ 108 RemoteName: "origin", 109 Remote: true, 110 Rename: true, 111 }, 112 }, 113 { 114 name: "to org", 115 cli: "--org batmanshome", 116 wants: ForkOptions{ 117 RemoteName: "origin", 118 Remote: false, 119 Rename: false, 120 Organization: "batmanshome", 121 }, 122 }, 123 { 124 name: "empty org", 125 cli: " --org=''", 126 wantErr: true, 127 errMsg: "--org cannot be blank", 128 }, 129 { 130 name: "git flags in wrong place", 131 cli: "--depth 1 OWNER/REPO", 132 wantErr: true, 133 errMsg: "unknown flag: --depth\nSeparate git clone flags with `--`.", 134 }, 135 { 136 name: "with fork name", 137 cli: "--fork-name new-fork", 138 wants: ForkOptions{ 139 Remote: false, 140 RemoteName: "origin", 141 ForkName: "new-fork", 142 Rename: false, 143 }, 144 }, 145 } 146 147 for _, tt := range tests { 148 t.Run(tt.name, func(t *testing.T) { 149 ios, _, _, _ := iostreams.Test() 150 151 f := &cmdutil.Factory{ 152 IOStreams: ios, 153 } 154 155 ios.SetStdoutTTY(tt.tty) 156 ios.SetStdinTTY(tt.tty) 157 158 argv, err := shlex.Split(tt.cli) 159 assert.NoError(t, err) 160 161 var gotOpts *ForkOptions 162 cmd := NewCmdFork(f, func(opts *ForkOptions) error { 163 gotOpts = opts 164 return nil 165 }) 166 cmd.SetArgs(argv) 167 cmd.SetIn(&bytes.Buffer{}) 168 cmd.SetOut(&bytes.Buffer{}) 169 cmd.SetErr(&bytes.Buffer{}) 170 171 _, err = cmd.ExecuteC() 172 if tt.wantErr { 173 assert.Error(t, err) 174 assert.Equal(t, tt.errMsg, err.Error()) 175 return 176 } 177 assert.NoError(t, err) 178 179 assert.Equal(t, tt.wants.RemoteName, gotOpts.RemoteName) 180 assert.Equal(t, tt.wants.Remote, gotOpts.Remote) 181 assert.Equal(t, tt.wants.PromptRemote, gotOpts.PromptRemote) 182 assert.Equal(t, tt.wants.PromptClone, gotOpts.PromptClone) 183 assert.Equal(t, tt.wants.Organization, gotOpts.Organization) 184 assert.Equal(t, tt.wants.GitArgs, gotOpts.GitArgs) 185 }) 186 } 187 } 188 189 func TestRepoFork(t *testing.T) { 190 forkResult := `{ 191 "node_id": "123", 192 "name": "REPO", 193 "clone_url": "https://github.com/someone/repo.git", 194 "created_at": "2011-01-26T19:01:12Z", 195 "owner": { 196 "login": "someone" 197 } 198 }` 199 200 forkPost := func(reg *httpmock.Registry) { 201 reg.Register( 202 httpmock.REST("POST", "repos/OWNER/REPO/forks"), 203 httpmock.StringResponse(forkResult)) 204 } 205 206 tests := []struct { 207 name string 208 opts *ForkOptions 209 tty bool 210 httpStubs func(*httpmock.Registry) 211 execStubs func(*run.CommandStubber) 212 askStubs func(*prompt.AskStubber) 213 cfgStubs func(*config.ConfigMock) 214 remotes []*context.Remote 215 wantOut string 216 wantErrOut string 217 wantErr bool 218 errMsg string 219 }{ 220 { 221 name: "implicit match, configured protocol overrides provided", 222 tty: true, 223 opts: &ForkOptions{ 224 Remote: true, 225 RemoteName: "fork", 226 }, 227 remotes: []*context.Remote{ 228 { 229 Remote: &git.Remote{Name: "origin", PushURL: &url.URL{ 230 Scheme: "ssh", 231 }}, 232 Repo: ghrepo.New("OWNER", "REPO"), 233 }, 234 }, 235 httpStubs: forkPost, 236 execStubs: func(cs *run.CommandStubber) { 237 cs.Register(`git remote add -f fork https://github\.com/someone/REPO\.git`, 0, "") 238 }, 239 wantErrOut: "✓ Created fork someone/REPO\n✓ Added remote fork\n", 240 }, 241 { 242 name: "implicit match, no configured protocol", 243 tty: true, 244 opts: &ForkOptions{ 245 Remote: true, 246 RemoteName: "fork", 247 }, 248 remotes: []*context.Remote{ 249 { 250 Remote: &git.Remote{Name: "origin", PushURL: &url.URL{ 251 Scheme: "ssh", 252 }}, 253 Repo: ghrepo.New("OWNER", "REPO"), 254 }, 255 }, 256 cfgStubs: func(c *config.ConfigMock) { 257 c.Set("", "git_protocol", "") 258 }, 259 httpStubs: forkPost, 260 execStubs: func(cs *run.CommandStubber) { 261 cs.Register(`git remote add -f fork git@github\.com:someone/REPO\.git`, 0, "") 262 }, 263 wantErrOut: "✓ Created fork someone/REPO\n✓ Added remote fork\n", 264 }, 265 { 266 name: "implicit with negative interactive choices", 267 tty: true, 268 opts: &ForkOptions{ 269 PromptRemote: true, 270 Rename: true, 271 RemoteName: defaultRemoteName, 272 }, 273 httpStubs: forkPost, 274 askStubs: func(as *prompt.AskStubber) { 275 //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt 276 as.StubOne(false) 277 }, 278 wantErrOut: "✓ Created fork someone/REPO\n", 279 }, 280 { 281 name: "implicit with interactive choices", 282 tty: true, 283 opts: &ForkOptions{ 284 PromptRemote: true, 285 Rename: true, 286 RemoteName: defaultRemoteName, 287 }, 288 httpStubs: forkPost, 289 execStubs: func(cs *run.CommandStubber) { 290 cs.Register("git remote rename origin upstream", 0, "") 291 cs.Register(`git remote add -f origin https://github.com/someone/REPO.git`, 0, "") 292 }, 293 askStubs: func(as *prompt.AskStubber) { 294 //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt 295 as.StubOne(true) 296 }, 297 wantErrOut: "✓ Created fork someone/REPO\n✓ Added remote origin\n", 298 }, 299 { 300 name: "implicit tty reuse existing remote", 301 tty: true, 302 opts: &ForkOptions{ 303 Remote: true, 304 RemoteName: defaultRemoteName, 305 }, 306 remotes: []*context.Remote{ 307 { 308 Remote: &git.Remote{Name: "origin", FetchURL: &url.URL{}}, 309 Repo: ghrepo.New("someone", "REPO"), 310 }, 311 { 312 Remote: &git.Remote{Name: "upstream", FetchURL: &url.URL{}}, 313 Repo: ghrepo.New("OWNER", "REPO"), 314 }, 315 }, 316 httpStubs: forkPost, 317 wantErrOut: "✓ Created fork someone/REPO\n✓ Using existing remote origin\n", 318 }, 319 { 320 name: "implicit tty remote exists", 321 // gh repo fork --remote --remote-name origin | cat 322 tty: true, 323 opts: &ForkOptions{ 324 Remote: true, 325 RemoteName: defaultRemoteName, 326 }, 327 httpStubs: forkPost, 328 wantErr: true, 329 errMsg: "a git remote named 'origin' already exists", 330 }, 331 { 332 name: "implicit tty current owner forked", 333 tty: true, 334 opts: &ForkOptions{ 335 Repository: "someone/REPO", 336 }, 337 httpStubs: func(reg *httpmock.Registry) { 338 reg.Register( 339 httpmock.REST("POST", "repos/someone/REPO/forks"), 340 httpmock.StringResponse(forkResult)) 341 }, 342 wantErr: true, 343 errMsg: "failed to fork: someone/REPO cannot be forked", 344 }, 345 { 346 name: "implicit tty already forked", 347 tty: true, 348 opts: &ForkOptions{ 349 Since: func(t time.Time) time.Duration { 350 return 120 * time.Second 351 }, 352 }, 353 httpStubs: forkPost, 354 wantErrOut: "! someone/REPO already exists\n", 355 }, 356 { 357 name: "implicit tty --remote", 358 tty: true, 359 opts: &ForkOptions{ 360 Remote: true, 361 RemoteName: defaultRemoteName, 362 Rename: true, 363 }, 364 httpStubs: forkPost, 365 execStubs: func(cs *run.CommandStubber) { 366 cs.Register("git remote rename origin upstream", 0, "") 367 cs.Register(`git remote add -f origin https://github.com/someone/REPO.git`, 0, "") 368 }, 369 wantErrOut: "✓ Created fork someone/REPO\n✓ Added remote origin\n", 370 }, 371 { 372 name: "implicit nontty reuse existing remote", 373 opts: &ForkOptions{ 374 Remote: true, 375 RemoteName: defaultRemoteName, 376 Rename: true, 377 }, 378 remotes: []*context.Remote{ 379 { 380 Remote: &git.Remote{Name: "origin", FetchURL: &url.URL{}}, 381 Repo: ghrepo.New("someone", "REPO"), 382 }, 383 { 384 Remote: &git.Remote{Name: "upstream", FetchURL: &url.URL{}}, 385 Repo: ghrepo.New("OWNER", "REPO"), 386 }, 387 }, 388 httpStubs: forkPost, 389 }, 390 { 391 name: "implicit nontty remote exists", 392 // gh repo fork --remote --remote-name origin | cat 393 opts: &ForkOptions{ 394 Remote: true, 395 RemoteName: defaultRemoteName, 396 }, 397 httpStubs: forkPost, 398 wantErr: true, 399 errMsg: "a git remote named 'origin' already exists", 400 }, 401 { 402 name: "implicit nontty already forked", 403 opts: &ForkOptions{ 404 Since: func(t time.Time) time.Duration { 405 return 120 * time.Second 406 }, 407 }, 408 httpStubs: forkPost, 409 wantErrOut: "someone/REPO already exists", 410 }, 411 { 412 name: "implicit nontty --remote", 413 opts: &ForkOptions{ 414 Remote: true, 415 RemoteName: defaultRemoteName, 416 Rename: true, 417 }, 418 httpStubs: forkPost, 419 execStubs: func(cs *run.CommandStubber) { 420 cs.Register("git remote rename origin upstream", 0, "") 421 cs.Register(`git remote add -f origin https://github.com/someone/REPO.git`, 0, "") 422 }, 423 }, 424 { 425 name: "implicit nontty no args", 426 opts: &ForkOptions{}, 427 httpStubs: forkPost, 428 }, 429 { 430 name: "passes git flags", 431 tty: true, 432 opts: &ForkOptions{ 433 Repository: "OWNER/REPO", 434 GitArgs: []string{"--depth", "1"}, 435 Clone: true, 436 }, 437 httpStubs: forkPost, 438 execStubs: func(cs *run.CommandStubber) { 439 cs.Register(`git clone --depth 1 https://github.com/someone/REPO\.git`, 0, "") 440 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 441 }, 442 wantErrOut: "✓ Created fork someone/REPO\n✓ Cloned fork\n", 443 }, 444 { 445 name: "repo arg fork to org", 446 tty: true, 447 opts: &ForkOptions{ 448 Repository: "OWNER/REPO", 449 Organization: "gamehendge", 450 Clone: true, 451 }, 452 httpStubs: func(reg *httpmock.Registry) { 453 reg.Register( 454 httpmock.REST("POST", "repos/OWNER/REPO/forks"), 455 func(req *http.Request) (*http.Response, error) { 456 bb, err := io.ReadAll(req.Body) 457 if err != nil { 458 return nil, err 459 } 460 assert.Equal(t, `{"organization":"gamehendge"}`, strings.TrimSpace(string(bb))) 461 return &http.Response{ 462 Request: req, 463 StatusCode: 200, 464 Body: io.NopCloser(bytes.NewBufferString(`{"name":"REPO", "owner":{"login":"gamehendge"}}`)), 465 }, nil 466 }) 467 }, 468 execStubs: func(cs *run.CommandStubber) { 469 cs.Register(`git clone https://github.com/gamehendge/REPO\.git`, 0, "") 470 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 471 }, 472 wantErrOut: "✓ Created fork gamehendge/REPO\n✓ Cloned fork\n", 473 }, 474 { 475 name: "repo arg url arg", 476 tty: true, 477 opts: &ForkOptions{ 478 Repository: "https://github.com/OWNER/REPO.git", 479 Clone: true, 480 }, 481 httpStubs: forkPost, 482 execStubs: func(cs *run.CommandStubber) { 483 cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "") 484 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 485 }, 486 wantErrOut: "✓ Created fork someone/REPO\n✓ Cloned fork\n", 487 }, 488 { 489 name: "repo arg interactive no clone", 490 tty: true, 491 opts: &ForkOptions{ 492 Repository: "OWNER/REPO", 493 PromptClone: true, 494 }, 495 httpStubs: forkPost, 496 askStubs: func(as *prompt.AskStubber) { 497 //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt 498 as.StubOne(false) 499 }, 500 wantErrOut: "✓ Created fork someone/REPO\n", 501 }, 502 { 503 name: "repo arg interactive", 504 tty: true, 505 opts: &ForkOptions{ 506 Repository: "OWNER/REPO", 507 PromptClone: true, 508 }, 509 httpStubs: forkPost, 510 askStubs: func(as *prompt.AskStubber) { 511 //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt 512 as.StubOne(true) 513 }, 514 execStubs: func(cs *run.CommandStubber) { 515 cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "") 516 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 517 }, 518 wantErrOut: "✓ Created fork someone/REPO\n✓ Cloned fork\n", 519 }, 520 { 521 name: "repo arg interactive already forked", 522 tty: true, 523 opts: &ForkOptions{ 524 Repository: "OWNER/REPO", 525 PromptClone: true, 526 Since: func(t time.Time) time.Duration { 527 return 120 * time.Second 528 }, 529 }, 530 httpStubs: forkPost, 531 askStubs: func(as *prompt.AskStubber) { 532 //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt 533 as.StubOne(true) 534 }, 535 execStubs: func(cs *run.CommandStubber) { 536 cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "") 537 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 538 }, 539 wantErrOut: "! someone/REPO already exists\n✓ Cloned fork\n", 540 }, 541 { 542 name: "repo arg nontty no flags", 543 opts: &ForkOptions{ 544 Repository: "OWNER/REPO", 545 }, 546 httpStubs: forkPost, 547 }, 548 { 549 name: "repo arg nontty repo already exists", 550 opts: &ForkOptions{ 551 Repository: "OWNER/REPO", 552 Since: func(t time.Time) time.Duration { 553 return 120 * time.Second 554 }, 555 }, 556 httpStubs: forkPost, 557 wantErrOut: "someone/REPO already exists", 558 }, 559 { 560 name: "repo arg nontty clone arg already exists", 561 opts: &ForkOptions{ 562 Repository: "OWNER/REPO", 563 Clone: true, 564 Since: func(t time.Time) time.Duration { 565 return 120 * time.Second 566 }, 567 }, 568 httpStubs: forkPost, 569 execStubs: func(cs *run.CommandStubber) { 570 cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "") 571 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 572 }, 573 wantErrOut: "someone/REPO already exists", 574 }, 575 { 576 name: "repo arg nontty clone arg", 577 opts: &ForkOptions{ 578 Repository: "OWNER/REPO", 579 Clone: true, 580 }, 581 httpStubs: forkPost, 582 execStubs: func(cs *run.CommandStubber) { 583 cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "") 584 cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "") 585 }, 586 }, 587 { 588 name: "non tty repo arg with fork-name", 589 opts: &ForkOptions{ 590 Repository: "someone/REPO", 591 Clone: false, 592 ForkName: "NEW_REPO", 593 }, 594 tty: false, 595 httpStubs: func(reg *httpmock.Registry) { 596 forkResult := `{ 597 "node_id": "123", 598 "name": "REPO", 599 "clone_url": "https://github.com/OWNER/REPO.git", 600 "created_at": "2011-01-26T19:01:12Z", 601 "owner": { 602 "login": "OWNER" 603 } 604 }` 605 renameResult := `{ 606 "node_id": "1234", 607 "name": "NEW_REPO", 608 "clone_url": "https://github.com/OWNER/NEW_REPO.git", 609 "created_at": "2012-01-26T19:01:12Z", 610 "owner": { 611 "login": "OWNER" 612 } 613 }` 614 reg.Register( 615 httpmock.REST("POST", "repos/someone/REPO/forks"), 616 httpmock.StringResponse(forkResult)) 617 reg.Register( 618 httpmock.REST("PATCH", "repos/OWNER/REPO"), 619 httpmock.StringResponse(renameResult)) 620 }, 621 wantErrOut: "", 622 }, 623 { 624 name: "tty repo arg with fork-name", 625 opts: &ForkOptions{ 626 Repository: "someone/REPO", 627 Clone: false, 628 ForkName: "NEW_REPO", 629 }, 630 tty: true, 631 httpStubs: func(reg *httpmock.Registry) { 632 forkResult := `{ 633 "node_id": "123", 634 "name": "REPO", 635 "clone_url": "https://github.com/OWNER/REPO.git", 636 "created_at": "2011-01-26T19:01:12Z", 637 "owner": { 638 "login": "OWNER" 639 } 640 }` 641 renameResult := `{ 642 "node_id": "1234", 643 "name": "NEW_REPO", 644 "clone_url": "https://github.com/OWNER/NEW_REPO.git", 645 "created_at": "2012-01-26T19:01:12Z", 646 "owner": { 647 "login": "OWNER" 648 } 649 }` 650 reg.Register( 651 httpmock.REST("POST", "repos/someone/REPO/forks"), 652 httpmock.StringResponse(forkResult)) 653 reg.Register( 654 httpmock.REST("PATCH", "repos/OWNER/REPO"), 655 httpmock.StringResponse(renameResult)) 656 }, 657 wantErrOut: "✓ Created fork OWNER/REPO\n✓ Renamed fork to OWNER/NEW_REPO\n", 658 }, 659 } 660 661 for _, tt := range tests { 662 ios, _, stdout, stderr := iostreams.Test() 663 ios.SetStdinTTY(tt.tty) 664 ios.SetStdoutTTY(tt.tty) 665 ios.SetStderrTTY(tt.tty) 666 tt.opts.IO = ios 667 668 tt.opts.BaseRepo = func() (ghrepo.Interface, error) { 669 return ghrepo.New("OWNER", "REPO"), nil 670 } 671 672 reg := &httpmock.Registry{} 673 if tt.httpStubs != nil { 674 tt.httpStubs(reg) 675 } 676 tt.opts.HttpClient = func() (*http.Client, error) { 677 return &http.Client{Transport: reg}, nil 678 } 679 680 cfg := config.NewBlankConfig() 681 if tt.cfgStubs != nil { 682 tt.cfgStubs(cfg) 683 } 684 tt.opts.Config = func() (config.Config, error) { 685 return cfg, nil 686 } 687 688 tt.opts.Remotes = func() (context.Remotes, error) { 689 if tt.remotes == nil { 690 return []*context.Remote{ 691 { 692 Remote: &git.Remote{ 693 Name: "origin", 694 FetchURL: &url.URL{}, 695 }, 696 Repo: ghrepo.New("OWNER", "REPO"), 697 }, 698 }, nil 699 } 700 return tt.remotes, nil 701 } 702 703 tt.opts.GitClient = &git.Client{GitPath: "some/path/git"} 704 705 //nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber 706 as, teardown := prompt.InitAskStubber() 707 defer teardown() 708 if tt.askStubs != nil { 709 tt.askStubs(as) 710 } 711 cs, restoreRun := run.Stub() 712 defer restoreRun(t) 713 if tt.execStubs != nil { 714 tt.execStubs(cs) 715 } 716 717 t.Run(tt.name, func(t *testing.T) { 718 if tt.opts.Since == nil { 719 tt.opts.Since = func(t time.Time) time.Duration { 720 return 2 * time.Second 721 } 722 } 723 defer reg.Verify(t) 724 err := forkRun(tt.opts) 725 if tt.wantErr { 726 assert.Error(t, err) 727 assert.Equal(t, tt.errMsg, err.Error()) 728 return 729 } 730 731 assert.NoError(t, err) 732 assert.Equal(t, tt.wantOut, stdout.String()) 733 assert.Equal(t, tt.wantErrOut, stderr.String()) 734 }) 735 } 736 }