github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/errs/errs_test.go (about)

     1  package errs_test
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/ActiveState/cli/internal/errs"
    13  	"github.com/ActiveState/cli/internal/locale"
    14  	"github.com/ActiveState/cli/internal/rtutils"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  func TestErrs(t *testing.T) {
    19  	tests := []struct {
    20  		name            string
    21  		err             error
    22  		wantMessage     string
    23  		wantJoinMessage string
    24  	}{
    25  		{
    26  			"Creates error",
    27  			errs.New("hello %s", "world"),
    28  			"hello world",
    29  			"hello world",
    30  		},
    31  		{
    32  			"Creates wrapped error",
    33  			errs.Wrap(errors.New("Wrapped"), "Wrapper %s", "error"),
    34  			"Wrapper error",
    35  			"Wrapper error: Wrapped",
    36  		},
    37  	}
    38  	for _, tt := range tests {
    39  		t.Run(tt.name, func(t *testing.T) {
    40  			err := tt.err
    41  			if err != nil && err.Error() != tt.wantMessage {
    42  				t.Errorf("New() error message = %s, wantMessage %s", err.Error(), tt.wantMessage)
    43  			}
    44  			ee, ok := err.(errs.Errorable)
    45  			if !ok {
    46  				t.Fatalf("Error should be of type errs.Error")
    47  			}
    48  			if ee.Stack() == nil {
    49  				t.Fatalf("Stacktrace was not created")
    50  			}
    51  			for i, frame := range ee.Stack().Frames {
    52  				curFile := rtutils.CurrentFile()
    53  				if strings.Contains(frame.Path, filepath.Dir(curFile)) && frame.Path != curFile {
    54  					t.Fatalf("Stack should not contain reference to errs package.\nFound: %s at frame %d. Full stack:\n%s", frame.Path, i, ee.Stack().String())
    55  				}
    56  			}
    57  			if joinmessage := errs.JoinMessage(err); joinmessage != tt.wantJoinMessage {
    58  				t.Errorf("JoinMessage did not match, want: '%s', got: '%s'", tt.wantJoinMessage, joinmessage)
    59  			}
    60  		})
    61  	}
    62  }
    63  
    64  type standardError struct{ error }
    65  
    66  func TestMatches(t *testing.T) {
    67  	type args struct {
    68  		err    error
    69  		target interface{}
    70  	}
    71  	tests := []struct {
    72  		name string
    73  		args args
    74  		want bool
    75  	}{
    76  		{
    77  			"Simple match",
    78  			args{
    79  				&standardError{errors.New("error")},
    80  				&standardError{},
    81  			},
    82  			true,
    83  		},
    84  		{
    85  			"Simple miss-match",
    86  			args{
    87  				errors.New("error"),
    88  				&standardError{},
    89  			},
    90  			false,
    91  		},
    92  		{
    93  			"Wrapped match",
    94  			args{
    95  				errs.Wrap(&standardError{errors.New("error")}, "Wrapped"),
    96  				&standardError{},
    97  			},
    98  			true,
    99  		},
   100  		{
   101  			"exec.ExitError", // this one has proved troublesome
   102  			args{
   103  				&exec.ExitError{&os.ProcessState{}, []byte("")},
   104  				&exec.ExitError{},
   105  			},
   106  			true,
   107  		},
   108  		{
   109  			"wrapped exec.ExitError",
   110  			args{
   111  				errs.Wrap(&exec.ExitError{&os.ProcessState{}, []byte("")}, "wrapped"),
   112  				&exec.ExitError{},
   113  			},
   114  			true,
   115  		},
   116  		{
   117  			"combined errors 1",
   118  			args{
   119  				errs.Pack(&exec.ExitError{&os.ProcessState{}, []byte("")}, errs.New("Random")),
   120  				&exec.ExitError{},
   121  			},
   122  			true,
   123  		},
   124  		{
   125  			"combined errors 2 - inverted",
   126  			args{
   127  				errs.Pack(errs.New("Random"), &exec.ExitError{&os.ProcessState{}, []byte("")}),
   128  				&exec.ExitError{},
   129  			},
   130  			true,
   131  		},
   132  	}
   133  	for _, tt := range tests {
   134  		t.Run(tt.name, func(t *testing.T) {
   135  			if got := errs.Matches(tt.args.err, tt.args.target); got != tt.want {
   136  				t.Errorf("Matches() = %v, want %v", got, tt.want)
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func TestAddTips(t *testing.T) {
   143  	type args struct {
   144  		err  error
   145  		tips []string
   146  	}
   147  	tests := []struct {
   148  		name          string
   149  		args          args
   150  		wantErrorMsgs []string
   151  		wantTips      []string
   152  	}{
   153  		{
   154  			"Simple",
   155  			args{
   156  				errs.New("error"),
   157  				[]string{"tip1", "tip2"},
   158  			},
   159  			[]string{"error"},
   160  			[]string{"tip1", "tip2"},
   161  		},
   162  		{
   163  			"Localized",
   164  			args{
   165  				locale.NewError("error"),
   166  				[]string{"tip1", "tip2"},
   167  			},
   168  			[]string{"error"},
   169  			[]string{"tip1", "tip2"},
   170  		},
   171  		{
   172  			"Multi error",
   173  			args{
   174  				errs.Pack(errs.New("error1"), errs.New("error2")),
   175  				[]string{"tip1", "tip2"},
   176  			},
   177  			[]string{"error1", "error2"},
   178  			[]string{"tip1", "tip2"},
   179  		},
   180  		{
   181  			"Multi error with locale",
   182  			args{
   183  				errs.Pack(locale.NewError("error1"), locale.NewError("error2")),
   184  				[]string{"tip1", "tip2"},
   185  			},
   186  			[]string{"error1", "error2"},
   187  			[]string{"tip1", "tip2"},
   188  		},
   189  	}
   190  	for _, tt := range tests {
   191  		t.Run(tt.name, func(t *testing.T) {
   192  			err := errs.AddTips(tt.args.err, tt.args.tips...)
   193  			gotTips := []string{}
   194  			msgs := []string{}
   195  			errors := errs.Unpack(err)
   196  			for _, err := range errors {
   197  				_, isMultiError := err.(*errs.PackedErrors)
   198  				if !isMultiError && err.Error() != errs.TipMessage {
   199  					msgs = append(msgs, err.Error())
   200  				}
   201  
   202  				// Check via direct type cast or via direct `.As()` method because otherwise the unwrapper of
   203  				// errors.As will go down paths we're not interested in.
   204  				var errTips errs.ErrorTips
   205  				if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(&errTips) {
   206  					gotTips = append(gotTips, errTips.ErrorTips()...)
   207  				} else if x, ok := err.(errs.ErrorTips); ok {
   208  					gotTips = append(gotTips, x.ErrorTips()...)
   209  				}
   210  			}
   211  			if !reflect.DeepEqual(gotTips, tt.wantTips) {
   212  				t.Errorf("AddTips() = %v, want %v", gotTips, tt.wantTips)
   213  			}
   214  			if !reflect.DeepEqual(msgs, tt.wantErrorMsgs) {
   215  				t.Errorf("Error Msgs = %v, want %v", msgs, tt.wantErrorMsgs)
   216  			}
   217  		})
   218  	}
   219  }
   220  
   221  func TestUnpack(t *testing.T) {
   222  	tests := []struct {
   223  		name string
   224  		err  error
   225  		want []string
   226  	}{
   227  		{
   228  			"Single",
   229  			errs.New("error1"),
   230  			[]string{"error1"},
   231  		},
   232  		{
   233  			"Wrapped",
   234  			errs.Wrap(errs.New("error1"), "error2"),
   235  			[]string{"error2", "error1"},
   236  		},
   237  		{
   238  			"Stacked",
   239  			errs.Pack(errs.New("error1"), errs.New("error2"), errs.New("error3")),
   240  			[]string{"error1", "error2", "error3"},
   241  		},
   242  		{
   243  			"Stacked and Wrapped",
   244  			errs.Pack(errs.New("error1"), errs.Wrap(errs.New("error2"), "error2-wrap"), errs.New("error3")),
   245  			[]string{"error1", "error2-wrap", "error2", "error3"},
   246  		},
   247  		{
   248  			"Stacked, Wrapped and Stacked",
   249  			errs.Pack(
   250  				errs.New("error1"),
   251  				errs.Wrap(
   252  					errs.Pack(errs.New("error2a"), errs.New("error2b")),
   253  					"error2-wrap",
   254  				),
   255  				errs.New("error3")),
   256  			[]string{"error1", "error2-wrap", "error2a", "error2b", "error3"},
   257  		},
   258  	}
   259  	for _, tt := range tests {
   260  		t.Run(tt.name, func(t *testing.T) {
   261  			errors := errs.Unpack(tt.err)
   262  			errorMsgs := []string{}
   263  			for _, err := range errors {
   264  				errorMsgs = append(errorMsgs, err.Error())
   265  			}
   266  			assert.Equalf(t, tt.want, errorMsgs, "Unpack(%v)", tt.err)
   267  		})
   268  	}
   269  }
   270  
   271  func TestJoinMessage(t *testing.T) {
   272  	tests := []struct {
   273  		name string
   274  		err  error
   275  		want string
   276  	}{
   277  		{
   278  			"Single",
   279  			errs.New("error1"),
   280  			"error1",
   281  		},
   282  		{
   283  			"Wrapped",
   284  			errs.Wrap(errs.New("error1"), "error2"),
   285  			"error2: error1",
   286  		},
   287  		{
   288  			"Stacked",
   289  			errs.Pack(errs.New("error1"), errs.New("error2"), errs.New("error3")),
   290  			"- error1\n- error2\n- error3",
   291  		},
   292  		{
   293  			"Stacked and Wrapped",
   294  			errs.Pack(
   295  				errs.New("error1"),
   296  				errs.Wrap(errs.New("error2"), "error2-wrap"),
   297  				errs.New("error3"),
   298  			),
   299  			"- error1\n- error2-wrap: error2\n- error3",
   300  		},
   301  		{
   302  			"Stacked, Wrapped and Stacked",
   303  			errs.Pack(
   304  				errs.New("error1"),
   305  				errs.Wrap(
   306  					errs.Pack(errs.New("error2a"), errs.New("error2b")),
   307  					"error2-wrap",
   308  				),
   309  				errs.New("error3")),
   310  			"- error1\n- error2-wrap:\n    - error2a\n    - error2b\n- error3",
   311  		},
   312  	}
   313  	for _, tt := range tests {
   314  		t.Run(tt.name, func(t *testing.T) {
   315  			msg := errs.JoinMessage(tt.err)
   316  			assert.Equalf(t, tt.want, msg, "JoinMessage(%v)", tt.err)
   317  		})
   318  	}
   319  }