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  }