github.com/pulumi/pulumi/sdk/v3@v3.108.1/go/common/util/cmdutil/exit_test.go (about) 1 package cmdutil 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "os" 8 "os/exec" 9 "testing" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/pulumi/pulumi/sdk/v3/go/common/testing/iotest" 13 "github.com/pulumi/pulumi/sdk/v3/go/common/util/result" 14 "github.com/spf13/cobra" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func TestRunFunc_Bail(t *testing.T) { 20 t.Parallel() 21 22 // Verifies that a use of RunFunc that returns BailError 23 // will cause the program to exit with a non-zero exit code 24 // without printing an error message. 25 // 26 // Unfortunately, we can't test this directly, 27 // because the `os.Exit` call in RunResultFunc. 28 // 29 // Instead, we'll re-run the test binary, 30 // and have it run TestFakeCommand. 31 // We'll verify the output of that binary instead. 32 33 exe, err := os.Executable() 34 require.NoError(t, err) 35 36 cmd := exec.Command(exe, "-test.run=^TestFakeCommand$") 37 cmd.Env = append(os.Environ(), "TEST_FAKE=1") 38 39 // Write output to the buffer and to the test logger. 40 var buff bytes.Buffer 41 output := io.MultiWriter(&buff, iotest.LogWriter(t)) 42 cmd.Stdout = output 43 cmd.Stderr = output 44 45 err = cmd.Run() 46 exitErr := new(exec.ExitError) 47 require.ErrorAs(t, err, &exitErr) 48 assert.NotZero(t, exitErr.ExitCode()) 49 50 assert.Empty(t, buff.String()) 51 } 52 53 //nolint:paralleltest // not a real test 54 func TestFakeCommand(t *testing.T) { 55 if os.Getenv("TEST_FAKE") != "1" { 56 // This is not a real test. 57 // It's a fake test that we'll run as a subprocess 58 // to verify that the RunFunc function works correctly. 59 // See TestRunFunc_Bail for more details. 60 return 61 } 62 63 cmd := &cobra.Command{ 64 Run: RunFunc(func(cmd *cobra.Command, args []string) error { 65 return result.BailErrorf("bail") 66 }), 67 } 68 err := cmd.Execute() 69 // Unreachable: RunFunc should have called os.Exit. 70 assert.Fail(t, "unreachable", "RunFunc should have called os.Exit: %v", err) 71 } 72 73 func TestErrorMessage(t *testing.T) { 74 t.Parallel() 75 76 tests := []struct { 77 desc string 78 give error 79 want string 80 }{ 81 { 82 desc: "simple error", 83 give: errors.New("great sadness"), 84 want: "great sadness", 85 }, 86 { 87 desc: "hashi multi error", 88 give: multierror.Append( 89 errors.New("foo"), 90 errors.New("bar"), 91 errors.New("baz"), 92 ), 93 want: "3 errors occurred:" + 94 "\n 1) foo" + 95 "\n 2) bar" + 96 "\n 3) baz", 97 }, 98 { 99 desc: "std errors.Join", 100 give: errors.Join( 101 errors.New("foo"), 102 errors.New("bar"), 103 errors.New("baz"), 104 ), 105 want: "3 errors occurred:" + 106 "\n 1) foo" + 107 "\n 2) bar" + 108 "\n 3) baz", 109 }, 110 { 111 desc: "empty multi error", 112 // This is technically invalid, 113 // but we guard against it, 114 // so let's test it too. 115 give: &invalidEmptyMultiError{}, 116 want: "invalid empty multi error", 117 }, 118 { 119 desc: "single wrapped error", 120 give: &multierror.Error{ 121 Errors: []error{ 122 errors.New("great sadness"), 123 }, 124 }, 125 want: "great sadness", 126 }, 127 { 128 desc: "multi error inside single wrapped error", 129 give: &multierror.Error{ 130 Errors: []error{ 131 errors.Join( 132 errors.New("foo"), 133 errors.New("bar"), 134 errors.New("baz"), 135 ), 136 }, 137 }, 138 want: "3 errors occurred:" + 139 "\n 1) foo" + 140 "\n 2) bar" + 141 "\n 3) baz", 142 }, 143 } 144 145 for _, tt := range tests { 146 tt := tt 147 t.Run(tt.desc, func(t *testing.T) { 148 t.Parallel() 149 150 got := errorMessage(tt.give) 151 assert.Equal(t, tt.want, got) 152 }) 153 } 154 } 155 156 // invalidEmptyMultiError is an invalid error type 157 // that implements Unwrap() []error, but returns an empty slice. 158 // This is invalid per the contract for that method. 159 type invalidEmptyMultiError struct{} 160 161 func (*invalidEmptyMultiError) Error() string { 162 return "invalid empty multi error" 163 } 164 165 func (*invalidEmptyMultiError) Unwrap() []error { 166 return []error{} // invalid 167 }