format_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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 testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
  347. got := fmt.Sprintf(format, arg)
  348. gotLines := strings.SplitN(got, "\n", -1)
  349. wantLines := strings.SplitN(want, "\n", -1)
  350. if len(wantLines) > len(gotLines) {
  351. t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
  352. return
  353. }
  354. for i, w := range wantLines {
  355. match, err := regexp.MatchString(w, gotLines[i])
  356. if err != nil {
  357. t.Fatal(err)
  358. }
  359. if !match {
  360. t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
  361. }
  362. }
  363. }
  364. var stackLineR = regexp.MustCompile(`\.`)
  365. // parseBlocks parses input into a slice, where:
  366. // - incase entry contains a newline, its a stacktrace
  367. // - incase entry contains no newline, its a solo line.
  368. //
  369. // Detecting stack boundaries only works incase the WithStack-calls are
  370. // to be found on the same line, thats why it is optionally here.
  371. //
  372. // Example use:
  373. //
  374. // for _, e := range blocks {
  375. // if strings.ContainsAny(e, "\n") {
  376. // // Match as stack
  377. // } else {
  378. // // Match as line
  379. // }
  380. // }
  381. //
  382. func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
  383. var blocks []string
  384. stack := ""
  385. wasStack := false
  386. lines := map[string]bool{} // already found lines
  387. for _, l := range strings.Split(input, "\n") {
  388. isStackLine := stackLineR.MatchString(l)
  389. switch {
  390. case !isStackLine && wasStack:
  391. blocks = append(blocks, stack, l)
  392. stack = ""
  393. lines = map[string]bool{}
  394. case isStackLine:
  395. if wasStack {
  396. // Detecting two stacks after another, possible cause lines match in
  397. // our tests due to WithStack(WithStack(io.EOF)) on same line.
  398. if detectStackboundaries {
  399. if lines[l] {
  400. if len(stack) == 0 {
  401. return nil, errors.New("len of block must not be zero here")
  402. }
  403. blocks = append(blocks, stack)
  404. stack = l
  405. lines = map[string]bool{l: true}
  406. continue
  407. }
  408. }
  409. stack = stack + "\n" + l
  410. } else {
  411. stack = l
  412. }
  413. lines[l] = true
  414. case !isStackLine && !wasStack:
  415. blocks = append(blocks, l)
  416. default:
  417. return nil, errors.New("must not happen")
  418. }
  419. wasStack = isStackLine
  420. }
  421. // Use up stack
  422. if stack != "" {
  423. blocks = append(blocks, stack)
  424. }
  425. return blocks, nil
  426. }
  427. func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
  428. gotStr := fmt.Sprintf(format, arg)
  429. got, err := parseBlocks(gotStr, detectStackBoundaries)
  430. if err != nil {
  431. t.Fatal(err)
  432. }
  433. if len(got) != len(want) {
  434. t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
  435. n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
  436. }
  437. for i := range got {
  438. if strings.ContainsAny(want[i], "\n") {
  439. // Match as stack
  440. match, err := regexp.MatchString(want[i], got[i])
  441. if err != nil {
  442. t.Fatal(err)
  443. }
  444. if !match {
  445. 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",
  446. n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
  447. }
  448. } else {
  449. // Match as message
  450. if got[i] != want[i] {
  451. 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])
  452. }
  453. }
  454. }
  455. }
  456. type wrapper struct {
  457. wrap func(err error) error
  458. want []string
  459. }
  460. func prettyBlocks(blocks []string, prefix ...string) string {
  461. var out []string
  462. for _, b := range blocks {
  463. out = append(out, fmt.Sprintf("%v", b))
  464. }
  465. return " " + strings.Join(out, "\n ")
  466. }
  467. func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
  468. if len(beforeWant) == 0 {
  469. panic("beforeWant must not be empty")
  470. }
  471. for _, w := range list {
  472. if len(w.want) == 0 {
  473. panic("want must not be empty")
  474. }
  475. err := w.wrap(beforeErr)
  476. // Copy required cause append(beforeWant, ..) modified beforeWant subtly.
  477. beforeCopy := make([]string, len(beforeWant))
  478. copy(beforeCopy, beforeWant)
  479. beforeWant := beforeCopy
  480. last := len(beforeWant) - 1
  481. var want []string
  482. // Merge two stacks behind each other.
  483. if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
  484. want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
  485. } else {
  486. want = append(beforeWant, w.want...)
  487. }
  488. testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
  489. if maxDepth > 0 {
  490. testGenericRecursive(t, err, want, list, maxDepth-1)
  491. }
  492. }
  493. }