format_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. package errors
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "regexp"
  7. "strings"
  8. "testing"
  9. )
  10. func TestFormatNew(t *testing.T) {
  11. tests := []struct {
  12. error
  13. format string
  14. want string
  15. }{{
  16. New("error"),
  17. "%s",
  18. "error",
  19. }, {
  20. New("error"),
  21. "%v",
  22. "error",
  23. }, {
  24. New("error"),
  25. "%+v",
  26. "error\n" +
  27. "github.com/pkg/errors.TestFormatNew\n" +
  28. "\t.+/github.com/pkg/errors/format_test.go:26",
  29. }, {
  30. New("error"),
  31. "%q",
  32. `"error"`,
  33. }}
  34. for i, tt := range tests {
  35. testFormatRegexp(t, i, tt.error, tt.format, tt.want)
  36. }
  37. }
  38. func TestFormatErrorf(t *testing.T) {
  39. tests := []struct {
  40. error
  41. format string
  42. want string
  43. }{{
  44. Errorf("%s", "error"),
  45. "%s",
  46. "error",
  47. }, {
  48. Errorf("%s", "error"),
  49. "%v",
  50. "error",
  51. }, {
  52. Errorf("%s", "error"),
  53. "%+v",
  54. "error\n" +
  55. "github.com/pkg/errors.TestFormatErrorf\n" +
  56. "\t.+/github.com/pkg/errors/format_test.go:56",
  57. }}
  58. for i, tt := range tests {
  59. testFormatRegexp(t, i, tt.error, tt.format, tt.want)
  60. }
  61. }
  62. func TestFormatWrap(t *testing.T) {
  63. tests := []struct {
  64. error
  65. format string
  66. want string
  67. }{{
  68. Wrap(New("error"), "error2"),
  69. "%s",
  70. "error2: error",
  71. }, {
  72. Wrap(New("error"), "error2"),
  73. "%v",
  74. "error2: error",
  75. }, {
  76. Wrap(New("error"), "error2"),
  77. "%+v",
  78. "error\n" +
  79. "github.com/pkg/errors.TestFormatWrap\n" +
  80. "\t.+/github.com/pkg/errors/format_test.go:82",
  81. }, {
  82. Wrap(io.EOF, "error"),
  83. "%s",
  84. "error: EOF",
  85. }, {
  86. Wrap(io.EOF, "error"),
  87. "%v",
  88. "error: EOF",
  89. }, {
  90. Wrap(io.EOF, "error"),
  91. "%+v",
  92. "EOF\n" +
  93. "error\n" +
  94. "github.com/pkg/errors.TestFormatWrap\n" +
  95. "\t.+/github.com/pkg/errors/format_test.go:96",
  96. }, {
  97. Wrap(Wrap(io.EOF, "error1"), "error2"),
  98. "%+v",
  99. "EOF\n" +
  100. "error1\n" +
  101. "github.com/pkg/errors.TestFormatWrap\n" +
  102. "\t.+/github.com/pkg/errors/format_test.go:103\n",
  103. }, {
  104. Wrap(New("error with space"), "context"),
  105. "%q",
  106. `"context: error with space"`,
  107. }}
  108. for i, tt := range tests {
  109. testFormatRegexp(t, i, tt.error, tt.format, tt.want)
  110. }
  111. }
  112. func TestFormatWrapf(t *testing.T) {
  113. tests := []struct {
  114. error
  115. format string
  116. want string
  117. }{{
  118. Wrapf(io.EOF, "error%d", 2),
  119. "%s",
  120. "error2: EOF",
  121. }, {
  122. Wrapf(io.EOF, "error%d", 2),
  123. "%v",
  124. "error2: EOF",
  125. }, {
  126. Wrapf(io.EOF, "error%d", 2),
  127. "%+v",
  128. "EOF\n" +
  129. "error2\n" +
  130. "github.com/pkg/errors.TestFormatWrapf\n" +
  131. "\t.+/github.com/pkg/errors/format_test.go:134",
  132. }, {
  133. Wrapf(New("error"), "error%d", 2),
  134. "%s",
  135. "error2: error",
  136. }, {
  137. Wrapf(New("error"), "error%d", 2),
  138. "%v",
  139. "error2: error",
  140. }, {
  141. Wrapf(New("error"), "error%d", 2),
  142. "%+v",
  143. "error\n" +
  144. "github.com/pkg/errors.TestFormatWrapf\n" +
  145. "\t.+/github.com/pkg/errors/format_test.go:149",
  146. }}
  147. for i, tt := range tests {
  148. testFormatRegexp(t, i, tt.error, tt.format, tt.want)
  149. }
  150. }
  151. func TestFormatWithStack(t *testing.T) {
  152. tests := []struct {
  153. error
  154. format string
  155. want []string
  156. }{{
  157. WithStack(io.EOF),
  158. "%s",
  159. []string{"EOF"},
  160. }, {
  161. WithStack(io.EOF),
  162. "%v",
  163. []string{"EOF"},
  164. }, {
  165. WithStack(io.EOF),
  166. "%+v",
  167. []string{"EOF",
  168. "github.com/pkg/errors.TestFormatWithStack\n" +
  169. "\t.+/github.com/pkg/errors/format_test.go:175"},
  170. }, {
  171. WithStack(New("error")),
  172. "%s",
  173. []string{"error"},
  174. }, {
  175. WithStack(New("error")),
  176. "%v",
  177. []string{"error"},
  178. }, {
  179. WithStack(New("error")),
  180. "%+v",
  181. []string{"error",
  182. "github.com/pkg/errors.TestFormatWithStack\n" +
  183. "\t.+/github.com/pkg/errors/format_test.go:189",
  184. "github.com/pkg/errors.TestFormatWithStack\n" +
  185. "\t.+/github.com/pkg/errors/format_test.go:189"},
  186. }, {
  187. WithStack(WithStack(io.EOF)),
  188. "%+v",
  189. []string{"EOF",
  190. "github.com/pkg/errors.TestFormatWithStack\n" +
  191. "\t.+/github.com/pkg/errors/format_test.go:197",
  192. "github.com/pkg/errors.TestFormatWithStack\n" +
  193. "\t.+/github.com/pkg/errors/format_test.go:197"},
  194. }, {
  195. WithStack(WithStack(Wrapf(io.EOF, "message"))),
  196. "%+v",
  197. []string{"EOF",
  198. "message",
  199. "github.com/pkg/errors.TestFormatWithStack\n" +
  200. "\t.+/github.com/pkg/errors/format_test.go:205",
  201. "github.com/pkg/errors.TestFormatWithStack\n" +
  202. "\t.+/github.com/pkg/errors/format_test.go:205",
  203. "github.com/pkg/errors.TestFormatWithStack\n" +
  204. "\t.+/github.com/pkg/errors/format_test.go:205"},
  205. }, {
  206. WithStack(Errorf("error%d", 1)),
  207. "%+v",
  208. []string{"error1",
  209. "github.com/pkg/errors.TestFormatWithStack\n" +
  210. "\t.+/github.com/pkg/errors/format_test.go:216",
  211. "github.com/pkg/errors.TestFormatWithStack\n" +
  212. "\t.+/github.com/pkg/errors/format_test.go:216"},
  213. }}
  214. for i, tt := range tests {
  215. testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
  216. }
  217. }
  218. func TestFormatWithMessage(t *testing.T) {
  219. tests := []struct {
  220. error
  221. format string
  222. want []string
  223. }{{
  224. WithMessage(New("error"), "error2"),
  225. "%s",
  226. []string{"error2: error"},
  227. }, {
  228. WithMessage(New("error"), "error2"),
  229. "%v",
  230. []string{"error2: error"},
  231. }, {
  232. WithMessage(New("error"), "error2"),
  233. "%+v",
  234. []string{
  235. "error",
  236. "github.com/pkg/errors.TestFormatWithMessage\n" +
  237. "\t.+/github.com/pkg/errors/format_test.go:244",
  238. "error2"},
  239. }, {
  240. WithMessage(io.EOF, "addition1"),
  241. "%s",
  242. []string{"addition1: EOF"},
  243. }, {
  244. WithMessage(io.EOF, "addition1"),
  245. "%v",
  246. []string{"addition1: EOF"},
  247. }, {
  248. WithMessage(io.EOF, "addition1"),
  249. "%+v",
  250. []string{"EOF", "addition1"},
  251. }, {
  252. WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
  253. "%v",
  254. []string{"addition2: addition1: EOF"},
  255. }, {
  256. WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
  257. "%+v",
  258. []string{"EOF", "addition1", "addition2"},
  259. }, {
  260. Wrap(WithMessage(io.EOF, "error1"), "error2"),
  261. "%+v",
  262. []string{"EOF", "error1", "error2",
  263. "github.com/pkg/errors.TestFormatWithMessage\n" +
  264. "\t.+/github.com/pkg/errors/format_test.go:272"},
  265. }, {
  266. WithMessage(Errorf("error%d", 1), "error2"),
  267. "%+v",
  268. []string{"error1",
  269. "github.com/pkg/errors.TestFormatWithMessage\n" +
  270. "\t.+/github.com/pkg/errors/format_test.go:278",
  271. "error2"},
  272. }, {
  273. WithMessage(WithStack(io.EOF), "error"),
  274. "%+v",
  275. []string{
  276. "EOF",
  277. "github.com/pkg/errors.TestFormatWithMessage\n" +
  278. "\t.+/github.com/pkg/errors/format_test.go:285",
  279. "error"},
  280. }, {
  281. WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
  282. "%+v",
  283. []string{
  284. "EOF",
  285. "github.com/pkg/errors.TestFormatWithMessage\n" +
  286. "\t.+/github.com/pkg/errors/format_test.go:293",
  287. "inside-error",
  288. "github.com/pkg/errors.TestFormatWithMessage\n" +
  289. "\t.+/github.com/pkg/errors/format_test.go:293",
  290. "outside-error"},
  291. }}
  292. for i, tt := range tests {
  293. testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
  294. }
  295. }
  296. func TestFormatGeneric(t *testing.T) {
  297. starts := []struct {
  298. err error
  299. want []string
  300. }{
  301. {New("new-error"), []string{
  302. "new-error",
  303. "github.com/pkg/errors.TestFormatGeneric\n" +
  304. "\t.+/github.com/pkg/errors/format_test.go:315"},
  305. }, {Errorf("errorf-error"), []string{
  306. "errorf-error",
  307. "github.com/pkg/errors.TestFormatGeneric\n" +
  308. "\t.+/github.com/pkg/errors/format_test.go:319"},
  309. }, {errors.New("errors-new-error"), []string{
  310. "errors-new-error"},
  311. },
  312. }
  313. wrappers := []wrapper{
  314. {
  315. func(err error) error { return WithMessage(err, "with-message") },
  316. []string{"with-message"},
  317. }, {
  318. func(err error) error { return WithStack(err) },
  319. []string{
  320. "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" +
  321. ".+/github.com/pkg/errors/format_test.go:333",
  322. },
  323. }, {
  324. func(err error) error { return Wrap(err, "wrap-error") },
  325. []string{
  326. "wrap-error",
  327. "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" +
  328. ".+/github.com/pkg/errors/format_test.go:339",
  329. },
  330. }, {
  331. func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
  332. []string{
  333. "wrapf-error1",
  334. "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" +
  335. ".+/github.com/pkg/errors/format_test.go:346",
  336. },
  337. },
  338. }
  339. for s := range starts {
  340. err := starts[s].err
  341. want := starts[s].want
  342. testFormatCompleteCompare(t, s, err, "%+v", want, false)
  343. testGenericRecursive(t, err, want, wrappers, 3)
  344. }
  345. }
  346. func wrappedNew(message string) error { // This function will be mid-stack inlined in go 1.12+
  347. return New(message)
  348. }
  349. func TestFormatWrappedNew(t *testing.T) {
  350. tests := []struct {
  351. error
  352. format string
  353. want string
  354. }{{
  355. wrappedNew("error"),
  356. "%+v",
  357. "error\n" +
  358. "github.com/pkg/errors.wrappedNew\n" +
  359. "\t.+/github.com/pkg/errors/format_test.go:364\n" +
  360. "github.com/pkg/errors.TestFormatWrappedNew\n" +
  361. "\t.+/github.com/pkg/errors/format_test.go:373",
  362. }}
  363. for i, tt := range tests {
  364. testFormatRegexp(t, i, tt.error, tt.format, tt.want)
  365. }
  366. }
  367. func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
  368. got := fmt.Sprintf(format, arg)
  369. gotLines := strings.SplitN(got, "\n", -1)
  370. wantLines := strings.SplitN(want, "\n", -1)
  371. if len(wantLines) > len(gotLines) {
  372. t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
  373. return
  374. }
  375. for i, w := range wantLines {
  376. match, err := regexp.MatchString(w, gotLines[i])
  377. if err != nil {
  378. t.Fatal(err)
  379. }
  380. if !match {
  381. t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
  382. }
  383. }
  384. }
  385. var stackLineR = regexp.MustCompile(`\.`)
  386. // parseBlocks parses input into a slice, where:
  387. // - incase entry contains a newline, its a stacktrace
  388. // - incase entry contains no newline, its a solo line.
  389. //
  390. // Detecting stack boundaries only works incase the WithStack-calls are
  391. // to be found on the same line, thats why it is optionally here.
  392. //
  393. // Example use:
  394. //
  395. // for _, e := range blocks {
  396. // if strings.ContainsAny(e, "\n") {
  397. // // Match as stack
  398. // } else {
  399. // // Match as line
  400. // }
  401. // }
  402. //
  403. func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
  404. var blocks []string
  405. stack := ""
  406. wasStack := false
  407. lines := map[string]bool{} // already found lines
  408. for _, l := range strings.Split(input, "\n") {
  409. isStackLine := stackLineR.MatchString(l)
  410. switch {
  411. case !isStackLine && wasStack:
  412. blocks = append(blocks, stack, l)
  413. stack = ""
  414. lines = map[string]bool{}
  415. case isStackLine:
  416. if wasStack {
  417. // Detecting two stacks after another, possible cause lines match in
  418. // our tests due to WithStack(WithStack(io.EOF)) on same line.
  419. if detectStackboundaries {
  420. if lines[l] {
  421. if len(stack) == 0 {
  422. return nil, errors.New("len of block must not be zero here")
  423. }
  424. blocks = append(blocks, stack)
  425. stack = l
  426. lines = map[string]bool{l: true}
  427. continue
  428. }
  429. }
  430. stack = stack + "\n" + l
  431. } else {
  432. stack = l
  433. }
  434. lines[l] = true
  435. case !isStackLine && !wasStack:
  436. blocks = append(blocks, l)
  437. default:
  438. return nil, errors.New("must not happen")
  439. }
  440. wasStack = isStackLine
  441. }
  442. // Use up stack
  443. if stack != "" {
  444. blocks = append(blocks, stack)
  445. }
  446. return blocks, nil
  447. }
  448. func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
  449. gotStr := fmt.Sprintf(format, arg)
  450. got, err := parseBlocks(gotStr, detectStackBoundaries)
  451. if err != nil {
  452. t.Fatal(err)
  453. }
  454. if len(got) != len(want) {
  455. t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
  456. n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
  457. }
  458. for i := range got {
  459. if strings.ContainsAny(want[i], "\n") {
  460. // Match as stack
  461. match, err := regexp.MatchString(want[i], got[i])
  462. if err != nil {
  463. t.Fatal(err)
  464. }
  465. if !match {
  466. t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
  467. n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
  468. }
  469. } else {
  470. // Match as message
  471. if got[i] != want[i] {
  472. t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
  473. }
  474. }
  475. }
  476. }
  477. type wrapper struct {
  478. wrap func(err error) error
  479. want []string
  480. }
  481. func prettyBlocks(blocks []string) string {
  482. var out []string
  483. for _, b := range blocks {
  484. out = append(out, fmt.Sprintf("%v", b))
  485. }
  486. return " " + strings.Join(out, "\n ")
  487. }
  488. func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
  489. if len(beforeWant) == 0 {
  490. panic("beforeWant must not be empty")
  491. }
  492. for _, w := range list {
  493. if len(w.want) == 0 {
  494. panic("want must not be empty")
  495. }
  496. err := w.wrap(beforeErr)
  497. // Copy required cause append(beforeWant, ..) modified beforeWant subtly.
  498. beforeCopy := make([]string, len(beforeWant))
  499. copy(beforeCopy, beforeWant)
  500. beforeWant := beforeCopy
  501. last := len(beforeWant) - 1
  502. var want []string
  503. // Merge two stacks behind each other.
  504. if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
  505. want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
  506. } else {
  507. want = append(beforeWant, w.want...)
  508. }
  509. testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
  510. if maxDepth > 0 {
  511. testGenericRecursive(t, err, want, list, maxDepth-1)
  512. }
  513. }
  514. }