error_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. // Copyright (c) 2019 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package multierr
  21. import (
  22. "errors"
  23. "fmt"
  24. "io"
  25. "sync"
  26. "testing"
  27. "github.com/stretchr/testify/assert"
  28. "github.com/stretchr/testify/require"
  29. )
  30. // richFormatError is an error that prints a different output depending on
  31. // whether %v or %+v was used.
  32. type richFormatError struct{}
  33. func (r richFormatError) Error() string {
  34. return fmt.Sprint(r)
  35. }
  36. func (richFormatError) Format(f fmt.State, c rune) {
  37. if c == 'v' && f.Flag('+') {
  38. io.WriteString(f, "multiline\nmessage\nwith plus")
  39. } else {
  40. io.WriteString(f, "without plus")
  41. }
  42. }
  43. func appendN(initial, err error, n int) error {
  44. errs := initial
  45. for i := 0; i < n; i++ {
  46. errs = Append(errs, err)
  47. }
  48. return errs
  49. }
  50. func newMultiErr(errors ...error) error {
  51. return &multiError{errors: errors}
  52. }
  53. func TestCombine(t *testing.T) {
  54. tests := []struct {
  55. // Input
  56. giveErrors []error
  57. // Resulting error
  58. wantError error
  59. // %+v and %v string representations
  60. wantMultiline string
  61. wantSingleline string
  62. }{
  63. {
  64. giveErrors: nil,
  65. wantError: nil,
  66. },
  67. {
  68. giveErrors: []error{},
  69. wantError: nil,
  70. },
  71. {
  72. giveErrors: []error{
  73. errors.New("foo"),
  74. nil,
  75. newMultiErr(
  76. errors.New("bar"),
  77. ),
  78. nil,
  79. },
  80. wantError: newMultiErr(
  81. errors.New("foo"),
  82. errors.New("bar"),
  83. ),
  84. wantMultiline: "the following errors occurred:\n" +
  85. " - foo\n" +
  86. " - bar",
  87. wantSingleline: "foo; bar",
  88. },
  89. {
  90. giveErrors: []error{
  91. errors.New("foo"),
  92. newMultiErr(
  93. errors.New("bar"),
  94. ),
  95. },
  96. wantError: newMultiErr(
  97. errors.New("foo"),
  98. errors.New("bar"),
  99. ),
  100. wantMultiline: "the following errors occurred:\n" +
  101. " - foo\n" +
  102. " - bar",
  103. wantSingleline: "foo; bar",
  104. },
  105. {
  106. giveErrors: []error{errors.New("great sadness")},
  107. wantError: errors.New("great sadness"),
  108. wantMultiline: "great sadness",
  109. wantSingleline: "great sadness",
  110. },
  111. {
  112. giveErrors: []error{
  113. errors.New("foo"),
  114. errors.New("bar"),
  115. },
  116. wantError: newMultiErr(
  117. errors.New("foo"),
  118. errors.New("bar"),
  119. ),
  120. wantMultiline: "the following errors occurred:\n" +
  121. " - foo\n" +
  122. " - bar",
  123. wantSingleline: "foo; bar",
  124. },
  125. {
  126. giveErrors: []error{
  127. errors.New("great sadness"),
  128. errors.New("multi\n line\nerror message"),
  129. errors.New("single line error message"),
  130. },
  131. wantError: newMultiErr(
  132. errors.New("great sadness"),
  133. errors.New("multi\n line\nerror message"),
  134. errors.New("single line error message"),
  135. ),
  136. wantMultiline: "the following errors occurred:\n" +
  137. " - great sadness\n" +
  138. " - multi\n" +
  139. " line\n" +
  140. " error message\n" +
  141. " - single line error message",
  142. wantSingleline: "great sadness; " +
  143. "multi\n line\nerror message; " +
  144. "single line error message",
  145. },
  146. {
  147. giveErrors: []error{
  148. errors.New("foo"),
  149. newMultiErr(
  150. errors.New("bar"),
  151. errors.New("baz"),
  152. ),
  153. errors.New("qux"),
  154. },
  155. wantError: newMultiErr(
  156. errors.New("foo"),
  157. errors.New("bar"),
  158. errors.New("baz"),
  159. errors.New("qux"),
  160. ),
  161. wantMultiline: "the following errors occurred:\n" +
  162. " - foo\n" +
  163. " - bar\n" +
  164. " - baz\n" +
  165. " - qux",
  166. wantSingleline: "foo; bar; baz; qux",
  167. },
  168. {
  169. giveErrors: []error{
  170. errors.New("foo"),
  171. nil,
  172. newMultiErr(
  173. errors.New("bar"),
  174. ),
  175. nil,
  176. },
  177. wantError: newMultiErr(
  178. errors.New("foo"),
  179. errors.New("bar"),
  180. ),
  181. wantMultiline: "the following errors occurred:\n" +
  182. " - foo\n" +
  183. " - bar",
  184. wantSingleline: "foo; bar",
  185. },
  186. {
  187. giveErrors: []error{
  188. errors.New("foo"),
  189. newMultiErr(
  190. errors.New("bar"),
  191. ),
  192. },
  193. wantError: newMultiErr(
  194. errors.New("foo"),
  195. errors.New("bar"),
  196. ),
  197. wantMultiline: "the following errors occurred:\n" +
  198. " - foo\n" +
  199. " - bar",
  200. wantSingleline: "foo; bar",
  201. },
  202. {
  203. giveErrors: []error{
  204. errors.New("foo"),
  205. richFormatError{},
  206. errors.New("bar"),
  207. },
  208. wantError: newMultiErr(
  209. errors.New("foo"),
  210. richFormatError{},
  211. errors.New("bar"),
  212. ),
  213. wantMultiline: "the following errors occurred:\n" +
  214. " - foo\n" +
  215. " - multiline\n" +
  216. " message\n" +
  217. " with plus\n" +
  218. " - bar",
  219. wantSingleline: "foo; without plus; bar",
  220. },
  221. }
  222. for i, tt := range tests {
  223. t.Run(fmt.Sprint(i), func(t *testing.T) {
  224. err := Combine(tt.giveErrors...)
  225. require.Equal(t, tt.wantError, err)
  226. if tt.wantMultiline != "" {
  227. t.Run("Sprintf/multiline", func(t *testing.T) {
  228. assert.Equal(t, tt.wantMultiline, fmt.Sprintf("%+v", err))
  229. })
  230. }
  231. if tt.wantSingleline != "" {
  232. t.Run("Sprintf/singleline", func(t *testing.T) {
  233. assert.Equal(t, tt.wantSingleline, fmt.Sprintf("%v", err))
  234. })
  235. t.Run("Error()", func(t *testing.T) {
  236. assert.Equal(t, tt.wantSingleline, err.Error())
  237. })
  238. if s, ok := err.(fmt.Stringer); ok {
  239. t.Run("String()", func(t *testing.T) {
  240. assert.Equal(t, tt.wantSingleline, s.String())
  241. })
  242. }
  243. }
  244. })
  245. }
  246. }
  247. func TestCombineDoesNotModifySlice(t *testing.T) {
  248. errors := []error{
  249. errors.New("foo"),
  250. nil,
  251. errors.New("bar"),
  252. }
  253. assert.NotNil(t, Combine(errors...))
  254. assert.Len(t, errors, 3)
  255. assert.Nil(t, errors[1], 3)
  256. }
  257. func TestAppend(t *testing.T) {
  258. tests := []struct {
  259. left error
  260. right error
  261. want error
  262. }{
  263. {
  264. left: nil,
  265. right: nil,
  266. want: nil,
  267. },
  268. {
  269. left: nil,
  270. right: errors.New("great sadness"),
  271. want: errors.New("great sadness"),
  272. },
  273. {
  274. left: errors.New("great sadness"),
  275. right: nil,
  276. want: errors.New("great sadness"),
  277. },
  278. {
  279. left: errors.New("foo"),
  280. right: errors.New("bar"),
  281. want: newMultiErr(
  282. errors.New("foo"),
  283. errors.New("bar"),
  284. ),
  285. },
  286. {
  287. left: newMultiErr(
  288. errors.New("foo"),
  289. errors.New("bar"),
  290. ),
  291. right: errors.New("baz"),
  292. want: newMultiErr(
  293. errors.New("foo"),
  294. errors.New("bar"),
  295. errors.New("baz"),
  296. ),
  297. },
  298. {
  299. left: errors.New("baz"),
  300. right: newMultiErr(
  301. errors.New("foo"),
  302. errors.New("bar"),
  303. ),
  304. want: newMultiErr(
  305. errors.New("baz"),
  306. errors.New("foo"),
  307. errors.New("bar"),
  308. ),
  309. },
  310. {
  311. left: newMultiErr(
  312. errors.New("foo"),
  313. ),
  314. right: newMultiErr(
  315. errors.New("bar"),
  316. ),
  317. want: newMultiErr(
  318. errors.New("foo"),
  319. errors.New("bar"),
  320. ),
  321. },
  322. }
  323. for _, tt := range tests {
  324. assert.Equal(t, tt.want, Append(tt.left, tt.right))
  325. }
  326. }
  327. type notMultiErr struct{}
  328. var _ errorGroup = notMultiErr{}
  329. func (notMultiErr) Error() string {
  330. return "great sadness"
  331. }
  332. func (notMultiErr) Errors() []error {
  333. return []error{errors.New("great sadness")}
  334. }
  335. func TestErrors(t *testing.T) {
  336. tests := []struct {
  337. give error
  338. want []error
  339. // Don't attempt to cast to errorGroup or *multiError
  340. dontCast bool
  341. }{
  342. {dontCast: true}, // nil
  343. {
  344. give: errors.New("hi"),
  345. want: []error{errors.New("hi")},
  346. dontCast: true,
  347. },
  348. {
  349. // We don't yet support non-multierr errors.
  350. give: notMultiErr{},
  351. want: []error{notMultiErr{}},
  352. dontCast: true,
  353. },
  354. {
  355. give: Combine(
  356. errors.New("foo"),
  357. errors.New("bar"),
  358. ),
  359. want: []error{
  360. errors.New("foo"),
  361. errors.New("bar"),
  362. },
  363. },
  364. {
  365. give: Append(
  366. errors.New("foo"),
  367. errors.New("bar"),
  368. ),
  369. want: []error{
  370. errors.New("foo"),
  371. errors.New("bar"),
  372. },
  373. },
  374. {
  375. give: Append(
  376. errors.New("foo"),
  377. Combine(
  378. errors.New("bar"),
  379. ),
  380. ),
  381. want: []error{
  382. errors.New("foo"),
  383. errors.New("bar"),
  384. },
  385. },
  386. {
  387. give: Combine(
  388. errors.New("foo"),
  389. Append(
  390. errors.New("bar"),
  391. errors.New("baz"),
  392. ),
  393. errors.New("qux"),
  394. ),
  395. want: []error{
  396. errors.New("foo"),
  397. errors.New("bar"),
  398. errors.New("baz"),
  399. errors.New("qux"),
  400. },
  401. },
  402. }
  403. for i, tt := range tests {
  404. t.Run(fmt.Sprint(i), func(t *testing.T) {
  405. t.Run("Errors()", func(t *testing.T) {
  406. require.Equal(t, tt.want, Errors(tt.give))
  407. })
  408. if tt.dontCast {
  409. return
  410. }
  411. t.Run("multiError", func(t *testing.T) {
  412. require.Equal(t, tt.want, tt.give.(*multiError).Errors())
  413. })
  414. t.Run("errorGroup", func(t *testing.T) {
  415. require.Equal(t, tt.want, tt.give.(errorGroup).Errors())
  416. })
  417. })
  418. }
  419. }
  420. func createMultiErrWithCapacity() error {
  421. // Create a multiError that has capacity for more errors so Append will
  422. // modify the underlying array that may be shared.
  423. return appendN(nil, errors.New("append"), 50)
  424. }
  425. func TestAppendDoesNotModify(t *testing.T) {
  426. initial := createMultiErrWithCapacity()
  427. err1 := Append(initial, errors.New("err1"))
  428. err2 := Append(initial, errors.New("err2"))
  429. // Make sure the error messages match, since we do modify the copyNeeded
  430. // atomic, the values cannot be compared.
  431. assert.EqualError(t, initial, createMultiErrWithCapacity().Error(), "Initial should not be modified")
  432. assert.EqualError(t, err1, Append(createMultiErrWithCapacity(), errors.New("err1")).Error())
  433. assert.EqualError(t, err2, Append(createMultiErrWithCapacity(), errors.New("err2")).Error())
  434. }
  435. func TestAppendRace(t *testing.T) {
  436. initial := createMultiErrWithCapacity()
  437. var wg sync.WaitGroup
  438. for i := 0; i < 10; i++ {
  439. wg.Add(1)
  440. go func() {
  441. defer wg.Done()
  442. err := initial
  443. for j := 0; j < 10; j++ {
  444. err = Append(err, errors.New("err"))
  445. }
  446. }()
  447. }
  448. wg.Wait()
  449. }
  450. func TestErrorsSliceIsImmutable(t *testing.T) {
  451. err1 := errors.New("err1")
  452. err2 := errors.New("err2")
  453. err := Append(err1, err2)
  454. gotErrors := Errors(err)
  455. require.Equal(t, []error{err1, err2}, gotErrors, "errors must match")
  456. gotErrors[0] = nil
  457. gotErrors[1] = errors.New("err3")
  458. require.Equal(t, []error{err1, err2}, Errors(err),
  459. "errors must match after modification")
  460. }
  461. func TestNilMultierror(t *testing.T) {
  462. // For safety, all operations on multiError should be safe even if it is
  463. // nil.
  464. var err *multiError
  465. require.Empty(t, err.Error())
  466. require.Empty(t, err.Errors())
  467. }
  468. func TestAppendInto(t *testing.T) {
  469. tests := []struct {
  470. desc string
  471. into *error
  472. give error
  473. want error
  474. }{
  475. {
  476. desc: "append into empty",
  477. into: new(error),
  478. give: errors.New("foo"),
  479. want: errors.New("foo"),
  480. },
  481. {
  482. desc: "append into non-empty, non-multierr",
  483. into: errorPtr(errors.New("foo")),
  484. give: errors.New("bar"),
  485. want: Combine(
  486. errors.New("foo"),
  487. errors.New("bar"),
  488. ),
  489. },
  490. {
  491. desc: "append into non-empty multierr",
  492. into: errorPtr(Combine(
  493. errors.New("foo"),
  494. errors.New("bar"),
  495. )),
  496. give: errors.New("baz"),
  497. want: Combine(
  498. errors.New("foo"),
  499. errors.New("bar"),
  500. errors.New("baz"),
  501. ),
  502. },
  503. }
  504. for _, tt := range tests {
  505. t.Run(tt.desc, func(t *testing.T) {
  506. assert.True(t, AppendInto(tt.into, tt.give))
  507. assert.Equal(t, tt.want, *tt.into)
  508. })
  509. }
  510. }
  511. func TestAppendIntoNil(t *testing.T) {
  512. t.Run("nil pointer panics", func(t *testing.T) {
  513. assert.Panics(t, func() {
  514. AppendInto(nil, errors.New("foo"))
  515. })
  516. })
  517. t.Run("nil error is no-op", func(t *testing.T) {
  518. t.Run("empty left", func(t *testing.T) {
  519. var err error
  520. assert.False(t, AppendInto(&err, nil))
  521. assert.Nil(t, err)
  522. })
  523. t.Run("non-empty left", func(t *testing.T) {
  524. err := errors.New("foo")
  525. assert.False(t, AppendInto(&err, nil))
  526. assert.Equal(t, errors.New("foo"), err)
  527. })
  528. })
  529. }
  530. func errorPtr(err error) *error {
  531. return &err
  532. }