github.com/blend/go-sdk@v1.20240719.1/ex/ex_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package ex
     9  
    10  import (
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/blend/go-sdk/assert"
    18  )
    19  
    20  func TestNewOfString(t *testing.T) {
    21  	a := assert.New(t)
    22  	ex := As(New("this is a test"))
    23  	a.Equal("this is a test", fmt.Sprintf("%v", ex))
    24  	a.NotNil(ex.StackTrace)
    25  	a.Nil(ex.Inner)
    26  }
    27  
    28  func TestNewOfError(t *testing.T) {
    29  	a := assert.New(t)
    30  
    31  	err := errors.New("This is an error")
    32  	wrappedErr := New(err)
    33  	a.NotNil(wrappedErr)
    34  	typedWrapped := As(wrappedErr)
    35  	a.NotNil(typedWrapped)
    36  	a.Equal("This is an error", fmt.Sprintf("%v", typedWrapped))
    37  }
    38  
    39  func TestNewOfException(t *testing.T) {
    40  	a := assert.New(t)
    41  	ex := New(Class("This is an exception"))
    42  	wrappedEx := New(ex)
    43  	a.NotNil(wrappedEx)
    44  	typedWrappedEx := As(wrappedEx)
    45  	a.Equal("This is an exception", fmt.Sprintf("%v", typedWrappedEx))
    46  	a.Equal(ex, typedWrappedEx)
    47  }
    48  
    49  func TestNewOfNil(t *testing.T) {
    50  	a := assert.New(t)
    51  
    52  	shouldBeNil := New(nil)
    53  	a.Nil(shouldBeNil)
    54  	a.Equal(nil, shouldBeNil)
    55  	a.True(nil == shouldBeNil)
    56  }
    57  
    58  func TestNewOfTypedNil(t *testing.T) {
    59  	a := assert.New(t)
    60  
    61  	var nilError error
    62  	a.Nil(nilError)
    63  	a.Equal(nil, nilError)
    64  
    65  	shouldBeNil := New(nilError)
    66  	a.Nil(shouldBeNil)
    67  	a.True(shouldBeNil == nil)
    68  }
    69  
    70  func TestNewOfReturnedNil(t *testing.T) {
    71  	a := assert.New(t)
    72  
    73  	returnsNil := func() error {
    74  		return nil
    75  	}
    76  
    77  	shouldBeNil := New(returnsNil())
    78  	a.Nil(shouldBeNil)
    79  	a.True(shouldBeNil == nil)
    80  
    81  	returnsTypedNil := func() error {
    82  		return New(nil)
    83  	}
    84  
    85  	shouldAlsoBeNil := returnsTypedNil()
    86  	a.Nil(shouldAlsoBeNil)
    87  	a.True(shouldAlsoBeNil == nil)
    88  }
    89  
    90  func TestError(t *testing.T) {
    91  	a := assert.New(t)
    92  
    93  	ex := New(Class("this is a test"))
    94  	message := ex.Error()
    95  	a.NotEmpty(message)
    96  }
    97  
    98  func TestErrorOptions(t *testing.T) {
    99  	a := assert.New(t)
   100  
   101  	ex := New(Class("this is a test"), OptMessage("foo"))
   102  	message := ex.Error()
   103  	a.NotEmpty(message)
   104  
   105  	typed := As(ex)
   106  	a.NotNil(typed)
   107  	a.Equal("foo", typed.Message)
   108  }
   109  
   110  func TestCallers(t *testing.T) {
   111  	a := assert.New(t)
   112  
   113  	callStack := func() StackTrace { return Callers(DefaultStartDepth) }()
   114  
   115  	a.NotNil(callStack)
   116  	callstackStr := callStack.String()
   117  	a.True(strings.Contains(callstackStr, "TestCallers"), callstackStr)
   118  }
   119  
   120  func TestExceptionFormatters(t *testing.T) {
   121  	assert := assert.New(t)
   122  
   123  	// test the "%v" formatter with just the exception class.
   124  	class := &Ex{Class: Class("this is a test")}
   125  	assert.Equal("this is a test", fmt.Sprintf("%v", class))
   126  
   127  	classAndMessage := &Ex{Class: Class("foo"), Message: "bar"}
   128  	assert.Equal("foo; bar", fmt.Sprintf("%v", classAndMessage))
   129  }
   130  
   131  func TestSerializeExamples(t *testing.T) {
   132  	it := assert.New(t)
   133  
   134  	n := (*Ex)(nil)
   135  	e := Ex{}
   136  	c1 := Ex{Class: Class("cls")}
   137  	c2 := Ex{Class: fmt.Errorf("cls")}
   138  	m := Ex{Message: "msg"}
   139  	i1 := Ex{Inner: Class("inner")}
   140  	i2 := Ex{Inner: &Ex{Class: Class("inner")}}
   141  	i3 := Ex{Inner: &Ex{Message: "inner"}}
   142  	i4 := Ex{Inner: fmt.Errorf("inner")}
   143  	s := Ex{StackTrace: StackStrings{"stack"}}
   144  	mc1 := Ex{Message: m.Message, Class: c1.Class}
   145  	mc2 := Ex{Message: m.Message, Class: c2.Class}
   146  	msc1 := Ex{Message: m.Message, Class: c1.Class, StackTrace: s.StackTrace}
   147  	msc2 := Ex{Message: m.Message, Class: c2.Class, StackTrace: s.StackTrace}
   148  
   149  	// Error ((*Ex).Error)
   150  	// it.Equal("<nil>", n.Error()) // panics
   151  	// it.Equal("", e.Error()) // panics
   152  	it.Equal("cls", c1.Error())
   153  	it.Equal("cls", c2.Error())
   154  	// it.Equal(" msg", m.Error()) // panics
   155  	// it.Equal("", i1.Error()) // panics
   156  	// it.Equal("", i2.Error()) // panics
   157  	// it.Equal("", i3.Error()) // panics
   158  	// it.Equal("", i4.Error()) // panics
   159  	// it.Equal(" \nstack", s.Error()) // panics
   160  	it.Equal("cls", mc1.Error())
   161  	it.Equal("cls", mc2.Error())
   162  	it.Equal("cls", msc1.Error())
   163  	it.Equal("cls", msc2.Error())
   164  
   165  	// Stringer ((*Ex).String)
   166  	// it.Equal("<nil>", n.String()) // panics
   167  	it.Equal("", e.String())
   168  	it.Equal("cls", c1.String())
   169  	it.Equal("cls", c2.String())
   170  	it.Equal(" msg", m.String())
   171  	it.Equal("", i1.String())
   172  	it.Equal("", i2.String())
   173  	it.Equal("", i3.String())
   174  	it.Equal("", i4.String())
   175  	it.Equal(" \nstack", s.String())
   176  	it.Equal("cls msg", mc1.String())
   177  	it.Equal("cls msg", mc2.String())
   178  	it.Equal("cls msg \nstack", msc1.String())
   179  	it.Equal("cls msg \nstack", msc2.String())
   180  
   181  	// JSON ((*Ex).MarshalJSON)
   182  	var b []byte
   183  	var err error
   184  	// b, err = n.MarshalJSON() // panics
   185  	// b, err = e.MarshalJSON() // panics
   186  	b, err = c1.MarshalJSON()
   187  	it.Nil(err)
   188  	it.Equal(`{"Class":"cls","Message":""}`, string(b))
   189  	b, err = c2.MarshalJSON()
   190  	it.Nil(err)
   191  	it.Equal(`{"Class":"cls","Message":""}`, string(b))
   192  	// b, err = m.MarshalJSON() // panics
   193  	// b, err = i1.MarshalJSON() // panics
   194  	// b, err = i2.MarshalJSON() // panics
   195  	// b, err = i3.MarshalJSON() // panics
   196  	// b, err = i4.MarshalJSON() // panics
   197  	// b, err = s.MarshalJSON() // panics
   198  	b, err = mc1.MarshalJSON()
   199  	it.Nil(err)
   200  	it.Equal(`{"Class":"cls","Message":"msg"}`, string(b))
   201  	b, err = mc2.MarshalJSON()
   202  	it.Nil(err)
   203  	it.Equal(`{"Class":"cls","Message":"msg"}`, string(b))
   204  	b, err = msc1.MarshalJSON()
   205  	it.Nil(err)
   206  	it.Equal(`{"Class":"cls","Message":"msg","StackTrace":["stack"]}`, string(b))
   207  	b, err = msc2.MarshalJSON()
   208  	it.Nil(err)
   209  	it.Equal(`{"Class":"cls","Message":"msg","StackTrace":["stack"]}`, string(b))
   210  
   211  	// Format: Ex -> v
   212  	it.Equal("<nil>", fmt.Sprintf("%v", n))
   213  	it.Equal("{<nil>  <nil> <nil>}", fmt.Sprintf("%v", e))
   214  	it.Equal("{cls  <nil> <nil>}", fmt.Sprintf("%v", c1))
   215  	it.Equal("{cls  <nil> <nil>}", fmt.Sprintf("%v", c2))
   216  	it.Equal("{<nil> msg <nil> <nil>}", fmt.Sprintf("%v", m))
   217  	it.Equal("{<nil>  inner <nil>}", fmt.Sprintf("%v", i1))
   218  	it.Equal("{<nil>  inner <nil>}", fmt.Sprintf("%v", i2))
   219  	it.Equal("{<nil>  ; inner <nil>}", fmt.Sprintf("%v", i3))
   220  	it.Equal("{<nil>  inner <nil>}", fmt.Sprintf("%v", i4))
   221  	it.Equal("{<nil>  <nil> \nstack}", fmt.Sprintf("%v", s))
   222  	it.Equal("{cls msg <nil> <nil>}", fmt.Sprintf("%v", mc1))
   223  	it.Equal("{cls msg <nil> <nil>}", fmt.Sprintf("%v", mc2))
   224  	it.Equal("{cls msg <nil> \nstack}", fmt.Sprintf("%v", msc1))
   225  	it.Equal("{cls msg <nil> \nstack}", fmt.Sprintf("%v", msc2))
   226  
   227  	// Format: *Ex -> v
   228  	it.Equal("", fmt.Sprintf("%v", &e))
   229  	it.Equal("cls", fmt.Sprintf("%v", &c1))
   230  	it.Equal("cls", fmt.Sprintf("%v", &c2))
   231  	it.Equal("; msg", fmt.Sprintf("%v", &m))
   232  	it.Equal("\ninner", fmt.Sprintf("%v", &i1))
   233  	it.Equal("\ninner", fmt.Sprintf("%v", &i2))
   234  	it.Equal("\n; inner", fmt.Sprintf("%v", &i3))
   235  	it.Equal("\ninner", fmt.Sprintf("%v", &i4))
   236  	it.Equal("", fmt.Sprintf("%v", &s))
   237  	it.Equal("cls; msg", fmt.Sprintf("%v", &mc1))
   238  	it.Equal("cls; msg", fmt.Sprintf("%v", &mc2))
   239  	it.Equal("cls; msg", fmt.Sprintf("%v", &msc1))
   240  	it.Equal("cls; msg", fmt.Sprintf("%v", &msc2))
   241  
   242  	// Format: Ex -> +v
   243  	it.Equal("<nil>", fmt.Sprintf("%+v", n))
   244  	it.Equal("{Class:<nil> Message: Inner:<nil> StackTrace:<nil>}", fmt.Sprintf("%+v", e))
   245  	it.Equal("{Class:cls Message: Inner:<nil> StackTrace:<nil>}", fmt.Sprintf("%+v", c1))
   246  	it.Equal("{Class:cls Message: Inner:<nil> StackTrace:<nil>}", fmt.Sprintf("%+v", c2))
   247  	it.Equal("{Class:<nil> Message:msg Inner:<nil> StackTrace:<nil>}", fmt.Sprintf("%+v", m))
   248  	it.Equal("{Class:<nil> Message: Inner:inner StackTrace:<nil>}", fmt.Sprintf("%+v", i1))
   249  	it.Equal("{Class:<nil> Message: Inner:inner StackTrace:<nil>}", fmt.Sprintf("%+v", i2))
   250  	it.Equal("{Class:<nil> Message: Inner:; inner StackTrace:<nil>}", fmt.Sprintf("%+v", i3))
   251  	it.Equal("{Class:<nil> Message: Inner:inner StackTrace:<nil>}", fmt.Sprintf("%+v", i4))
   252  	it.Equal("{Class:<nil> Message: Inner:<nil> StackTrace:\nstack}", fmt.Sprintf("%+v", s))
   253  	it.Equal("{Class:cls Message:msg Inner:<nil> StackTrace:<nil>}", fmt.Sprintf("%+v", mc1))
   254  	it.Equal("{Class:cls Message:msg Inner:<nil> StackTrace:<nil>}", fmt.Sprintf("%+v", mc2))
   255  	it.Equal("{Class:cls Message:msg Inner:<nil> StackTrace:\nstack}", fmt.Sprintf("%+v", msc1))
   256  	it.Equal("{Class:cls Message:msg Inner:<nil> StackTrace:\nstack}", fmt.Sprintf("%+v", msc2))
   257  
   258  	// Format: *Ex -> +v
   259  	it.Equal("", fmt.Sprintf("%+v", &e))
   260  	it.Equal("cls", fmt.Sprintf("%+v", &c1))
   261  	it.Equal("cls", fmt.Sprintf("%+v", &c2))
   262  	it.Equal("; msg", fmt.Sprintf("%+v", &m))
   263  	it.Equal("\ninner", fmt.Sprintf("%+v", &i1))
   264  	it.Equal("\ninner", fmt.Sprintf("%+v", &i2))
   265  	it.Equal("\n; inner", fmt.Sprintf("%+v", &i3))
   266  	it.Equal("\ninner", fmt.Sprintf("%+v", &i4))
   267  	it.Equal("\nstack", fmt.Sprintf("%+v", &s))
   268  	it.Equal("cls; msg", fmt.Sprintf("%+v", &mc1))
   269  	it.Equal("cls; msg", fmt.Sprintf("%+v", &mc2))
   270  	it.Equal("cls; msg\nstack", fmt.Sprintf("%+v", &msc1))
   271  	it.Equal("cls; msg\nstack", fmt.Sprintf("%+v", &msc2))
   272  
   273  	// Format: Ex -> #v
   274  	it.Equal("<nil>", fmt.Sprintf("%#v", n))
   275  	it.Equal(`ex.Ex{Class:error(nil), Message:"", Inner:error(nil), StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", e))
   276  	it.Equal(`ex.Ex{Class:"cls", Message:"", Inner:error(nil), StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", c1))
   277  	it.HasPrefix(fmt.Sprintf("%#v", c2), `ex.Ex{Class:(*errors.errorString)(0x`)
   278  	it.HasSuffix(fmt.Sprintf("%#v", c2), `), Message:"", Inner:error(nil), StackTrace:ex.StackTrace(nil)}`)
   279  	it.Equal(`ex.Ex{Class:error(nil), Message:"msg", Inner:error(nil), StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", m))
   280  	it.Equal(`ex.Ex{Class:error(nil), Message:"", Inner:"inner", StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", i1))
   281  	it.Equal(`ex.Ex{Class:error(nil), Message:"", Inner:inner, StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", i2))
   282  	it.Equal(`ex.Ex{Class:error(nil), Message:"", Inner:; inner, StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", i3))
   283  	it.HasPrefix(fmt.Sprintf("%#v", i4), `ex.Ex{Class:error(nil), Message:"", Inner:(*errors.errorString)(0x`)
   284  	it.HasSuffix(fmt.Sprintf("%#v", i4), `), StackTrace:ex.StackTrace(nil)}`)
   285  	it.Equal(`ex.Ex{Class:error(nil), Message:"", Inner:error(nil), StackTrace:[]string{"stack"}}`, fmt.Sprintf("%#v", s))
   286  	it.Equal(`ex.Ex{Class:"cls", Message:"msg", Inner:error(nil), StackTrace:ex.StackTrace(nil)}`, fmt.Sprintf("%#v", mc1))
   287  	it.HasPrefix(fmt.Sprintf("%#v", mc2), `ex.Ex{Class:(*errors.errorString)(0x`)
   288  	it.HasSuffix(fmt.Sprintf("%#v", mc2), `), Message:"msg", Inner:error(nil), StackTrace:ex.StackTrace(nil)}`)
   289  	it.Equal(`ex.Ex{Class:"cls", Message:"msg", Inner:error(nil), StackTrace:[]string{"stack"}}`, fmt.Sprintf("%#v", msc1))
   290  	it.HasPrefix(fmt.Sprintf("%#v", msc2), `ex.Ex{Class:(*errors.errorString)(0x`)
   291  	it.HasSuffix(fmt.Sprintf("%#v", msc2), `), Message:"msg", Inner:error(nil), StackTrace:[]string{"stack"}}`)
   292  
   293  	// Format: *Ex -> #v
   294  	it.Equal("", fmt.Sprintf("%#v", &e))
   295  	it.Equal("cls", fmt.Sprintf("%#v", &c1))
   296  	it.Equal("cls", fmt.Sprintf("%#v", &c2))
   297  	it.Equal("; msg", fmt.Sprintf("%#v", &m))
   298  	it.Equal("\ninner", fmt.Sprintf("%#v", &i1))
   299  	it.Equal("\ninner", fmt.Sprintf("%#v", &i2))
   300  	it.Equal("\n; inner", fmt.Sprintf("%#v", &i3))
   301  	it.Equal("\ninner", fmt.Sprintf("%#v", &i4))
   302  	it.Equal("", fmt.Sprintf("%#v", &s))
   303  	it.Equal("cls; msg", fmt.Sprintf("%#v", &mc1))
   304  	it.Equal("cls; msg", fmt.Sprintf("%#v", &mc2))
   305  	it.Equal("cls; msg", fmt.Sprintf("%#v", &msc1))
   306  	it.Equal("cls; msg", fmt.Sprintf("%#v", &msc2))
   307  
   308  	// Format: Ex -> c
   309  	// NOTE: trick the linter
   310  	var cfmt = "%"
   311  	cfmt += "c"
   312  	it.Equal("<nil>", fmt.Sprintf(cfmt, n))
   313  	it.Equal("{<nil> %!c(string=) <nil> <nil>}", fmt.Sprintf(cfmt, e))
   314  	it.Equal("{%!c(ex.Class=cls) %!c(string=) <nil> <nil>}", fmt.Sprintf(cfmt, c1))
   315  	it.Equal("{%!c(*errors.errorString=&{cls}) %!c(string=) <nil> <nil>}", fmt.Sprintf(cfmt, c2))
   316  	it.Equal("{<nil> %!c(string=msg) <nil> <nil>}", fmt.Sprintf(cfmt, m))
   317  	it.Equal("{<nil> %!c(string=) %!c(ex.Class=inner) <nil>}", fmt.Sprintf(cfmt, i1))
   318  	it.Equal("{<nil> %!c(string=) inner <nil>}", fmt.Sprintf(cfmt, i2))
   319  	it.Equal("{<nil> %!c(string=) %!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference) <nil>}", fmt.Sprintf(cfmt, i3))
   320  	it.Equal("{<nil> %!c(string=) %!c(*errors.errorString=&{inner}) <nil>}", fmt.Sprintf(cfmt, i4))
   321  	it.Equal("{<nil> %!c(string=) <nil> }", fmt.Sprintf(cfmt, s))
   322  	it.Equal("{%!c(ex.Class=cls) %!c(string=msg) <nil> <nil>}", fmt.Sprintf(cfmt, mc1))
   323  	it.Equal("{%!c(*errors.errorString=&{cls}) %!c(string=msg) <nil> <nil>}", fmt.Sprintf(cfmt, mc2))
   324  	it.Equal("{%!c(ex.Class=cls) %!c(string=msg) <nil> }", fmt.Sprintf(cfmt, msc1))
   325  	it.Equal("{%!c(*errors.errorString=&{cls}) %!c(string=msg) <nil> }", fmt.Sprintf(cfmt, msc2))
   326  
   327  	// Format: *Ex -> c
   328  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &e))
   329  	it.Equal("cls", fmt.Sprintf("%c", &c1))
   330  	it.Equal("cls", fmt.Sprintf("%c", &c2))
   331  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &m))
   332  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &i1))
   333  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &i2))
   334  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &i3))
   335  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &i4))
   336  	it.Equal("%!c(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference)", fmt.Sprintf("%c", &s))
   337  	it.Equal("cls", fmt.Sprintf("%c", &mc1))
   338  	it.Equal("cls", fmt.Sprintf("%c", &mc2))
   339  	it.Equal("cls", fmt.Sprintf("%c", &msc1))
   340  	it.Equal("cls", fmt.Sprintf("%c", &msc2))
   341  
   342  	// Format: Ex -> i
   343  	it.Equal("<nil>", fmt.Sprintf("%i", any(n)))
   344  	it.Equal("{<nil> %!i(string=) <nil> <nil>}", fmt.Sprintf("%i", any(e)))
   345  	it.Equal("{%!i(ex.Class=cls) %!i(string=) <nil> <nil>}", fmt.Sprintf("%i", any(c1)))
   346  	it.Equal("{%!i(*errors.errorString=&{cls}) %!i(string=) <nil> <nil>}", fmt.Sprintf("%i", any(c2)))
   347  	it.Equal("{<nil> %!i(string=msg) <nil> <nil>}", fmt.Sprintf("%i", any(m)))
   348  	it.Equal("{<nil> %!i(string=) %!i(ex.Class=inner) <nil>}", fmt.Sprintf("%i", any(i1)))
   349  	it.Equal("{<nil> %!i(string=)  <nil>}", fmt.Sprintf("%i", any(i2)))
   350  	it.Equal("{<nil> %!i(string=)  <nil>}", fmt.Sprintf("%i", any(i3)))
   351  	it.Equal("{<nil> %!i(string=) %!i(*errors.errorString=&{inner}) <nil>}", fmt.Sprintf("%i", any(i4)))
   352  	it.Equal("{<nil> %!i(string=) <nil> }", fmt.Sprintf("%i", any(s)))
   353  	it.Equal("{%!i(ex.Class=cls) %!i(string=msg) <nil> <nil>}", fmt.Sprintf("%i", any(mc1)))
   354  	it.Equal("{%!i(*errors.errorString=&{cls}) %!i(string=msg) <nil> <nil>}", fmt.Sprintf("%i", any(mc2)))
   355  	it.Equal("{%!i(ex.Class=cls) %!i(string=msg) <nil> }", fmt.Sprintf("%i", any(msc1)))
   356  	it.Equal("{%!i(*errors.errorString=&{cls}) %!i(string=msg) <nil> }", fmt.Sprintf("%i", any(msc2)))
   357  
   358  	// Format: *Ex -> i
   359  	it.Equal("", fmt.Sprintf("%i", &e))
   360  	it.Equal("", fmt.Sprintf("%i", &c1))
   361  	it.Equal("", fmt.Sprintf("%i", &c2))
   362  	it.Equal("", fmt.Sprintf("%i", &m))
   363  	it.Equal("inner", fmt.Sprintf("%i", &i1))
   364  	it.Equal("", fmt.Sprintf("%i", &i2))
   365  	it.Equal("", fmt.Sprintf("%i", &i3))
   366  	it.Equal("inner", fmt.Sprintf("%i", &i4))
   367  	it.Equal("", fmt.Sprintf("%i", &s))
   368  	it.Equal("", fmt.Sprintf("%i", &mc1))
   369  	it.Equal("", fmt.Sprintf("%i", &mc2))
   370  	it.Equal("", fmt.Sprintf("%i", &msc1))
   371  	it.Equal("", fmt.Sprintf("%i", &msc2))
   372  
   373  	// Format: Ex -> m
   374  	it.Equal("<nil>", fmt.Sprintf("%m", any(n)))
   375  	it.Equal("{<nil> %!m(string=) <nil> <nil>}", fmt.Sprintf("%m", any(e)))
   376  	it.Equal("{%!m(ex.Class=cls) %!m(string=) <nil> <nil>}", fmt.Sprintf("%m", any(c1)))
   377  	it.Equal("{%!m(*errors.errorString=&{cls}) %!m(string=) <nil> <nil>}", fmt.Sprintf("%m", any(c2)))
   378  	it.Equal("{<nil> %!m(string=msg) <nil> <nil>}", fmt.Sprintf("%m", any(m)))
   379  	it.Equal("{<nil> %!m(string=) %!m(ex.Class=inner) <nil>}", fmt.Sprintf("%m", any(i1)))
   380  	it.Equal("{<nil> %!m(string=)  <nil>}", fmt.Sprintf("%m", any(i2)))
   381  	it.Equal("{<nil> %!m(string=) inner <nil>}", fmt.Sprintf("%m", any(i3)))
   382  	it.Equal("{<nil> %!m(string=) %!m(*errors.errorString=&{inner}) <nil>}", fmt.Sprintf("%m", any(i4)))
   383  	it.Equal("{<nil> %!m(string=) <nil> }", fmt.Sprintf("%m", any(s)))
   384  	it.Equal("{%!m(ex.Class=cls) %!m(string=msg) <nil> <nil>}", fmt.Sprintf("%m", any(mc1)))
   385  	it.Equal("{%!m(*errors.errorString=&{cls}) %!m(string=msg) <nil> <nil>}", fmt.Sprintf("%m", any(mc2)))
   386  	it.Equal("{%!m(ex.Class=cls) %!m(string=msg) <nil> }", fmt.Sprintf("%m", any(msc1)))
   387  	it.Equal("{%!m(*errors.errorString=&{cls}) %!m(string=msg) <nil> }", fmt.Sprintf("%m", any(msc2)))
   388  
   389  	// Format: *Ex -> m
   390  	it.Equal("", fmt.Sprintf("%m", &e))
   391  	it.Equal("", fmt.Sprintf("%m", &c1))
   392  	it.Equal("", fmt.Sprintf("%m", &c2))
   393  	it.Equal("msg", fmt.Sprintf("%m", &m))
   394  	it.Equal("", fmt.Sprintf("%m", &i1))
   395  	it.Equal("", fmt.Sprintf("%m", &i2))
   396  	it.Equal("", fmt.Sprintf("%m", &i3))
   397  	it.Equal("", fmt.Sprintf("%m", &i4))
   398  	it.Equal("", fmt.Sprintf("%m", &s))
   399  	it.Equal("msg", fmt.Sprintf("%m", &mc1))
   400  	it.Equal("msg", fmt.Sprintf("%m", &mc2))
   401  	it.Equal("msg", fmt.Sprintf("%m", &msc1))
   402  	it.Equal("msg", fmt.Sprintf("%m", &msc2))
   403  
   404  	// Format: Ex -> q
   405  	it.Equal("<nil>", fmt.Sprintf("%q", n))
   406  	it.Equal(`{<nil> "" <nil> <nil>}`, fmt.Sprintf("%q", e))
   407  	it.Equal(`{"cls" "" <nil> <nil>}`, fmt.Sprintf("%q", c1))
   408  	it.Equal(`{"cls" "" <nil> <nil>}`, fmt.Sprintf("%q", c2))
   409  	it.Equal(`{<nil> "msg" <nil> <nil>}`, fmt.Sprintf("%q", m))
   410  	it.Equal(`{<nil> "" "inner" <nil>}`, fmt.Sprintf("%q", i1))
   411  	it.Equal(`{<nil> "" "" <nil>}`, fmt.Sprintf("%q", i2))
   412  	it.Equal(`{<nil> "" "inner" <nil>}`, fmt.Sprintf("%q", i3))
   413  	it.Equal(`{<nil> "" "inner" <nil>}`, fmt.Sprintf("%q", i4))
   414  	it.Equal(`{<nil> "" <nil> }`, fmt.Sprintf("%q", s))
   415  	it.Equal(`{"cls" "msg" <nil> <nil>}`, fmt.Sprintf("%q", mc1))
   416  	it.Equal(`{"cls" "msg" <nil> <nil>}`, fmt.Sprintf("%q", mc2))
   417  	it.Equal(`{"cls" "msg" <nil> }`, fmt.Sprintf("%q", msc1))
   418  	it.Equal(`{"cls" "msg" <nil> }`, fmt.Sprintf("%q", msc2))
   419  
   420  	// Format: *Ex -> q
   421  	it.Equal(`""`, fmt.Sprintf("%q", &e))
   422  	it.Equal(`""`, fmt.Sprintf("%q", &c1))
   423  	it.Equal(`""`, fmt.Sprintf("%q", &c2))
   424  	it.Equal(`"msg"`, fmt.Sprintf("%q", &m))
   425  	it.Equal(`""`, fmt.Sprintf("%q", &i1))
   426  	it.Equal(`""`, fmt.Sprintf("%q", &i2))
   427  	it.Equal(`""`, fmt.Sprintf("%q", &i3))
   428  	it.Equal(`""`, fmt.Sprintf("%q", &i4))
   429  	it.Equal(`""`, fmt.Sprintf("%q", &s))
   430  	it.Equal(`"msg"`, fmt.Sprintf("%q", &mc1))
   431  	it.Equal(`"msg"`, fmt.Sprintf("%q", &mc2))
   432  	it.Equal(`"msg"`, fmt.Sprintf("%q", &msc1))
   433  	it.Equal(`"msg"`, fmt.Sprintf("%q", &msc2))
   434  
   435  	// Format: Ex -> t
   436  	// NOTE: trick the linter
   437  	var tfmt = "%"
   438  	tfmt += "t"
   439  	it.Equal("", fmt.Sprintf(tfmt, n))
   440  	it.Equal("{<nil> %!t(string=) <nil> <nil>}", fmt.Sprintf(tfmt, any(e)))
   441  	it.Equal("{%!t(ex.Class=cls) %!t(string=) <nil> <nil>}", fmt.Sprintf(tfmt, any(c1)))
   442  	it.Equal("{%!t(*errors.errorString=&{cls}) %!t(string=) <nil> <nil>}", fmt.Sprintf(tfmt, any(c2)))
   443  	it.Equal("{<nil> %!t(string=msg) <nil> <nil>}", fmt.Sprintf(tfmt, any(m)))
   444  	it.Equal("{<nil> %!t(string=) %!t(ex.Class=inner) <nil>}", fmt.Sprintf(tfmt, any(i1)))
   445  	it.Equal("{<nil> %!t(string=)  <nil>}", fmt.Sprintf(tfmt, any(i2)))
   446  	it.Equal("{<nil> %!t(string=)  <nil>}", fmt.Sprintf(tfmt, any(i3)))
   447  	it.Equal("{<nil> %!t(string=) %!t(*errors.errorString=&{inner}) <nil>}", fmt.Sprintf(tfmt, any(i4)))
   448  	it.Equal("{<nil> %!t(string=) <nil> }", fmt.Sprintf(tfmt, any(s)))
   449  	it.Equal("{%!t(ex.Class=cls) %!t(string=msg) <nil> <nil>}", fmt.Sprintf(tfmt, any(mc1)))
   450  	it.Equal("{%!t(*errors.errorString=&{cls}) %!t(string=msg) <nil> <nil>}", fmt.Sprintf(tfmt, any(mc2)))
   451  	it.Equal("{%!t(ex.Class=cls) %!t(string=msg) <nil> }", fmt.Sprintf(tfmt, any(msc1)))
   452  	it.Equal("{%!t(*errors.errorString=&{cls}) %!t(string=msg) <nil> }", fmt.Sprintf(tfmt, any(msc2)))
   453  
   454  	// Format: *Ex -> t
   455  	it.Equal("", fmt.Sprintf("%t", &e))
   456  	it.Equal("", fmt.Sprintf("%t", &c1))
   457  	it.Equal("", fmt.Sprintf("%t", &c2))
   458  	it.Equal("", fmt.Sprintf("%t", &m))
   459  	it.Equal("", fmt.Sprintf("%t", &i1))
   460  	it.Equal("", fmt.Sprintf("%t", &i2))
   461  	it.Equal("", fmt.Sprintf("%t", &i3))
   462  	it.Equal("", fmt.Sprintf("%t", &i4))
   463  	it.Equal("", fmt.Sprintf("%t", &s))
   464  	it.Equal("", fmt.Sprintf("%t", &mc1))
   465  	it.Equal("", fmt.Sprintf("%t", &mc2))
   466  	it.Equal("", fmt.Sprintf("%t", &msc1))
   467  	it.Equal("", fmt.Sprintf("%t", &msc2))
   468  
   469  	// Format: Ex -> [default Sprint]
   470  	it.Equal("<nil>", fmt.Sprint(n))
   471  	it.Equal("{<nil>  <nil> <nil>}", fmt.Sprint(e))
   472  	it.Equal("{cls  <nil> <nil>}", fmt.Sprint(c1))
   473  	it.Equal("{cls  <nil> <nil>}", fmt.Sprint(c2))
   474  	it.Equal("{<nil> msg <nil> <nil>}", fmt.Sprint(m))
   475  	it.Equal("{<nil>  inner <nil>}", fmt.Sprint(i1))
   476  	it.Equal("{<nil>  inner <nil>}", fmt.Sprint(i2))
   477  	it.Equal("{<nil>  ; inner <nil>}", fmt.Sprint(i3))
   478  	it.Equal("{<nil>  inner <nil>}", fmt.Sprint(i4))
   479  	it.Equal("{<nil>  <nil> \nstack}", fmt.Sprint(s))
   480  	it.Equal("{cls msg <nil> <nil>}", fmt.Sprint(mc1))
   481  	it.Equal("{cls msg <nil> <nil>}", fmt.Sprint(mc2))
   482  	it.Equal("{cls msg <nil> \nstack}", fmt.Sprint(msc1))
   483  	it.Equal("{cls msg <nil> \nstack}", fmt.Sprint(msc2))
   484  
   485  	// Format: *Ex -> [default Sprint]
   486  	it.Equal("", fmt.Sprint(&e))
   487  	it.Equal("cls", fmt.Sprint(&c1))
   488  	it.Equal("cls", fmt.Sprint(&c2))
   489  	it.Equal("; msg", fmt.Sprint(&m))
   490  	it.Equal("\ninner", fmt.Sprint(&i1))
   491  	it.Equal("\ninner", fmt.Sprint(&i2))
   492  	it.Equal("\n; inner", fmt.Sprint(&i3))
   493  	it.Equal("\ninner", fmt.Sprint(&i4))
   494  	it.Equal("", fmt.Sprint(&s))
   495  	it.Equal("cls; msg", fmt.Sprint(&mc1))
   496  	it.Equal("cls; msg", fmt.Sprint(&mc2))
   497  	it.Equal("cls; msg", fmt.Sprint(&msc1))
   498  	it.Equal("cls; msg", fmt.Sprint(&msc2))
   499  }
   500  
   501  func TestMarshalJSON(t *testing.T) {
   502  
   503  	type ReadableStackTrace struct {
   504  		Class   string   `json:"Class"`
   505  		Message string   `json:"Message"`
   506  		Inner   error    `json:"Inner"`
   507  		Stack   []string `json:"StackTrace"`
   508  	}
   509  
   510  	a := assert.New(t)
   511  	message := "new test error"
   512  	ex := As(New(message))
   513  	a.NotNil(ex)
   514  	stackTrace := ex.StackTrace
   515  	typed, isTyped := stackTrace.(StackPointers)
   516  	a.True(isTyped)
   517  	a.NotNil(typed)
   518  	stackDepth := len(typed)
   519  
   520  	jsonErr, err := json.Marshal(ex)
   521  	a.Nil(err)
   522  	a.NotNil(jsonErr)
   523  
   524  	ex2 := &ReadableStackTrace{}
   525  	err = json.Unmarshal(jsonErr, ex2)
   526  	a.Nil(err)
   527  	a.Len(ex2.Stack, stackDepth)
   528  	a.Equal(message, ex2.Class)
   529  
   530  	ex = As(New(fmt.Errorf(message)))
   531  	a.NotNil(ex)
   532  	stackTrace = ex.StackTrace
   533  	typed, isTyped = stackTrace.(StackPointers)
   534  	a.True(isTyped)
   535  	a.NotNil(typed)
   536  	stackDepth = len(typed)
   537  
   538  	jsonErr, err = json.Marshal(ex)
   539  	a.Nil(err)
   540  	a.NotNil(jsonErr)
   541  
   542  	ex2 = &ReadableStackTrace{}
   543  	err = json.Unmarshal(jsonErr, ex2)
   544  	a.Nil(err)
   545  	a.Len(ex2.Stack, stackDepth)
   546  	a.Equal(message, ex2.Class)
   547  }
   548  
   549  func TestJSON(t *testing.T) {
   550  	assert := assert.New(t)
   551  
   552  	ex := New("this is a test",
   553  		OptMessage("test message"),
   554  		OptInner(New("inner exception", OptMessagef("inner test message"))),
   555  	)
   556  
   557  	contents, err := json.Marshal(ex)
   558  	assert.Nil(err)
   559  
   560  	var verify Ex
   561  	err = json.Unmarshal(contents, &verify)
   562  	assert.Nil(err)
   563  
   564  	assert.Equal(ErrClass(ex), ErrClass(verify))
   565  	assert.Equal(ErrMessage(ex), ErrMessage(verify))
   566  	assert.NotNil(verify.Inner)
   567  	assert.Equal(ErrClass(ErrInner(ex)), ErrClass(ErrInner(verify)))
   568  	assert.Equal(ErrMessage(ErrInner(ex)), ErrMessage(ErrInner(verify)))
   569  }
   570  
   571  func TestNest(t *testing.T) {
   572  	a := assert.New(t)
   573  
   574  	ex1 := As(New("this is an error"))
   575  	ex2 := As(New("this is another error"))
   576  	err := As(Nest(ex1, ex2))
   577  
   578  	a.NotNil(err)
   579  	a.NotNil(err.Inner)
   580  	a.NotEmpty(err.Error())
   581  
   582  	a.True(Is(ex1, Class("this is an error")))
   583  	a.True(Is(ex1.Inner, Class("this is another error")))
   584  }
   585  
   586  func TestNestNil(t *testing.T) {
   587  	a := assert.New(t)
   588  
   589  	var ex1 error
   590  	var ex2 error
   591  	var ex3 error
   592  
   593  	err := Nest(ex1, ex2, ex3)
   594  	a.Nil(err)
   595  	a.Equal(nil, err)
   596  	a.True(nil == err)
   597  }
   598  
   599  func TestExceptionFormat(t *testing.T) {
   600  	assert := assert.New(t)
   601  
   602  	e := &Ex{Class: fmt.Errorf("this is only a test")}
   603  	output := fmt.Sprintf("%v", e)
   604  	assert.Equal("this is only a test", output)
   605  
   606  	output = fmt.Sprintf("%+v", e)
   607  	assert.Equal("this is only a test", output)
   608  
   609  	e = &Ex{
   610  		Class: fmt.Errorf("this is only a test"),
   611  		StackTrace: StackStrings([]string{
   612  			"foo",
   613  			"bar",
   614  		}),
   615  	}
   616  
   617  	output = fmt.Sprintf("%+v", e)
   618  	assert.Equal("this is only a test\nfoo\nbar", output)
   619  }
   620  
   621  func TestExceptionPrintsInner(t *testing.T) {
   622  	assert := assert.New(t)
   623  
   624  	ex := New("outer", OptInner(New("middle", OptInner(New("terminal")))))
   625  
   626  	output := fmt.Sprintf("%v", ex)
   627  
   628  	assert.Contains(output, "outer")
   629  	assert.Contains(output, "middle")
   630  	assert.Contains(output, "terminal")
   631  
   632  	output = fmt.Sprintf("%+v", ex)
   633  
   634  	assert.Contains(output, "outer")
   635  	assert.Contains(output, "middle")
   636  	assert.Contains(output, "terminal")
   637  }
   638  
   639  type structuredError struct {
   640  	value string
   641  }
   642  
   643  func (err structuredError) Error() string {
   644  	return err.value
   645  }
   646  
   647  func TestException_ErrorsIsCompatability(t *testing.T) {
   648  	assert := assert.New(t)
   649  
   650  	{ // Single nesting, Ex is outermost
   651  		innerErr := errors.New("inner")
   652  		outerErr := New("outer", OptInnerClass(innerErr))
   653  
   654  		assert.True(errors.Is(outerErr, innerErr))
   655  	}
   656  
   657  	{ // Single nesting, Ex is innermost
   658  		innerErr := New("inner")
   659  		outerErr := fmt.Errorf("outer: %w", innerErr)
   660  
   661  		assert.True(errors.Is(outerErr, Class("inner")))
   662  	}
   663  
   664  	{ // Triple nesting, including Ex and non-Ex
   665  		firstErr := errors.New("inner most")
   666  		secondErr := fmt.Errorf("standard err: %w", firstErr)
   667  		thirdErr := New("ex err", OptInner(secondErr))
   668  		fourthErr := New("outer most", OptInner(thirdErr))
   669  
   670  		assert.True(errors.Is(fourthErr, firstErr))
   671  		assert.True(errors.Is(fourthErr, secondErr))
   672  		assert.True(errors.Is(fourthErr, Class("ex err")))
   673  	}
   674  
   675  	{ // Target is nested in an Ex class and not in Inner chain
   676  		firstErr := errors.New("inner most")
   677  		secondErr := fmt.Errorf("standard err: %w", firstErr)
   678  		thirdErr := New(secondErr, OptInner(fmt.Errorf("another cause")))
   679  
   680  		assert.True(errors.Is(thirdErr, firstErr))
   681  		assert.True(errors.Is(thirdErr, secondErr))
   682  	}
   683  
   684  	{ // Simple Ex against a Multi
   685  		firstErr := New("ex")
   686  		secondErr := errors.New("second")
   687  		thirdErr := Multi{firstErr, secondErr}
   688  
   689  		assert.False(errors.Is(firstErr, thirdErr))
   690  	}
   691  
   692  	{ // Ex wrapping a Multi testing against Multi
   693  		firstErr := errors.New("inner most")
   694  		secondErr := fmt.Errorf("standard err: %w", firstErr)
   695  		thirdErr := New(Multi{firstErr, secondErr})
   696  
   697  		assert.True(errors.Is(thirdErr, firstErr))
   698  		assert.True(errors.Is(thirdErr, secondErr))
   699  		assert.True(errors.Is(thirdErr, thirdErr))
   700  		assert.False(errors.Is(thirdErr, Multi{firstErr, secondErr}))
   701  		assert.False(errors.Is(thirdErr, Multi{firstErr, secondErr, secondErr}))
   702  		assert.False(errors.Is(thirdErr, Multi{secondErr, firstErr}))
   703  	}
   704  
   705  	{
   706  		// nil checks
   707  		var nilEx *Ex
   708  		assert.False(errors.Is(nilEx, nil))
   709  		assert.False(errors.Is(&Ex{}, nil))
   710  	}
   711  }
   712  
   713  func TestException_ErrorsAsCompatability(t *testing.T) {
   714  	assert := assert.New(t)
   715  
   716  	{ // Single nesting, targeting non-Ex
   717  		innerErr := structuredError{"inner most"}
   718  		outerErr := New("outer", OptInner(innerErr))
   719  
   720  		var matchedErr structuredError
   721  		assert.True(errors.As(outerErr, &matchedErr))
   722  		assert.Equal("inner most", matchedErr.value)
   723  	}
   724  
   725  	{ // Single nesting, targeting Ex
   726  		innerErr := New("outer most")
   727  		outerErr := fmt.Errorf("outer err: %w", innerErr)
   728  
   729  		var matchedErr *Ex
   730  		assert.True(errors.As(outerErr, &matchedErr))
   731  		assert.Equal("outer most", matchedErr.Class.Error())
   732  	}
   733  
   734  	{ // Single nesting, targeting inner Ex class
   735  		innerErr := New(structuredError{"inner most"})
   736  		outerErr := New("outer most", OptInner(innerErr))
   737  
   738  		var matchedErr structuredError
   739  		assert.True(errors.As(outerErr, &matchedErr))
   740  		assert.Equal("inner most", matchedErr.value)
   741  	}
   742  
   743  	{ // Triple Nesting, targeting non-Ex
   744  		firstErr := structuredError{"inner most"}
   745  		secondErr := fmt.Errorf("standard err: %w", firstErr)
   746  		thirdErr := New("ex err", OptInner(secondErr))
   747  		fourthErr := New("outer most", OptInner(thirdErr))
   748  
   749  		var matchedErr structuredError
   750  		assert.True(errors.As(fourthErr, &matchedErr))
   751  		assert.Equal("inner most", matchedErr.value)
   752  	}
   753  }