Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 

2830 rindas
80 KiB

  1. // Copyright 2017, The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE.md file.
  4. package cmp_test
  5. import (
  6. "bytes"
  7. "crypto/md5"
  8. "encoding/json"
  9. "fmt"
  10. "io"
  11. "math"
  12. "math/rand"
  13. "reflect"
  14. "regexp"
  15. "sort"
  16. "strings"
  17. "sync"
  18. "testing"
  19. "time"
  20. "github.com/google/go-cmp/cmp"
  21. "github.com/google/go-cmp/cmp/cmpopts"
  22. "github.com/google/go-cmp/cmp/internal/flags"
  23. pb "github.com/google/go-cmp/cmp/internal/testprotos"
  24. ts "github.com/google/go-cmp/cmp/internal/teststructs"
  25. )
  26. func init() {
  27. flags.Deterministic = true
  28. }
  29. var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
  30. func intPtr(n int) *int { return &n }
  31. type test struct {
  32. label string // Test name
  33. x, y interface{} // Input values to compare
  34. opts []cmp.Option // Input options
  35. wantDiff string // The exact difference string
  36. wantPanic string // Sub-string of an expected panic message
  37. reason string // The reason for the expected outcome
  38. }
  39. func TestDiff(t *testing.T) {
  40. var tests []test
  41. tests = append(tests, comparerTests()...)
  42. tests = append(tests, transformerTests()...)
  43. tests = append(tests, embeddedTests()...)
  44. tests = append(tests, methodTests()...)
  45. tests = append(tests, project1Tests()...)
  46. tests = append(tests, project2Tests()...)
  47. tests = append(tests, project3Tests()...)
  48. tests = append(tests, project4Tests()...)
  49. for _, tt := range tests {
  50. tt := tt
  51. t.Run(tt.label, func(t *testing.T) {
  52. t.Parallel()
  53. var gotDiff, gotPanic string
  54. func() {
  55. defer func() {
  56. if ex := recover(); ex != nil {
  57. if s, ok := ex.(string); ok {
  58. gotPanic = s
  59. } else {
  60. panic(ex)
  61. }
  62. }
  63. }()
  64. gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
  65. }()
  66. // TODO: Require every test case to provide a reason.
  67. if tt.wantPanic == "" {
  68. if gotPanic != "" {
  69. t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
  70. }
  71. tt.wantDiff = strings.TrimPrefix(tt.wantDiff, "\n")
  72. if gotDiff != tt.wantDiff {
  73. t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s\nreason: %v", gotDiff, tt.wantDiff, tt.reason)
  74. }
  75. } else {
  76. if !strings.Contains(gotPanic, tt.wantPanic) {
  77. t.Fatalf("panic message:\ngot: %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
  78. }
  79. }
  80. })
  81. }
  82. }
  83. func comparerTests() []test {
  84. const label = "Comparer"
  85. type Iface1 interface {
  86. Method()
  87. }
  88. type Iface2 interface {
  89. Method()
  90. }
  91. type tarHeader struct {
  92. Name string
  93. Mode int64
  94. Uid int
  95. Gid int
  96. Size int64
  97. ModTime time.Time
  98. Typeflag byte
  99. Linkname string
  100. Uname string
  101. Gname string
  102. Devmajor int64
  103. Devminor int64
  104. AccessTime time.Time
  105. ChangeTime time.Time
  106. Xattrs map[string]string
  107. }
  108. makeTarHeaders := func(tf byte) (hs []tarHeader) {
  109. for i := 0; i < 5; i++ {
  110. hs = append(hs, tarHeader{
  111. Name: fmt.Sprintf("some/dummy/test/file%d", i),
  112. Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i),
  113. ModTime: now.Add(time.Duration(i) * time.Hour),
  114. Uname: "user", Gname: "group",
  115. Typeflag: tf,
  116. })
  117. }
  118. return hs
  119. }
  120. return []test{{
  121. label: label,
  122. x: nil,
  123. y: nil,
  124. }, {
  125. label: label,
  126. x: 1,
  127. y: 1,
  128. }, {
  129. label: label,
  130. x: 1,
  131. y: 1,
  132. opts: []cmp.Option{cmp.Ignore()},
  133. wantPanic: "cannot use an unfiltered option",
  134. }, {
  135. label: label,
  136. x: 1,
  137. y: 1,
  138. opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
  139. wantPanic: "cannot use an unfiltered option",
  140. }, {
  141. label: label,
  142. x: 1,
  143. y: 1,
  144. opts: []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })},
  145. wantPanic: "cannot use an unfiltered option",
  146. }, {
  147. label: label,
  148. x: 1,
  149. y: 1,
  150. opts: []cmp.Option{
  151. cmp.Comparer(func(x, y int) bool { return true }),
  152. cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
  153. },
  154. wantPanic: "ambiguous set of applicable options",
  155. }, {
  156. label: label,
  157. x: 1,
  158. y: 1,
  159. opts: []cmp.Option{
  160. cmp.FilterPath(func(p cmp.Path) bool {
  161. return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
  162. }, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
  163. cmp.Comparer(func(x, y int) bool { return true }),
  164. cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
  165. },
  166. }, {
  167. label: label,
  168. opts: []cmp.Option{struct{ cmp.Option }{}},
  169. wantPanic: "unknown option",
  170. }, {
  171. label: label,
  172. x: struct{ A, B, C int }{1, 2, 3},
  173. y: struct{ A, B, C int }{1, 2, 3},
  174. }, {
  175. label: label,
  176. x: struct{ A, B, C int }{1, 2, 3},
  177. y: struct{ A, B, C int }{1, 2, 4},
  178. wantDiff: `
  179. struct{ A int; B int; C int }{
  180. A: 1,
  181. B: 2,
  182. - C: 3,
  183. + C: 4,
  184. }
  185. `,
  186. }, {
  187. label: label,
  188. x: struct{ a, b, c int }{1, 2, 3},
  189. y: struct{ a, b, c int }{1, 2, 4},
  190. wantPanic: "cannot handle unexported field",
  191. }, {
  192. label: label,
  193. x: &struct{ A *int }{intPtr(4)},
  194. y: &struct{ A *int }{intPtr(4)},
  195. }, {
  196. label: label,
  197. x: &struct{ A *int }{intPtr(4)},
  198. y: &struct{ A *int }{intPtr(5)},
  199. wantDiff: `
  200. &struct{ A *int }{
  201. - A: &4,
  202. + A: &5,
  203. }
  204. `,
  205. }, {
  206. label: label,
  207. x: &struct{ A *int }{intPtr(4)},
  208. y: &struct{ A *int }{intPtr(5)},
  209. opts: []cmp.Option{
  210. cmp.Comparer(func(x, y int) bool { return true }),
  211. },
  212. }, {
  213. label: label,
  214. x: &struct{ A *int }{intPtr(4)},
  215. y: &struct{ A *int }{intPtr(5)},
  216. opts: []cmp.Option{
  217. cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
  218. },
  219. }, {
  220. label: label,
  221. x: &struct{ R *bytes.Buffer }{},
  222. y: &struct{ R *bytes.Buffer }{},
  223. }, {
  224. label: label,
  225. x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
  226. y: &struct{ R *bytes.Buffer }{},
  227. wantDiff: `
  228. &struct{ R *bytes.Buffer }{
  229. - R: s"",
  230. + R: nil,
  231. }
  232. `,
  233. }, {
  234. label: label,
  235. x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
  236. y: &struct{ R *bytes.Buffer }{},
  237. opts: []cmp.Option{
  238. cmp.Comparer(func(x, y io.Reader) bool { return true }),
  239. },
  240. }, {
  241. label: label,
  242. x: &struct{ R bytes.Buffer }{},
  243. y: &struct{ R bytes.Buffer }{},
  244. wantPanic: "cannot handle unexported field",
  245. }, {
  246. label: label,
  247. x: &struct{ R bytes.Buffer }{},
  248. y: &struct{ R bytes.Buffer }{},
  249. opts: []cmp.Option{
  250. cmp.Comparer(func(x, y io.Reader) bool { return true }),
  251. },
  252. wantPanic: "cannot handle unexported field",
  253. }, {
  254. label: label,
  255. x: &struct{ R bytes.Buffer }{},
  256. y: &struct{ R bytes.Buffer }{},
  257. opts: []cmp.Option{
  258. cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
  259. cmp.Comparer(func(x, y io.Reader) bool { return true }),
  260. },
  261. }, {
  262. label: label,
  263. x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
  264. y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
  265. wantPanic: "cannot handle unexported field",
  266. }, {
  267. label: label,
  268. x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
  269. y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
  270. opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
  271. if x == nil || y == nil {
  272. return x == nil && y == nil
  273. }
  274. return x.String() == y.String()
  275. })},
  276. }, {
  277. label: label,
  278. x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
  279. y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
  280. opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
  281. if x == nil || y == nil {
  282. return x == nil && y == nil
  283. }
  284. return x.String() == y.String()
  285. })},
  286. wantDiff: `
  287. []*regexp.Regexp{
  288. nil,
  289. - s"a*b*c*",
  290. + s"a*b*d*",
  291. }
  292. `,
  293. }, {
  294. label: label,
  295. x: func() ***int {
  296. a := 0
  297. b := &a
  298. c := &b
  299. return &c
  300. }(),
  301. y: func() ***int {
  302. a := 0
  303. b := &a
  304. c := &b
  305. return &c
  306. }(),
  307. }, {
  308. label: label,
  309. x: func() ***int {
  310. a := 0
  311. b := &a
  312. c := &b
  313. return &c
  314. }(),
  315. y: func() ***int {
  316. a := 1
  317. b := &a
  318. c := &b
  319. return &c
  320. }(),
  321. wantDiff: `
  322. &&&int(
  323. - 0,
  324. + 1,
  325. )
  326. `,
  327. }, {
  328. label: label,
  329. x: []int{1, 2, 3, 4, 5}[:3],
  330. y: []int{1, 2, 3},
  331. }, {
  332. label: label,
  333. x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
  334. y: struct{ fmt.Stringer }{regexp.MustCompile("hello")},
  335. opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
  336. }, {
  337. label: label,
  338. x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
  339. y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
  340. opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
  341. wantDiff: `
  342. struct{ fmt.Stringer }(
  343. - s"hello",
  344. + s"hello2",
  345. )
  346. `,
  347. }, {
  348. label: label,
  349. x: md5.Sum([]byte{'a'}),
  350. y: md5.Sum([]byte{'b'}),
  351. wantDiff: `
  352. [16]uint8{
  353. - 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61,
  354. + 0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f,
  355. }
  356. `,
  357. }, {
  358. label: label,
  359. x: new(fmt.Stringer),
  360. y: nil,
  361. wantDiff: `
  362. interface{}(
  363. - &fmt.Stringer(nil),
  364. )
  365. `,
  366. }, {
  367. label: label,
  368. x: makeTarHeaders('0'),
  369. y: makeTarHeaders('\x00'),
  370. wantDiff: `
  371. []cmp_test.tarHeader{
  372. {
  373. ... // 4 identical fields
  374. Size: 1,
  375. ModTime: s"2009-11-10 23:00:00 +0000 UTC",
  376. - Typeflag: 0x30,
  377. + Typeflag: 0x00,
  378. Linkname: "",
  379. Uname: "user",
  380. ... // 6 identical fields
  381. },
  382. {
  383. ... // 4 identical fields
  384. Size: 2,
  385. ModTime: s"2009-11-11 00:00:00 +0000 UTC",
  386. - Typeflag: 0x30,
  387. + Typeflag: 0x00,
  388. Linkname: "",
  389. Uname: "user",
  390. ... // 6 identical fields
  391. },
  392. {
  393. ... // 4 identical fields
  394. Size: 4,
  395. ModTime: s"2009-11-11 01:00:00 +0000 UTC",
  396. - Typeflag: 0x30,
  397. + Typeflag: 0x00,
  398. Linkname: "",
  399. Uname: "user",
  400. ... // 6 identical fields
  401. },
  402. {
  403. ... // 4 identical fields
  404. Size: 8,
  405. ModTime: s"2009-11-11 02:00:00 +0000 UTC",
  406. - Typeflag: 0x30,
  407. + Typeflag: 0x00,
  408. Linkname: "",
  409. Uname: "user",
  410. ... // 6 identical fields
  411. },
  412. {
  413. ... // 4 identical fields
  414. Size: 16,
  415. ModTime: s"2009-11-11 03:00:00 +0000 UTC",
  416. - Typeflag: 0x30,
  417. + Typeflag: 0x00,
  418. Linkname: "",
  419. Uname: "user",
  420. ... // 6 identical fields
  421. },
  422. }
  423. `,
  424. }, {
  425. label: label,
  426. x: make([]int, 1000),
  427. y: make([]int, 1000),
  428. opts: []cmp.Option{
  429. cmp.Comparer(func(_, _ int) bool {
  430. return rand.Intn(2) == 0
  431. }),
  432. },
  433. wantPanic: "non-deterministic or non-symmetric function detected",
  434. }, {
  435. label: label,
  436. x: make([]int, 1000),
  437. y: make([]int, 1000),
  438. opts: []cmp.Option{
  439. cmp.FilterValues(func(_, _ int) bool {
  440. return rand.Intn(2) == 0
  441. }, cmp.Ignore()),
  442. },
  443. wantPanic: "non-deterministic or non-symmetric function detected",
  444. }, {
  445. label: label,
  446. x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
  447. y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
  448. opts: []cmp.Option{
  449. cmp.Comparer(func(x, y int) bool {
  450. return x < y
  451. }),
  452. },
  453. wantPanic: "non-deterministic or non-symmetric function detected",
  454. }, {
  455. label: label,
  456. x: make([]string, 1000),
  457. y: make([]string, 1000),
  458. opts: []cmp.Option{
  459. cmp.Transformer("λ", func(x string) int {
  460. return rand.Int()
  461. }),
  462. },
  463. wantPanic: "non-deterministic function detected",
  464. }, {
  465. // Make sure the dynamic checks don't raise a false positive for
  466. // non-reflexive comparisons.
  467. label: label,
  468. x: make([]int, 10),
  469. y: make([]int, 10),
  470. opts: []cmp.Option{
  471. cmp.Transformer("λ", func(x int) float64 {
  472. return math.NaN()
  473. }),
  474. },
  475. wantDiff: `
  476. []int{
  477. - Inverse(λ, float64(NaN)),
  478. + Inverse(λ, float64(NaN)),
  479. - Inverse(λ, float64(NaN)),
  480. + Inverse(λ, float64(NaN)),
  481. - Inverse(λ, float64(NaN)),
  482. + Inverse(λ, float64(NaN)),
  483. - Inverse(λ, float64(NaN)),
  484. + Inverse(λ, float64(NaN)),
  485. - Inverse(λ, float64(NaN)),
  486. + Inverse(λ, float64(NaN)),
  487. - Inverse(λ, float64(NaN)),
  488. + Inverse(λ, float64(NaN)),
  489. - Inverse(λ, float64(NaN)),
  490. + Inverse(λ, float64(NaN)),
  491. - Inverse(λ, float64(NaN)),
  492. + Inverse(λ, float64(NaN)),
  493. - Inverse(λ, float64(NaN)),
  494. + Inverse(λ, float64(NaN)),
  495. - Inverse(λ, float64(NaN)),
  496. + Inverse(λ, float64(NaN)),
  497. }
  498. `,
  499. }, {
  500. // Ensure reasonable Stringer formatting of map keys.
  501. label: label,
  502. x: map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
  503. y: map[*pb.Stringer]*pb.Stringer(nil),
  504. wantDiff: `
  505. map[*testprotos.Stringer]*testprotos.Stringer(
  506. - {⟪0xdeadf00f⟫: s"world"},
  507. + nil,
  508. )
  509. `,
  510. }, {
  511. // Ensure Stringer avoids double-quote escaping if possible.
  512. label: label,
  513. x: []*pb.Stringer{{`multi\nline\nline\nline`}},
  514. wantDiff: strings.Replace(`
  515. interface{}(
  516. - []*testprotos.Stringer{s'multi\nline\nline\nline'},
  517. )
  518. `, "'", "`", -1),
  519. }, {
  520. label: label,
  521. x: struct{ I Iface2 }{},
  522. y: struct{ I Iface2 }{},
  523. opts: []cmp.Option{
  524. cmp.Comparer(func(x, y Iface1) bool {
  525. return x == nil && y == nil
  526. }),
  527. },
  528. }, {
  529. label: label,
  530. x: struct{ I Iface2 }{},
  531. y: struct{ I Iface2 }{},
  532. opts: []cmp.Option{
  533. cmp.Transformer("λ", func(v Iface1) bool {
  534. return v == nil
  535. }),
  536. },
  537. }, {
  538. label: label,
  539. x: struct{ I Iface2 }{},
  540. y: struct{ I Iface2 }{},
  541. opts: []cmp.Option{
  542. cmp.FilterValues(func(x, y Iface1) bool {
  543. return x == nil && y == nil
  544. }, cmp.Ignore()),
  545. },
  546. }, {
  547. label: label,
  548. x: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
  549. y: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65.0, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63.0, "name": "Sammy Sosa"}},
  550. wantDiff: `
  551. []interface{}{
  552. map[string]interface{}{
  553. "avg": float64(0.278),
  554. - "hr": int(65),
  555. + "hr": float64(65),
  556. "name": string("Mark McGwire"),
  557. },
  558. map[string]interface{}{
  559. "avg": float64(0.288),
  560. - "hr": int(63),
  561. + "hr": float64(63),
  562. "name": string("Sammy Sosa"),
  563. },
  564. }
  565. `,
  566. }, {
  567. label: label,
  568. x: map[*int]string{
  569. new(int): "hello",
  570. },
  571. y: map[*int]string{
  572. new(int): "world",
  573. },
  574. wantDiff: `
  575. map[*int]string{
  576. - ⟪0xdeadf00f⟫: "hello",
  577. + ⟪0xdeadf00f⟫: "world",
  578. }
  579. `,
  580. }, {
  581. label: label,
  582. x: intPtr(0),
  583. y: intPtr(0),
  584. opts: []cmp.Option{
  585. cmp.Comparer(func(x, y *int) bool { return x == y }),
  586. },
  587. // TODO: This output is unhelpful and should show the address.
  588. wantDiff: `
  589. (*int)(
  590. - &0,
  591. + &0,
  592. )
  593. `,
  594. }, {
  595. label: label,
  596. x: [2][]int{
  597. {0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
  598. {0, 1, 0, 0, 0, 20},
  599. },
  600. y: [2][]int{
  601. {1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0},
  602. {0, 0, 1, 2, 0, 0, 0},
  603. },
  604. opts: []cmp.Option{
  605. cmp.FilterPath(func(p cmp.Path) bool {
  606. vx, vy := p.Last().Values()
  607. if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
  608. return true
  609. }
  610. if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
  611. return true
  612. }
  613. return false
  614. }, cmp.Ignore()),
  615. },
  616. wantDiff: `
  617. [2][]int{
  618. {..., 1, 2, 3, ..., 4, 5, 6, 7, ..., 8, ..., 9, ...},
  619. {
  620. ... // 6 ignored and 1 identical elements
  621. - 20,
  622. + 2,
  623. ... // 3 ignored elements
  624. },
  625. }
  626. `,
  627. reason: "all zero slice elements are ignored (even if missing)",
  628. }, {
  629. label: label,
  630. x: [2]map[string]int{
  631. {"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0},
  632. {"keep1": 1, "ignore1": 0},
  633. },
  634. y: [2]map[string]int{
  635. {"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3},
  636. {"keep1": 1, "keep2": 2, "ignore2": 0},
  637. },
  638. opts: []cmp.Option{
  639. cmp.FilterPath(func(p cmp.Path) bool {
  640. vx, vy := p.Last().Values()
  641. if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
  642. return true
  643. }
  644. if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
  645. return true
  646. }
  647. return false
  648. }, cmp.Ignore()),
  649. },
  650. wantDiff: `
  651. [2]map[string]int{
  652. {"KEEP3": 3, "keep1": 1, "keep2": 2, ...},
  653. {
  654. ... // 2 ignored entries
  655. "keep1": 1,
  656. + "keep2": 2,
  657. },
  658. }
  659. `,
  660. reason: "all zero map entries are ignored (even if missing)",
  661. }}
  662. }
  663. func transformerTests() []test {
  664. type StringBytes struct {
  665. String string
  666. Bytes []byte
  667. }
  668. const label = "Transformer"
  669. transformOnce := func(name string, f interface{}) cmp.Option {
  670. xform := cmp.Transformer(name, f)
  671. return cmp.FilterPath(func(p cmp.Path) bool {
  672. for _, ps := range p {
  673. if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
  674. return false
  675. }
  676. }
  677. return true
  678. }, xform)
  679. }
  680. return []test{{
  681. label: label,
  682. x: uint8(0),
  683. y: uint8(1),
  684. opts: []cmp.Option{
  685. cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }),
  686. cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }),
  687. cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }),
  688. },
  689. wantDiff: `
  690. uint8(Inverse(λ, uint16(Inverse(λ, uint32(Inverse(λ, uint64(
  691. - 0x00,
  692. + 0x01,
  693. )))))))
  694. `,
  695. }, {
  696. label: label,
  697. x: 0,
  698. y: 1,
  699. opts: []cmp.Option{
  700. cmp.Transformer("λ", func(in int) int { return in / 2 }),
  701. cmp.Transformer("λ", func(in int) int { return in }),
  702. },
  703. wantPanic: "ambiguous set of applicable options",
  704. }, {
  705. label: label,
  706. x: []int{0, -5, 0, -1},
  707. y: []int{1, 3, 0, -5},
  708. opts: []cmp.Option{
  709. cmp.FilterValues(
  710. func(x, y int) bool { return x+y >= 0 },
  711. cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }),
  712. ),
  713. cmp.FilterValues(
  714. func(x, y int) bool { return x+y < 0 },
  715. cmp.Transformer("λ", func(in int) int64 { return int64(in) }),
  716. ),
  717. },
  718. wantDiff: `
  719. []int{
  720. Inverse(λ, int64(0)),
  721. - Inverse(λ, int64(-5)),
  722. + Inverse(λ, int64(3)),
  723. Inverse(λ, int64(0)),
  724. - Inverse(λ, int64(-1)),
  725. + Inverse(λ, int64(-5)),
  726. }
  727. `,
  728. }, {
  729. label: label,
  730. x: 0,
  731. y: 1,
  732. opts: []cmp.Option{
  733. cmp.Transformer("λ", func(in int) interface{} {
  734. if in == 0 {
  735. return "zero"
  736. }
  737. return float64(in)
  738. }),
  739. },
  740. wantDiff: `
  741. int(Inverse(λ, interface{}(
  742. - string("zero"),
  743. + float64(1),
  744. )))
  745. `,
  746. }, {
  747. label: label,
  748. x: `{
  749. "firstName": "John",
  750. "lastName": "Smith",
  751. "age": 25,
  752. "isAlive": true,
  753. "address": {
  754. "city": "Los Angeles",
  755. "postalCode": "10021-3100",
  756. "state": "CA",
  757. "streetAddress": "21 2nd Street"
  758. },
  759. "phoneNumbers": [{
  760. "type": "home",
  761. "number": "212 555-4321"
  762. },{
  763. "type": "office",
  764. "number": "646 555-4567"
  765. },{
  766. "number": "123 456-7890",
  767. "type": "mobile"
  768. }],
  769. "children": []
  770. }`,
  771. y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
  772. "address":{"streetAddress":"21 2nd Street","city":"New York",
  773. "state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home",
  774. "number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{
  775. "type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
  776. opts: []cmp.Option{
  777. transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
  778. if err := json.Unmarshal([]byte(s), &m); err != nil {
  779. panic(err)
  780. }
  781. return m
  782. }),
  783. },
  784. wantDiff: `
  785. string(Inverse(ParseJSON, map[string]interface{}{
  786. "address": map[string]interface{}{
  787. - "city": string("Los Angeles"),
  788. + "city": string("New York"),
  789. "postalCode": string("10021-3100"),
  790. - "state": string("CA"),
  791. + "state": string("NY"),
  792. "streetAddress": string("21 2nd Street"),
  793. },
  794. "age": float64(25),
  795. "children": []interface{}{},
  796. "firstName": string("John"),
  797. "isAlive": bool(true),
  798. "lastName": string("Smith"),
  799. "phoneNumbers": []interface{}{
  800. map[string]interface{}{
  801. - "number": string("212 555-4321"),
  802. + "number": string("212 555-1234"),
  803. "type": string("home"),
  804. },
  805. map[string]interface{}{"number": string("646 555-4567"), "type": string("office")},
  806. map[string]interface{}{"number": string("123 456-7890"), "type": string("mobile")},
  807. },
  808. + "spouse": nil,
  809. }))
  810. `,
  811. }, {
  812. label: label,
  813. x: StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
  814. y: StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
  815. opts: []cmp.Option{
  816. transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
  817. transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
  818. },
  819. wantDiff: `
  820. cmp_test.StringBytes{
  821. String: Inverse(SplitString, []string{
  822. "some",
  823. "multi",
  824. - "Line",
  825. + "line",
  826. "string",
  827. }),
  828. Bytes: []uint8(Inverse(SplitBytes, [][]uint8{
  829. {0x73, 0x6f, 0x6d, 0x65},
  830. {0x6d, 0x75, 0x6c, 0x74, 0x69},
  831. {0x6c, 0x69, 0x6e, 0x65},
  832. {
  833. - 0x62,
  834. + 0x42,
  835. 0x79,
  836. 0x74,
  837. ... // 2 identical elements
  838. },
  839. })),
  840. }
  841. `,
  842. }, {
  843. x: "a\nb\nc\n",
  844. y: "a\nb\nc\n",
  845. opts: []cmp.Option{
  846. cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
  847. },
  848. wantPanic: "recursive set of Transformers detected",
  849. }, {
  850. x: complex64(0),
  851. y: complex64(0),
  852. opts: []cmp.Option{
  853. cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
  854. cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
  855. cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
  856. },
  857. wantPanic: "recursive set of Transformers detected",
  858. }}
  859. }
  860. func reporterTests() []test {
  861. const label = "Reporter"
  862. type (
  863. MyString string
  864. MyByte byte
  865. MyBytes []byte
  866. MyInt int8
  867. MyInts []int8
  868. MyUint int16
  869. MyUints []int16
  870. MyFloat float32
  871. MyFloats []float32
  872. MyComposite struct {
  873. StringA string
  874. StringB MyString
  875. BytesA []byte
  876. BytesB []MyByte
  877. BytesC MyBytes
  878. IntsA []int8
  879. IntsB []MyInt
  880. IntsC MyInts
  881. UintsA []uint16
  882. UintsB []MyUint
  883. UintsC MyUints
  884. FloatsA []float32
  885. FloatsB []MyFloat
  886. FloatsC MyFloats
  887. }
  888. )
  889. return []test{{
  890. label: label,
  891. x: MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  892. y: MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  893. wantDiff: `
  894. cmp_test.MyComposite{
  895. ... // 3 identical fields
  896. BytesB: nil,
  897. BytesC: nil,
  898. IntsA: []int8{
  899. + 10,
  900. 11,
  901. - 12,
  902. + 21,
  903. 13,
  904. 14,
  905. ... // 15 identical elements
  906. },
  907. IntsB: nil,
  908. IntsC: nil,
  909. ... // 6 identical fields
  910. }
  911. `,
  912. reason: "unbatched diffing desired since few elements differ",
  913. }, {
  914. label: label,
  915. x: MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  916. y: MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
  917. wantDiff: `
  918. cmp_test.MyComposite{
  919. ... // 3 identical fields
  920. BytesB: nil,
  921. BytesC: nil,
  922. IntsA: []int8{
  923. - 10, 11, 12, 13, 14, 15, 16,
  924. + 12, 29, 13, 27, 22, 23,
  925. 17, 18, 19, 20, 21,
  926. - 22, 23, 24, 25, 26, 27, 28, 29,
  927. + 10, 26, 16, 25, 28, 11, 15, 24, 14,
  928. },
  929. IntsB: nil,
  930. IntsC: nil,
  931. ... // 6 identical fields
  932. }
  933. `,
  934. reason: "batched diffing desired since many elements differ",
  935. }, {
  936. label: label,
  937. x: MyComposite{
  938. BytesA: []byte{1, 2, 3},
  939. BytesB: []MyByte{4, 5, 6},
  940. BytesC: MyBytes{7, 8, 9},
  941. IntsA: []int8{-1, -2, -3},
  942. IntsB: []MyInt{-4, -5, -6},
  943. IntsC: MyInts{-7, -8, -9},
  944. UintsA: []uint16{1000, 2000, 3000},
  945. UintsB: []MyUint{4000, 5000, 6000},
  946. UintsC: MyUints{7000, 8000, 9000},
  947. FloatsA: []float32{1.5, 2.5, 3.5},
  948. FloatsB: []MyFloat{4.5, 5.5, 6.5},
  949. FloatsC: MyFloats{7.5, 8.5, 9.5},
  950. },
  951. y: MyComposite{
  952. BytesA: []byte{3, 2, 1},
  953. BytesB: []MyByte{6, 5, 4},
  954. BytesC: MyBytes{9, 8, 7},
  955. IntsA: []int8{-3, -2, -1},
  956. IntsB: []MyInt{-6, -5, -4},
  957. IntsC: MyInts{-9, -8, -7},
  958. UintsA: []uint16{3000, 2000, 1000},
  959. UintsB: []MyUint{6000, 5000, 4000},
  960. UintsC: MyUints{9000, 8000, 7000},
  961. FloatsA: []float32{3.5, 2.5, 1.5},
  962. FloatsB: []MyFloat{6.5, 5.5, 4.5},
  963. FloatsC: MyFloats{9.5, 8.5, 7.5},
  964. },
  965. wantDiff: `
  966. cmp_test.MyComposite{
  967. StringA: "",
  968. StringB: "",
  969. BytesA: []uint8{
  970. - 0x01, 0x02, 0x03, // -|...|
  971. + 0x03, 0x02, 0x01, // +|...|
  972. },
  973. BytesB: []cmp_test.MyByte{
  974. - 0x04, 0x05, 0x06,
  975. + 0x06, 0x05, 0x04,
  976. },
  977. BytesC: cmp_test.MyBytes{
  978. - 0x07, 0x08, 0x09, // -|...|
  979. + 0x09, 0x08, 0x07, // +|...|
  980. },
  981. IntsA: []int8{
  982. - -1, -2, -3,
  983. + -3, -2, -1,
  984. },
  985. IntsB: []cmp_test.MyInt{
  986. - -4, -5, -6,
  987. + -6, -5, -4,
  988. },
  989. IntsC: cmp_test.MyInts{
  990. - -7, -8, -9,
  991. + -9, -8, -7,
  992. },
  993. UintsA: []uint16{
  994. - 0x03e8, 0x07d0, 0x0bb8,
  995. + 0x0bb8, 0x07d0, 0x03e8,
  996. },
  997. UintsB: []cmp_test.MyUint{
  998. - 4000, 5000, 6000,
  999. + 6000, 5000, 4000,
  1000. },
  1001. UintsC: cmp_test.MyUints{
  1002. - 7000, 8000, 9000,
  1003. + 9000, 8000, 7000,
  1004. },
  1005. FloatsA: []float32{
  1006. - 1.5, 2.5, 3.5,
  1007. + 3.5, 2.5, 1.5,
  1008. },
  1009. FloatsB: []cmp_test.MyFloat{
  1010. - 4.5, 5.5, 6.5,
  1011. + 6.5, 5.5, 4.5,
  1012. },
  1013. FloatsC: cmp_test.MyFloats{
  1014. - 7.5, 8.5, 9.5,
  1015. + 9.5, 8.5, 7.5,
  1016. },
  1017. }
  1018. `,
  1019. reason: "batched diffing available for both named and unnamed slices",
  1020. }, {
  1021. label: label,
  1022. x: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeX\x95A\xfd$fX\x8byT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1U~{\xf6\xb3~\x1dWi \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
  1023. y: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1u-[]]\xf6\xb3haha~\x1dWI \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
  1024. wantDiff: `
  1025. cmp_test.MyComposite{
  1026. StringA: "",
  1027. StringB: "",
  1028. BytesA: []uint8{
  1029. 0xf3, 0x0f, 0x8a, 0xa4, 0xd3, 0x12, 0x52, 0x09, 0x24, 0xbe, // |......R.$.|
  1030. - 0x58, 0x95, 0x41, 0xfd, 0x24, 0x66, 0x58, 0x8b, 0x79, // -|X.A.$fX.y|
  1031. 0x54, 0xac, 0x0d, 0xd8, 0x71, 0x77, 0x70, 0x20, 0x6a, 0x5c, 0x73, 0x7f, 0x8c, 0x17, 0x55, 0xc0, // |T...qwp j\s...U.|
  1032. 0x34, 0xce, 0x6e, 0xf7, 0xaa, 0x47, 0xee, 0x32, 0x9d, 0xc5, 0xca, 0x1e, 0x58, 0xaf, 0x8f, 0x27, // |4.n..G.2....X..'|
  1033. 0xf3, 0x02, 0x4a, 0x90, 0xed, 0x69, 0x2e, 0x70, 0x32, 0xb4, 0xab, 0x30, 0x20, 0xb6, 0xbd, 0x5c, // |..J..i.p2..0 ..\|
  1034. 0x62, 0x34, 0x17, 0xb0, 0x00, 0xbb, 0x4f, 0x7e, 0x27, 0x47, 0x06, 0xf4, 0x2e, 0x66, 0xfd, 0x63, // |b4....O~'G...f.c|
  1035. 0xd7, 0x04, 0xdd, 0xb7, 0x30, 0xb7, 0xd1, // |....0..|
  1036. - 0x55, 0x7e, 0x7b, 0xf6, 0xb3, 0x7e, 0x1d, 0x57, 0x69, // -|U~{..~.Wi|
  1037. + 0x75, 0x2d, 0x5b, 0x5d, 0x5d, 0xf6, 0xb3, 0x68, 0x61, 0x68, 0x61, 0x7e, 0x1d, 0x57, 0x49, // +|u-[]]..haha~.WI|
  1038. 0x20, 0x9e, 0xbc, 0xdf, 0xe1, 0x4d, 0xa9, 0xef, 0xa2, 0xd2, 0xed, 0xb4, 0x47, 0x78, 0xc9, 0xc9, // | ....M......Gx..|
  1039. 0x27, 0xa4, 0xc6, 0xce, 0xec, 0x44, 0x70, 0x5d, // |'....Dp]|
  1040. },
  1041. BytesB: nil,
  1042. BytesC: nil,
  1043. ... // 9 identical fields
  1044. }
  1045. `,
  1046. reason: "binary diff in hexdump form since data is binary data",
  1047. }, {
  1048. label: label,
  1049. x: MyComposite{StringB: MyString("readme.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000046\x0000000000000\x00011173\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
  1050. y: MyComposite{StringB: MyString("gopher.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000043\x0000000000000\x00011217\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
  1051. wantDiff: `
  1052. cmp_test.MyComposite{
  1053. StringA: "",
  1054. StringB: cmp_test.MyString{
  1055. - 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, // -|readme|
  1056. + 0x67, 0x6f, 0x70, 0x68, 0x65, 0x72, // +|gopher|
  1057. 0x2e, 0x74, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |.txt............|
  1058. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |................|
  1059. ... // 64 identical bytes
  1060. 0x30, 0x30, 0x36, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, // |00600.0000000.00|
  1061. 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, // |00000.0000000004|
  1062. - 0x36, // -|6|
  1063. + 0x33, // +|3|
  1064. 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x31, 0x31, // |.00000000000.011|
  1065. - 0x31, 0x37, 0x33, // -|173|
  1066. + 0x32, 0x31, 0x37, // +|217|
  1067. 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |. 0.............|
  1068. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |................|
  1069. ... // 326 identical bytes
  1070. },
  1071. BytesA: nil,
  1072. BytesB: nil,
  1073. ... // 10 identical fields
  1074. }
  1075. `,
  1076. reason: "binary diff desired since string looks like binary data",
  1077. }, {
  1078. label: label,
  1079. x: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"314 54th Avenue","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)},
  1080. y: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)},
  1081. wantDiff: strings.Replace(`
  1082. cmp_test.MyComposite{
  1083. StringA: "",
  1084. StringB: "",
  1085. BytesA: bytes.Join({
  1086. '{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"',
  1087. 'address":{"streetAddress":"',
  1088. - "314 54th Avenue",
  1089. + "21 2nd Street",
  1090. '","city":"New York","state":"NY","postalCode":"10021-3100"},"pho',
  1091. 'neNumbers":[{"type":"home","number":"212 555-1234"},{"type":"off',
  1092. ... // 101 identical bytes
  1093. }, ""),
  1094. BytesB: nil,
  1095. BytesC: nil,
  1096. ... // 9 identical fields
  1097. }
  1098. `, "'", "`", -1),
  1099. reason: "batched textual diff desired since bytes looks like textual data",
  1100. }, {
  1101. label: label,
  1102. x: MyComposite{
  1103. StringA: strings.TrimPrefix(`
  1104. Package cmp determines equality of values.
  1105. This package is intended to be a more powerful and safer alternative to
  1106. reflect.DeepEqual for comparing whether two values are semantically equal.
  1107. The primary features of cmp are:
  1108. • When the default behavior of equality does not suit the needs of the test,
  1109. custom equality functions can override the equality operation.
  1110. For example, an equality function may report floats as equal so long as they
  1111. are within some tolerance of each other.
  1112. • Types that have an Equal method may use that method to determine equality.
  1113. This allows package authors to determine the equality operation for the types
  1114. that they define.
  1115. • If no custom equality functions are used and no Equal method is defined,
  1116. equality is determined by recursively comparing the primitive kinds on both
  1117. values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
  1118. fields are not compared by default; they result in panics unless suppressed
  1119. by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
  1120. using the AllowUnexported option.
  1121. `, "\n"),
  1122. },
  1123. y: MyComposite{
  1124. StringA: strings.TrimPrefix(`
  1125. Package cmp determines equality of value.
  1126. This package is intended to be a more powerful and safer alternative to
  1127. reflect.DeepEqual for comparing whether two values are semantically equal.
  1128. The primary features of cmp are:
  1129. • When the default behavior of equality does not suit the needs of the test,
  1130. custom equality functions can override the equality operation.
  1131. For example, an equality function may report floats as equal so long as they
  1132. are within some tolerance of each other.
  1133. • If no custom equality functions are used and no Equal method is defined,
  1134. equality is determined by recursively comparing the primitive kinds on both
  1135. values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
  1136. fields are not compared by default; they result in panics unless suppressed
  1137. by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
  1138. using the AllowUnexported option.`, "\n"),
  1139. },
  1140. wantDiff: `
  1141. cmp_test.MyComposite{
  1142. StringA: strings.Join({
  1143. - "Package cmp determines equality of values.",
  1144. + "Package cmp determines equality of value.",
  1145. "",
  1146. "This package is intended to be a more powerful and safer alternative to",
  1147. ... // 6 identical lines
  1148. "For example, an equality function may report floats as equal so long as they",
  1149. "are within some tolerance of each other.",
  1150. - "",
  1151. - "• Types that have an Equal method may use that method to determine equality.",
  1152. - "This allows package authors to determine the equality operation for the types",
  1153. - "that they define.",
  1154. "",
  1155. "• If no custom equality functions are used and no Equal method is defined,",
  1156. ... // 3 identical lines
  1157. "by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared",
  1158. "using the AllowUnexported option.",
  1159. - "",
  1160. }, "\n"),
  1161. StringB: "",
  1162. BytesA: nil,
  1163. ... // 11 identical fields
  1164. }
  1165. `,
  1166. reason: "batched per-line diff desired since string looks like multi-line textual data",
  1167. }}
  1168. }
  1169. func embeddedTests() []test {
  1170. const label = "EmbeddedStruct/"
  1171. privateStruct := *new(ts.ParentStructA).PrivateStruct()
  1172. createStructA := func(i int) ts.ParentStructA {
  1173. s := ts.ParentStructA{}
  1174. s.PrivateStruct().Public = 1 + i
  1175. s.PrivateStruct().SetPrivate(2 + i)
  1176. return s
  1177. }
  1178. createStructB := func(i int) ts.ParentStructB {
  1179. s := ts.ParentStructB{}
  1180. s.PublicStruct.Public = 1 + i
  1181. s.PublicStruct.SetPrivate(2 + i)
  1182. return s
  1183. }
  1184. createStructC := func(i int) ts.ParentStructC {
  1185. s := ts.ParentStructC{}
  1186. s.PrivateStruct().Public = 1 + i
  1187. s.PrivateStruct().SetPrivate(2 + i)
  1188. s.Public = 3 + i
  1189. s.SetPrivate(4 + i)
  1190. return s
  1191. }
  1192. createStructD := func(i int) ts.ParentStructD {
  1193. s := ts.ParentStructD{}
  1194. s.PublicStruct.Public = 1 + i
  1195. s.PublicStruct.SetPrivate(2 + i)
  1196. s.Public = 3 + i
  1197. s.SetPrivate(4 + i)
  1198. return s
  1199. }
  1200. createStructE := func(i int) ts.ParentStructE {
  1201. s := ts.ParentStructE{}
  1202. s.PrivateStruct().Public = 1 + i
  1203. s.PrivateStruct().SetPrivate(2 + i)
  1204. s.PublicStruct.Public = 3 + i
  1205. s.PublicStruct.SetPrivate(4 + i)
  1206. return s
  1207. }
  1208. createStructF := func(i int) ts.ParentStructF {
  1209. s := ts.ParentStructF{}
  1210. s.PrivateStruct().Public = 1 + i
  1211. s.PrivateStruct().SetPrivate(2 + i)
  1212. s.PublicStruct.Public = 3 + i
  1213. s.PublicStruct.SetPrivate(4 + i)
  1214. s.Public = 5 + i
  1215. s.SetPrivate(6 + i)
  1216. return s
  1217. }
  1218. createStructG := func(i int) *ts.ParentStructG {
  1219. s := ts.NewParentStructG()
  1220. s.PrivateStruct().Public = 1 + i
  1221. s.PrivateStruct().SetPrivate(2 + i)
  1222. return s
  1223. }
  1224. createStructH := func(i int) *ts.ParentStructH {
  1225. s := ts.NewParentStructH()
  1226. s.PublicStruct.Public = 1 + i
  1227. s.PublicStruct.SetPrivate(2 + i)
  1228. return s
  1229. }
  1230. createStructI := func(i int) *ts.ParentStructI {
  1231. s := ts.NewParentStructI()
  1232. s.PrivateStruct().Public = 1 + i
  1233. s.PrivateStruct().SetPrivate(2 + i)
  1234. s.PublicStruct.Public = 3 + i
  1235. s.PublicStruct.SetPrivate(4 + i)
  1236. return s
  1237. }
  1238. createStructJ := func(i int) *ts.ParentStructJ {
  1239. s := ts.NewParentStructJ()
  1240. s.PrivateStruct().Public = 1 + i
  1241. s.PrivateStruct().SetPrivate(2 + i)
  1242. s.PublicStruct.Public = 3 + i
  1243. s.PublicStruct.SetPrivate(4 + i)
  1244. s.Private().Public = 5 + i
  1245. s.Private().SetPrivate(6 + i)
  1246. s.Public.Public = 7 + i
  1247. s.Public.SetPrivate(8 + i)
  1248. return s
  1249. }
  1250. // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/21122).
  1251. wantPanicNotGo110 := func(s string) string {
  1252. if !flags.AtLeastGo110 {
  1253. return ""
  1254. }
  1255. return s
  1256. }
  1257. return []test{{
  1258. label: label + "ParentStructA",
  1259. x: ts.ParentStructA{},
  1260. y: ts.ParentStructA{},
  1261. wantPanic: "cannot handle unexported field",
  1262. }, {
  1263. label: label + "ParentStructA",
  1264. x: ts.ParentStructA{},
  1265. y: ts.ParentStructA{},
  1266. opts: []cmp.Option{
  1267. cmpopts.IgnoreUnexported(ts.ParentStructA{}),
  1268. },
  1269. }, {
  1270. label: label + "ParentStructA",
  1271. x: createStructA(0),
  1272. y: createStructA(0),
  1273. opts: []cmp.Option{
  1274. cmp.AllowUnexported(ts.ParentStructA{}),
  1275. },
  1276. wantPanic: "cannot handle unexported field",
  1277. }, {
  1278. label: label + "ParentStructA",
  1279. x: createStructA(0),
  1280. y: createStructA(0),
  1281. opts: []cmp.Option{
  1282. cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
  1283. },
  1284. }, {
  1285. label: label + "ParentStructA",
  1286. x: createStructA(0),
  1287. y: createStructA(1),
  1288. opts: []cmp.Option{
  1289. cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
  1290. },
  1291. wantDiff: `
  1292. teststructs.ParentStructA{
  1293. privateStruct: teststructs.privateStruct{
  1294. - Public: 1,
  1295. + Public: 2,
  1296. - private: 2,
  1297. + private: 3,
  1298. },
  1299. }
  1300. `,
  1301. }, {
  1302. label: label + "ParentStructB",
  1303. x: ts.ParentStructB{},
  1304. y: ts.ParentStructB{},
  1305. opts: []cmp.Option{
  1306. cmpopts.IgnoreUnexported(ts.ParentStructB{}),
  1307. },
  1308. wantPanic: "cannot handle unexported field",
  1309. }, {
  1310. label: label + "ParentStructB",
  1311. x: ts.ParentStructB{},
  1312. y: ts.ParentStructB{},
  1313. opts: []cmp.Option{
  1314. cmpopts.IgnoreUnexported(ts.ParentStructB{}),
  1315. cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1316. },
  1317. }, {
  1318. label: label + "ParentStructB",
  1319. x: createStructB(0),
  1320. y: createStructB(0),
  1321. opts: []cmp.Option{
  1322. cmp.AllowUnexported(ts.ParentStructB{}),
  1323. },
  1324. wantPanic: "cannot handle unexported field",
  1325. }, {
  1326. label: label + "ParentStructB",
  1327. x: createStructB(0),
  1328. y: createStructB(0),
  1329. opts: []cmp.Option{
  1330. cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
  1331. },
  1332. }, {
  1333. label: label + "ParentStructB",
  1334. x: createStructB(0),
  1335. y: createStructB(1),
  1336. opts: []cmp.Option{
  1337. cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
  1338. },
  1339. wantDiff: `
  1340. teststructs.ParentStructB{
  1341. PublicStruct: teststructs.PublicStruct{
  1342. - Public: 1,
  1343. + Public: 2,
  1344. - private: 2,
  1345. + private: 3,
  1346. },
  1347. }
  1348. `,
  1349. }, {
  1350. label: label + "ParentStructC",
  1351. x: ts.ParentStructC{},
  1352. y: ts.ParentStructC{},
  1353. wantPanic: "cannot handle unexported field",
  1354. }, {
  1355. label: label + "ParentStructC",
  1356. x: ts.ParentStructC{},
  1357. y: ts.ParentStructC{},
  1358. opts: []cmp.Option{
  1359. cmpopts.IgnoreUnexported(ts.ParentStructC{}),
  1360. },
  1361. }, {
  1362. label: label + "ParentStructC",
  1363. x: createStructC(0),
  1364. y: createStructC(0),
  1365. opts: []cmp.Option{
  1366. cmp.AllowUnexported(ts.ParentStructC{}),
  1367. },
  1368. wantPanic: "cannot handle unexported field",
  1369. }, {
  1370. label: label + "ParentStructC",
  1371. x: createStructC(0),
  1372. y: createStructC(0),
  1373. opts: []cmp.Option{
  1374. cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
  1375. },
  1376. }, {
  1377. label: label + "ParentStructC",
  1378. x: createStructC(0),
  1379. y: createStructC(1),
  1380. opts: []cmp.Option{
  1381. cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
  1382. },
  1383. wantDiff: `
  1384. teststructs.ParentStructC{
  1385. privateStruct: teststructs.privateStruct{
  1386. - Public: 1,
  1387. + Public: 2,
  1388. - private: 2,
  1389. + private: 3,
  1390. },
  1391. - Public: 3,
  1392. + Public: 4,
  1393. - private: 4,
  1394. + private: 5,
  1395. }
  1396. `,
  1397. }, {
  1398. label: label + "ParentStructD",
  1399. x: ts.ParentStructD{},
  1400. y: ts.ParentStructD{},
  1401. opts: []cmp.Option{
  1402. cmpopts.IgnoreUnexported(ts.ParentStructD{}),
  1403. },
  1404. wantPanic: "cannot handle unexported field",
  1405. }, {
  1406. label: label + "ParentStructD",
  1407. x: ts.ParentStructD{},
  1408. y: ts.ParentStructD{},
  1409. opts: []cmp.Option{
  1410. cmpopts.IgnoreUnexported(ts.ParentStructD{}),
  1411. cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1412. },
  1413. }, {
  1414. label: label + "ParentStructD",
  1415. x: createStructD(0),
  1416. y: createStructD(0),
  1417. opts: []cmp.Option{
  1418. cmp.AllowUnexported(ts.ParentStructD{}),
  1419. },
  1420. wantPanic: "cannot handle unexported field",
  1421. }, {
  1422. label: label + "ParentStructD",
  1423. x: createStructD(0),
  1424. y: createStructD(0),
  1425. opts: []cmp.Option{
  1426. cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
  1427. },
  1428. }, {
  1429. label: label + "ParentStructD",
  1430. x: createStructD(0),
  1431. y: createStructD(1),
  1432. opts: []cmp.Option{
  1433. cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
  1434. },
  1435. wantDiff: `
  1436. teststructs.ParentStructD{
  1437. PublicStruct: teststructs.PublicStruct{
  1438. - Public: 1,
  1439. + Public: 2,
  1440. - private: 2,
  1441. + private: 3,
  1442. },
  1443. - Public: 3,
  1444. + Public: 4,
  1445. - private: 4,
  1446. + private: 5,
  1447. }
  1448. `,
  1449. }, {
  1450. label: label + "ParentStructE",
  1451. x: ts.ParentStructE{},
  1452. y: ts.ParentStructE{},
  1453. opts: []cmp.Option{
  1454. cmpopts.IgnoreUnexported(ts.ParentStructE{}),
  1455. },
  1456. wantPanic: "cannot handle unexported field",
  1457. }, {
  1458. label: label + "ParentStructE",
  1459. x: ts.ParentStructE{},
  1460. y: ts.ParentStructE{},
  1461. opts: []cmp.Option{
  1462. cmpopts.IgnoreUnexported(ts.ParentStructE{}),
  1463. cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1464. },
  1465. }, {
  1466. label: label + "ParentStructE",
  1467. x: createStructE(0),
  1468. y: createStructE(0),
  1469. opts: []cmp.Option{
  1470. cmp.AllowUnexported(ts.ParentStructE{}),
  1471. },
  1472. wantPanic: "cannot handle unexported field",
  1473. }, {
  1474. label: label + "ParentStructE",
  1475. x: createStructE(0),
  1476. y: createStructE(0),
  1477. opts: []cmp.Option{
  1478. cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
  1479. },
  1480. wantPanic: "cannot handle unexported field",
  1481. }, {
  1482. label: label + "ParentStructE",
  1483. x: createStructE(0),
  1484. y: createStructE(0),
  1485. opts: []cmp.Option{
  1486. cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
  1487. },
  1488. }, {
  1489. label: label + "ParentStructE",
  1490. x: createStructE(0),
  1491. y: createStructE(1),
  1492. opts: []cmp.Option{
  1493. cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
  1494. },
  1495. wantDiff: `
  1496. teststructs.ParentStructE{
  1497. privateStruct: teststructs.privateStruct{
  1498. - Public: 1,
  1499. + Public: 2,
  1500. - private: 2,
  1501. + private: 3,
  1502. },
  1503. PublicStruct: teststructs.PublicStruct{
  1504. - Public: 3,
  1505. + Public: 4,
  1506. - private: 4,
  1507. + private: 5,
  1508. },
  1509. }
  1510. `,
  1511. }, {
  1512. label: label + "ParentStructF",
  1513. x: ts.ParentStructF{},
  1514. y: ts.ParentStructF{},
  1515. opts: []cmp.Option{
  1516. cmpopts.IgnoreUnexported(ts.ParentStructF{}),
  1517. },
  1518. wantPanic: "cannot handle unexported field",
  1519. }, {
  1520. label: label + "ParentStructF",
  1521. x: ts.ParentStructF{},
  1522. y: ts.ParentStructF{},
  1523. opts: []cmp.Option{
  1524. cmpopts.IgnoreUnexported(ts.ParentStructF{}),
  1525. cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1526. },
  1527. }, {
  1528. label: label + "ParentStructF",
  1529. x: createStructF(0),
  1530. y: createStructF(0),
  1531. opts: []cmp.Option{
  1532. cmp.AllowUnexported(ts.ParentStructF{}),
  1533. },
  1534. wantPanic: "cannot handle unexported field",
  1535. }, {
  1536. label: label + "ParentStructF",
  1537. x: createStructF(0),
  1538. y: createStructF(0),
  1539. opts: []cmp.Option{
  1540. cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
  1541. },
  1542. wantPanic: "cannot handle unexported field",
  1543. }, {
  1544. label: label + "ParentStructF",
  1545. x: createStructF(0),
  1546. y: createStructF(0),
  1547. opts: []cmp.Option{
  1548. cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
  1549. },
  1550. }, {
  1551. label: label + "ParentStructF",
  1552. x: createStructF(0),
  1553. y: createStructF(1),
  1554. opts: []cmp.Option{
  1555. cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
  1556. },
  1557. wantDiff: `
  1558. teststructs.ParentStructF{
  1559. privateStruct: teststructs.privateStruct{
  1560. - Public: 1,
  1561. + Public: 2,
  1562. - private: 2,
  1563. + private: 3,
  1564. },
  1565. PublicStruct: teststructs.PublicStruct{
  1566. - Public: 3,
  1567. + Public: 4,
  1568. - private: 4,
  1569. + private: 5,
  1570. },
  1571. - Public: 5,
  1572. + Public: 6,
  1573. - private: 6,
  1574. + private: 7,
  1575. }
  1576. `,
  1577. }, {
  1578. label: label + "ParentStructG",
  1579. x: ts.ParentStructG{},
  1580. y: ts.ParentStructG{},
  1581. wantPanic: wantPanicNotGo110("cannot handle unexported field"),
  1582. }, {
  1583. label: label + "ParentStructG",
  1584. x: ts.ParentStructG{},
  1585. y: ts.ParentStructG{},
  1586. opts: []cmp.Option{
  1587. cmpopts.IgnoreUnexported(ts.ParentStructG{}),
  1588. },
  1589. }, {
  1590. label: label + "ParentStructG",
  1591. x: createStructG(0),
  1592. y: createStructG(0),
  1593. opts: []cmp.Option{
  1594. cmp.AllowUnexported(ts.ParentStructG{}),
  1595. },
  1596. wantPanic: "cannot handle unexported field",
  1597. }, {
  1598. label: label + "ParentStructG",
  1599. x: createStructG(0),
  1600. y: createStructG(0),
  1601. opts: []cmp.Option{
  1602. cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
  1603. },
  1604. }, {
  1605. label: label + "ParentStructG",
  1606. x: createStructG(0),
  1607. y: createStructG(1),
  1608. opts: []cmp.Option{
  1609. cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
  1610. },
  1611. wantDiff: `
  1612. &teststructs.ParentStructG{
  1613. privateStruct: &teststructs.privateStruct{
  1614. - Public: 1,
  1615. + Public: 2,
  1616. - private: 2,
  1617. + private: 3,
  1618. },
  1619. }
  1620. `,
  1621. }, {
  1622. label: label + "ParentStructH",
  1623. x: ts.ParentStructH{},
  1624. y: ts.ParentStructH{},
  1625. }, {
  1626. label: label + "ParentStructH",
  1627. x: createStructH(0),
  1628. y: createStructH(0),
  1629. wantPanic: "cannot handle unexported field",
  1630. }, {
  1631. label: label + "ParentStructH",
  1632. x: ts.ParentStructH{},
  1633. y: ts.ParentStructH{},
  1634. opts: []cmp.Option{
  1635. cmpopts.IgnoreUnexported(ts.ParentStructH{}),
  1636. },
  1637. }, {
  1638. label: label + "ParentStructH",
  1639. x: createStructH(0),
  1640. y: createStructH(0),
  1641. opts: []cmp.Option{
  1642. cmp.AllowUnexported(ts.ParentStructH{}),
  1643. },
  1644. wantPanic: "cannot handle unexported field",
  1645. }, {
  1646. label: label + "ParentStructH",
  1647. x: createStructH(0),
  1648. y: createStructH(0),
  1649. opts: []cmp.Option{
  1650. cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
  1651. },
  1652. }, {
  1653. label: label + "ParentStructH",
  1654. x: createStructH(0),
  1655. y: createStructH(1),
  1656. opts: []cmp.Option{
  1657. cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
  1658. },
  1659. wantDiff: `
  1660. &teststructs.ParentStructH{
  1661. PublicStruct: &teststructs.PublicStruct{
  1662. - Public: 1,
  1663. + Public: 2,
  1664. - private: 2,
  1665. + private: 3,
  1666. },
  1667. }
  1668. `,
  1669. }, {
  1670. label: label + "ParentStructI",
  1671. x: ts.ParentStructI{},
  1672. y: ts.ParentStructI{},
  1673. wantPanic: wantPanicNotGo110("cannot handle unexported field"),
  1674. }, {
  1675. label: label + "ParentStructI",
  1676. x: ts.ParentStructI{},
  1677. y: ts.ParentStructI{},
  1678. opts: []cmp.Option{
  1679. cmpopts.IgnoreUnexported(ts.ParentStructI{}),
  1680. },
  1681. }, {
  1682. label: label + "ParentStructI",
  1683. x: createStructI(0),
  1684. y: createStructI(0),
  1685. opts: []cmp.Option{
  1686. cmpopts.IgnoreUnexported(ts.ParentStructI{}),
  1687. },
  1688. wantPanic: "cannot handle unexported field",
  1689. }, {
  1690. label: label + "ParentStructI",
  1691. x: createStructI(0),
  1692. y: createStructI(0),
  1693. opts: []cmp.Option{
  1694. cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
  1695. },
  1696. }, {
  1697. label: label + "ParentStructI",
  1698. x: createStructI(0),
  1699. y: createStructI(0),
  1700. opts: []cmp.Option{
  1701. cmp.AllowUnexported(ts.ParentStructI{}),
  1702. },
  1703. wantPanic: "cannot handle unexported field",
  1704. }, {
  1705. label: label + "ParentStructI",
  1706. x: createStructI(0),
  1707. y: createStructI(0),
  1708. opts: []cmp.Option{
  1709. cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
  1710. },
  1711. }, {
  1712. label: label + "ParentStructI",
  1713. x: createStructI(0),
  1714. y: createStructI(1),
  1715. opts: []cmp.Option{
  1716. cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
  1717. },
  1718. wantDiff: `
  1719. &teststructs.ParentStructI{
  1720. privateStruct: &teststructs.privateStruct{
  1721. - Public: 1,
  1722. + Public: 2,
  1723. - private: 2,
  1724. + private: 3,
  1725. },
  1726. PublicStruct: &teststructs.PublicStruct{
  1727. - Public: 3,
  1728. + Public: 4,
  1729. - private: 4,
  1730. + private: 5,
  1731. },
  1732. }
  1733. `,
  1734. }, {
  1735. label: label + "ParentStructJ",
  1736. x: ts.ParentStructJ{},
  1737. y: ts.ParentStructJ{},
  1738. wantPanic: "cannot handle unexported field",
  1739. }, {
  1740. label: label + "ParentStructJ",
  1741. x: ts.ParentStructJ{},
  1742. y: ts.ParentStructJ{},
  1743. opts: []cmp.Option{
  1744. cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
  1745. },
  1746. wantPanic: "cannot handle unexported field",
  1747. }, {
  1748. label: label + "ParentStructJ",
  1749. x: ts.ParentStructJ{},
  1750. y: ts.ParentStructJ{},
  1751. opts: []cmp.Option{
  1752. cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
  1753. },
  1754. }, {
  1755. label: label + "ParentStructJ",
  1756. x: createStructJ(0),
  1757. y: createStructJ(0),
  1758. opts: []cmp.Option{
  1759. cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
  1760. },
  1761. wantPanic: "cannot handle unexported field",
  1762. }, {
  1763. label: label + "ParentStructJ",
  1764. x: createStructJ(0),
  1765. y: createStructJ(0),
  1766. opts: []cmp.Option{
  1767. cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
  1768. },
  1769. }, {
  1770. label: label + "ParentStructJ",
  1771. x: createStructJ(0),
  1772. y: createStructJ(1),
  1773. opts: []cmp.Option{
  1774. cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
  1775. },
  1776. wantDiff: `
  1777. &teststructs.ParentStructJ{
  1778. privateStruct: &teststructs.privateStruct{
  1779. - Public: 1,
  1780. + Public: 2,
  1781. - private: 2,
  1782. + private: 3,
  1783. },
  1784. PublicStruct: &teststructs.PublicStruct{
  1785. - Public: 3,
  1786. + Public: 4,
  1787. - private: 4,
  1788. + private: 5,
  1789. },
  1790. Public: teststructs.PublicStruct{
  1791. - Public: 7,
  1792. + Public: 8,
  1793. - private: 8,
  1794. + private: 9,
  1795. },
  1796. private: teststructs.privateStruct{
  1797. - Public: 5,
  1798. + Public: 6,
  1799. - private: 6,
  1800. + private: 7,
  1801. },
  1802. }
  1803. `,
  1804. }}
  1805. }
  1806. func methodTests() []test {
  1807. const label = "EqualMethod/"
  1808. // A common mistake that the Equal method is on a pointer receiver,
  1809. // but only a non-pointer value is present in the struct.
  1810. // A transform can be used to forcibly reference the value.
  1811. derefTransform := cmp.FilterPath(func(p cmp.Path) bool {
  1812. if len(p) == 0 {
  1813. return false
  1814. }
  1815. t := p[len(p)-1].Type()
  1816. if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
  1817. return false
  1818. }
  1819. if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
  1820. tf := m.Func.Type()
  1821. return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
  1822. tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
  1823. }
  1824. return false
  1825. }, cmp.Transformer("Ref", func(x interface{}) interface{} {
  1826. v := reflect.ValueOf(x)
  1827. vp := reflect.New(v.Type())
  1828. vp.Elem().Set(v)
  1829. return vp.Interface()
  1830. }))
  1831. // For each of these types, there is an Equal method defined, which always
  1832. // returns true, while the underlying data are fundamentally different.
  1833. // Since the method should be called, these are expected to be equal.
  1834. return []test{{
  1835. label: label + "StructA",
  1836. x: ts.StructA{X: "NotEqual"},
  1837. y: ts.StructA{X: "not_equal"},
  1838. }, {
  1839. label: label + "StructA",
  1840. x: &ts.StructA{X: "NotEqual"},
  1841. y: &ts.StructA{X: "not_equal"},
  1842. }, {
  1843. label: label + "StructB",
  1844. x: ts.StructB{X: "NotEqual"},
  1845. y: ts.StructB{X: "not_equal"},
  1846. wantDiff: `
  1847. teststructs.StructB{
  1848. - X: "NotEqual",
  1849. + X: "not_equal",
  1850. }
  1851. `,
  1852. }, {
  1853. label: label + "StructB",
  1854. x: ts.StructB{X: "NotEqual"},
  1855. y: ts.StructB{X: "not_equal"},
  1856. opts: []cmp.Option{derefTransform},
  1857. }, {
  1858. label: label + "StructB",
  1859. x: &ts.StructB{X: "NotEqual"},
  1860. y: &ts.StructB{X: "not_equal"},
  1861. }, {
  1862. label: label + "StructC",
  1863. x: ts.StructC{X: "NotEqual"},
  1864. y: ts.StructC{X: "not_equal"},
  1865. }, {
  1866. label: label + "StructC",
  1867. x: &ts.StructC{X: "NotEqual"},
  1868. y: &ts.StructC{X: "not_equal"},
  1869. }, {
  1870. label: label + "StructD",
  1871. x: ts.StructD{X: "NotEqual"},
  1872. y: ts.StructD{X: "not_equal"},
  1873. wantDiff: `
  1874. teststructs.StructD{
  1875. - X: "NotEqual",
  1876. + X: "not_equal",
  1877. }
  1878. `,
  1879. }, {
  1880. label: label + "StructD",
  1881. x: ts.StructD{X: "NotEqual"},
  1882. y: ts.StructD{X: "not_equal"},
  1883. opts: []cmp.Option{derefTransform},
  1884. }, {
  1885. label: label + "StructD",
  1886. x: &ts.StructD{X: "NotEqual"},
  1887. y: &ts.StructD{X: "not_equal"},
  1888. }, {
  1889. label: label + "StructE",
  1890. x: ts.StructE{X: "NotEqual"},
  1891. y: ts.StructE{X: "not_equal"},
  1892. wantDiff: `
  1893. teststructs.StructE{
  1894. - X: "NotEqual",
  1895. + X: "not_equal",
  1896. }
  1897. `,
  1898. }, {
  1899. label: label + "StructE",
  1900. x: ts.StructE{X: "NotEqual"},
  1901. y: ts.StructE{X: "not_equal"},
  1902. opts: []cmp.Option{derefTransform},
  1903. }, {
  1904. label: label + "StructE",
  1905. x: &ts.StructE{X: "NotEqual"},
  1906. y: &ts.StructE{X: "not_equal"},
  1907. }, {
  1908. label: label + "StructF",
  1909. x: ts.StructF{X: "NotEqual"},
  1910. y: ts.StructF{X: "not_equal"},
  1911. wantDiff: `
  1912. teststructs.StructF{
  1913. - X: "NotEqual",
  1914. + X: "not_equal",
  1915. }
  1916. `,
  1917. }, {
  1918. label: label + "StructF",
  1919. x: &ts.StructF{X: "NotEqual"},
  1920. y: &ts.StructF{X: "not_equal"},
  1921. }, {
  1922. label: label + "StructA1",
  1923. x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
  1924. y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
  1925. }, {
  1926. label: label + "StructA1",
  1927. x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  1928. y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
  1929. wantDiff: `
  1930. teststructs.StructA1{
  1931. StructA: teststructs.StructA{X: "NotEqual"},
  1932. - X: "NotEqual",
  1933. + X: "not_equal",
  1934. }
  1935. `,
  1936. }, {
  1937. label: label + "StructA1",
  1938. x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
  1939. y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
  1940. }, {
  1941. label: label + "StructA1",
  1942. x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  1943. y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
  1944. wantDiff: `
  1945. &teststructs.StructA1{
  1946. StructA: teststructs.StructA{X: "NotEqual"},
  1947. - X: "NotEqual",
  1948. + X: "not_equal",
  1949. }
  1950. `,
  1951. }, {
  1952. label: label + "StructB1",
  1953. x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
  1954. y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
  1955. opts: []cmp.Option{derefTransform},
  1956. }, {
  1957. label: label + "StructB1",
  1958. x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  1959. y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
  1960. opts: []cmp.Option{derefTransform},
  1961. wantDiff: `
  1962. teststructs.StructB1{
  1963. StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
  1964. - X: "NotEqual",
  1965. + X: "not_equal",
  1966. }
  1967. `,
  1968. }, {
  1969. label: label + "StructB1",
  1970. x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
  1971. y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
  1972. opts: []cmp.Option{derefTransform},
  1973. }, {
  1974. label: label + "StructB1",
  1975. x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  1976. y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
  1977. opts: []cmp.Option{derefTransform},
  1978. wantDiff: `
  1979. &teststructs.StructB1{
  1980. StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
  1981. - X: "NotEqual",
  1982. + X: "not_equal",
  1983. }
  1984. `,
  1985. }, {
  1986. label: label + "StructC1",
  1987. x: ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  1988. y: ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
  1989. }, {
  1990. label: label + "StructC1",
  1991. x: &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  1992. y: &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
  1993. }, {
  1994. label: label + "StructD1",
  1995. x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  1996. y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  1997. wantDiff: `
  1998. teststructs.StructD1{
  1999. - StructD: teststructs.StructD{X: "NotEqual"},
  2000. + StructD: teststructs.StructD{X: "not_equal"},
  2001. - X: "NotEqual",
  2002. + X: "not_equal",
  2003. }
  2004. `,
  2005. }, {
  2006. label: label + "StructD1",
  2007. x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2008. y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  2009. opts: []cmp.Option{derefTransform},
  2010. }, {
  2011. label: label + "StructD1",
  2012. x: &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2013. y: &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  2014. }, {
  2015. label: label + "StructE1",
  2016. x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2017. y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  2018. wantDiff: `
  2019. teststructs.StructE1{
  2020. - StructE: teststructs.StructE{X: "NotEqual"},
  2021. + StructE: teststructs.StructE{X: "not_equal"},
  2022. - X: "NotEqual",
  2023. + X: "not_equal",
  2024. }
  2025. `,
  2026. }, {
  2027. label: label + "StructE1",
  2028. x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2029. y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  2030. opts: []cmp.Option{derefTransform},
  2031. }, {
  2032. label: label + "StructE1",
  2033. x: &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2034. y: &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  2035. }, {
  2036. label: label + "StructF1",
  2037. x: ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2038. y: ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
  2039. wantDiff: `
  2040. teststructs.StructF1{
  2041. - StructF: teststructs.StructF{X: "NotEqual"},
  2042. + StructF: teststructs.StructF{X: "not_equal"},
  2043. - X: "NotEqual",
  2044. + X: "not_equal",
  2045. }
  2046. `,
  2047. }, {
  2048. label: label + "StructF1",
  2049. x: &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2050. y: &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
  2051. }, {
  2052. label: label + "StructA2",
  2053. x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
  2054. y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
  2055. }, {
  2056. label: label + "StructA2",
  2057. x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  2058. y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
  2059. wantDiff: `
  2060. teststructs.StructA2{
  2061. StructA: &teststructs.StructA{X: "NotEqual"},
  2062. - X: "NotEqual",
  2063. + X: "not_equal",
  2064. }
  2065. `,
  2066. }, {
  2067. label: label + "StructA2",
  2068. x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
  2069. y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
  2070. }, {
  2071. label: label + "StructA2",
  2072. x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  2073. y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
  2074. wantDiff: `
  2075. &teststructs.StructA2{
  2076. StructA: &teststructs.StructA{X: "NotEqual"},
  2077. - X: "NotEqual",
  2078. + X: "not_equal",
  2079. }
  2080. `,
  2081. }, {
  2082. label: label + "StructB2",
  2083. x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
  2084. y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
  2085. }, {
  2086. label: label + "StructB2",
  2087. x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  2088. y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
  2089. wantDiff: `
  2090. teststructs.StructB2{
  2091. StructB: &teststructs.StructB{X: "NotEqual"},
  2092. - X: "NotEqual",
  2093. + X: "not_equal",
  2094. }
  2095. `,
  2096. }, {
  2097. label: label + "StructB2",
  2098. x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
  2099. y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
  2100. }, {
  2101. label: label + "StructB2",
  2102. x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  2103. y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
  2104. wantDiff: `
  2105. &teststructs.StructB2{
  2106. StructB: &teststructs.StructB{X: "NotEqual"},
  2107. - X: "NotEqual",
  2108. + X: "not_equal",
  2109. }
  2110. `,
  2111. }, {
  2112. label: label + "StructC2",
  2113. x: ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  2114. y: ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
  2115. }, {
  2116. label: label + "StructC2",
  2117. x: &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  2118. y: &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
  2119. }, {
  2120. label: label + "StructD2",
  2121. x: ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2122. y: ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
  2123. }, {
  2124. label: label + "StructD2",
  2125. x: &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2126. y: &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
  2127. }, {
  2128. label: label + "StructE2",
  2129. x: ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2130. y: ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
  2131. }, {
  2132. label: label + "StructE2",
  2133. x: &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2134. y: &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
  2135. }, {
  2136. label: label + "StructF2",
  2137. x: ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2138. y: ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
  2139. }, {
  2140. label: label + "StructF2",
  2141. x: &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2142. y: &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
  2143. }, {
  2144. label: label + "StructNo",
  2145. x: ts.StructNo{X: "NotEqual"},
  2146. y: ts.StructNo{X: "not_equal"},
  2147. wantDiff: `
  2148. teststructs.StructNo{
  2149. - X: "NotEqual",
  2150. + X: "not_equal",
  2151. }
  2152. `,
  2153. }, {
  2154. label: label + "AssignA",
  2155. x: ts.AssignA(func() int { return 0 }),
  2156. y: ts.AssignA(func() int { return 1 }),
  2157. }, {
  2158. label: label + "AssignB",
  2159. x: ts.AssignB(struct{ A int }{0}),
  2160. y: ts.AssignB(struct{ A int }{1}),
  2161. }, {
  2162. label: label + "AssignC",
  2163. x: ts.AssignC(make(chan bool)),
  2164. y: ts.AssignC(make(chan bool)),
  2165. }, {
  2166. label: label + "AssignD",
  2167. x: ts.AssignD(make(chan bool)),
  2168. y: ts.AssignD(make(chan bool)),
  2169. }}
  2170. }
  2171. func project1Tests() []test {
  2172. const label = "Project1"
  2173. ignoreUnexported := cmpopts.IgnoreUnexported(
  2174. ts.EagleImmutable{},
  2175. ts.DreamerImmutable{},
  2176. ts.SlapImmutable{},
  2177. ts.GoatImmutable{},
  2178. ts.DonkeyImmutable{},
  2179. ts.LoveRadius{},
  2180. ts.SummerLove{},
  2181. ts.SummerLoveSummary{},
  2182. )
  2183. createEagle := func() ts.Eagle {
  2184. return ts.Eagle{
  2185. Name: "eagle",
  2186. Hounds: []string{"buford", "tannen"},
  2187. Desc: "some description",
  2188. Dreamers: []ts.Dreamer{{}, {
  2189. Name: "dreamer2",
  2190. Animal: []interface{}{
  2191. ts.Goat{
  2192. Target: "corporation",
  2193. Immutable: &ts.GoatImmutable{
  2194. ID: "southbay",
  2195. State: (*pb.Goat_States)(intPtr(5)),
  2196. Started: now,
  2197. },
  2198. },
  2199. ts.Donkey{},
  2200. },
  2201. Amoeba: 53,
  2202. }},
  2203. Slaps: []ts.Slap{{
  2204. Name: "slapID",
  2205. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2206. Immutable: &ts.SlapImmutable{
  2207. ID: "immutableSlap",
  2208. MildSlap: true,
  2209. Started: now,
  2210. LoveRadius: &ts.LoveRadius{
  2211. Summer: &ts.SummerLove{
  2212. Summary: &ts.SummerLoveSummary{
  2213. Devices: []string{"foo", "bar", "baz"},
  2214. ChangeType: []pb.SummerType{1, 2, 3},
  2215. },
  2216. },
  2217. },
  2218. },
  2219. }},
  2220. Immutable: &ts.EagleImmutable{
  2221. ID: "eagleID",
  2222. Birthday: now,
  2223. MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)),
  2224. },
  2225. }
  2226. }
  2227. return []test{{
  2228. label: label,
  2229. x: ts.Eagle{Slaps: []ts.Slap{{
  2230. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2231. }}},
  2232. y: ts.Eagle{Slaps: []ts.Slap{{
  2233. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2234. }}},
  2235. wantPanic: "cannot handle unexported field",
  2236. }, {
  2237. label: label,
  2238. x: ts.Eagle{Slaps: []ts.Slap{{
  2239. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2240. }}},
  2241. y: ts.Eagle{Slaps: []ts.Slap{{
  2242. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2243. }}},
  2244. opts: []cmp.Option{cmp.Comparer(pb.Equal)},
  2245. }, {
  2246. label: label,
  2247. x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
  2248. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2249. }}},
  2250. y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
  2251. Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
  2252. }}},
  2253. opts: []cmp.Option{cmp.Comparer(pb.Equal)},
  2254. wantDiff: `
  2255. teststructs.Eagle{
  2256. ... // 4 identical fields
  2257. Dreamers: nil,
  2258. Prong: 0,
  2259. Slaps: []teststructs.Slap{
  2260. ... // 2 identical elements
  2261. {},
  2262. {},
  2263. {
  2264. Name: "",
  2265. Desc: "",
  2266. DescLong: "",
  2267. - Args: s"metadata",
  2268. + Args: s"metadata2",
  2269. Tense: 0,
  2270. Interval: 0,
  2271. ... // 3 identical fields
  2272. },
  2273. },
  2274. StateGoverner: "",
  2275. PrankRating: "",
  2276. ... // 2 identical fields
  2277. }
  2278. `,
  2279. }, {
  2280. label: label,
  2281. x: createEagle(),
  2282. y: createEagle(),
  2283. opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
  2284. }, {
  2285. label: label,
  2286. x: func() ts.Eagle {
  2287. eg := createEagle()
  2288. eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
  2289. eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6))
  2290. eg.Slaps[0].Immutable.MildSlap = false
  2291. return eg
  2292. }(),
  2293. y: func() ts.Eagle {
  2294. eg := createEagle()
  2295. devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
  2296. eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
  2297. return eg
  2298. }(),
  2299. opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
  2300. wantDiff: `
  2301. teststructs.Eagle{
  2302. ... // 2 identical fields
  2303. Desc: "some description",
  2304. DescLong: "",
  2305. Dreamers: []teststructs.Dreamer{
  2306. {},
  2307. {
  2308. ... // 4 identical fields
  2309. ContSlaps: nil,
  2310. ContSlapsInterval: 0,
  2311. Animal: []interface{}{
  2312. teststructs.Goat{
  2313. Target: "corporation",
  2314. Slaps: nil,
  2315. FunnyPrank: "",
  2316. Immutable: &teststructs.GoatImmutable{
  2317. - ID: "southbay2",
  2318. + ID: "southbay",
  2319. - State: &6,
  2320. + State: &5,
  2321. Started: s"2009-11-10 23:00:00 +0000 UTC",
  2322. Stopped: s"0001-01-01 00:00:00 +0000 UTC",
  2323. ... // 1 ignored and 1 identical fields
  2324. },
  2325. },
  2326. teststructs.Donkey{},
  2327. },
  2328. Ornamental: false,
  2329. Amoeba: 53,
  2330. ... // 5 identical fields
  2331. },
  2332. },
  2333. Prong: 0,
  2334. Slaps: []teststructs.Slap{
  2335. {
  2336. ... // 6 identical fields
  2337. Homeland: 0x00,
  2338. FunnyPrank: "",
  2339. Immutable: &teststructs.SlapImmutable{
  2340. ID: "immutableSlap",
  2341. Out: nil,
  2342. - MildSlap: false,
  2343. + MildSlap: true,
  2344. PrettyPrint: "",
  2345. State: nil,
  2346. Started: s"2009-11-10 23:00:00 +0000 UTC",
  2347. Stopped: s"0001-01-01 00:00:00 +0000 UTC",
  2348. LastUpdate: s"0001-01-01 00:00:00 +0000 UTC",
  2349. LoveRadius: &teststructs.LoveRadius{
  2350. Summer: &teststructs.SummerLove{
  2351. Summary: &teststructs.SummerLoveSummary{
  2352. Devices: []string{
  2353. "foo",
  2354. - "bar",
  2355. - "baz",
  2356. },
  2357. ChangeType: []testprotos.SummerType{1, 2, 3},
  2358. ... // 1 ignored field
  2359. },
  2360. ... // 1 ignored field
  2361. },
  2362. ... // 1 ignored field
  2363. },
  2364. ... // 1 ignored field
  2365. },
  2366. },
  2367. },
  2368. StateGoverner: "",
  2369. PrankRating: "",
  2370. ... // 2 identical fields
  2371. }
  2372. `,
  2373. }}
  2374. }
  2375. type germSorter []*pb.Germ
  2376. func (gs germSorter) Len() int { return len(gs) }
  2377. func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
  2378. func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] }
  2379. func project2Tests() []test {
  2380. const label = "Project2"
  2381. sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
  2382. out := append([]*pb.Germ(nil), in...) // Make copy
  2383. sort.Sort(germSorter(out))
  2384. return out
  2385. })
  2386. equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
  2387. if x == nil || y == nil {
  2388. return x == nil && y == nil
  2389. }
  2390. px, err1 := x.Proto()
  2391. py, err2 := y.Proto()
  2392. if err1 != nil || err2 != nil {
  2393. return err1 == err2
  2394. }
  2395. return pb.Equal(px, py)
  2396. })
  2397. createBatch := func() ts.GermBatch {
  2398. return ts.GermBatch{
  2399. DirtyGerms: map[int32][]*pb.Germ{
  2400. 17: {
  2401. {Stringer: pb.Stringer{X: "germ1"}},
  2402. },
  2403. 18: {
  2404. {Stringer: pb.Stringer{X: "germ2"}},
  2405. {Stringer: pb.Stringer{X: "germ3"}},
  2406. {Stringer: pb.Stringer{X: "germ4"}},
  2407. },
  2408. },
  2409. GermMap: map[int32]*pb.Germ{
  2410. 13: {Stringer: pb.Stringer{X: "germ13"}},
  2411. 21: {Stringer: pb.Stringer{X: "germ21"}},
  2412. },
  2413. DishMap: map[int32]*ts.Dish{
  2414. 0: ts.CreateDish(nil, io.EOF),
  2415. 1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
  2416. 2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
  2417. },
  2418. HasPreviousResult: true,
  2419. DirtyID: 10,
  2420. GermStrain: 421,
  2421. InfectedAt: now,
  2422. }
  2423. }
  2424. return []test{{
  2425. label: label,
  2426. x: createBatch(),
  2427. y: createBatch(),
  2428. wantPanic: "cannot handle unexported field",
  2429. }, {
  2430. label: label,
  2431. x: createBatch(),
  2432. y: createBatch(),
  2433. opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  2434. }, {
  2435. label: label,
  2436. x: createBatch(),
  2437. y: func() ts.GermBatch {
  2438. gb := createBatch()
  2439. s := gb.DirtyGerms[18]
  2440. s[0], s[1], s[2] = s[1], s[2], s[0]
  2441. return gb
  2442. }(),
  2443. opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
  2444. wantDiff: `
  2445. teststructs.GermBatch{
  2446. DirtyGerms: map[int32][]*testprotos.Germ{
  2447. 17: {s"germ1"},
  2448. 18: {
  2449. - s"germ2",
  2450. s"germ3",
  2451. s"germ4",
  2452. + s"germ2",
  2453. },
  2454. },
  2455. CleanGerms: nil,
  2456. GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
  2457. ... // 7 identical fields
  2458. }
  2459. `,
  2460. }, {
  2461. label: label,
  2462. x: createBatch(),
  2463. y: func() ts.GermBatch {
  2464. gb := createBatch()
  2465. s := gb.DirtyGerms[18]
  2466. s[0], s[1], s[2] = s[1], s[2], s[0]
  2467. return gb
  2468. }(),
  2469. opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  2470. }, {
  2471. label: label,
  2472. x: func() ts.GermBatch {
  2473. gb := createBatch()
  2474. delete(gb.DirtyGerms, 17)
  2475. gb.DishMap[1] = nil
  2476. return gb
  2477. }(),
  2478. y: func() ts.GermBatch {
  2479. gb := createBatch()
  2480. gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
  2481. gb.GermStrain = 22
  2482. return gb
  2483. }(),
  2484. opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  2485. wantDiff: `
  2486. teststructs.GermBatch{
  2487. DirtyGerms: map[int32][]*testprotos.Germ{
  2488. + 17: {s"germ1"},
  2489. 18: Inverse(Sort, []*testprotos.Germ{
  2490. s"germ2",
  2491. s"germ3",
  2492. - s"germ4",
  2493. }),
  2494. },
  2495. CleanGerms: nil,
  2496. GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
  2497. DishMap: map[int32]*teststructs.Dish{
  2498. 0: &{err: &errors.errorString{s: "EOF"}},
  2499. - 1: nil,
  2500. + 1: &{err: &errors.errorString{s: "unexpected EOF"}},
  2501. 2: &{pb: &testprotos.Dish{Stringer: testprotos.Stringer{X: "dish"}}},
  2502. },
  2503. HasPreviousResult: true,
  2504. DirtyID: 10,
  2505. CleanID: 0,
  2506. - GermStrain: 421,
  2507. + GermStrain: 22,
  2508. TotalDirtyGerms: 0,
  2509. InfectedAt: s"2009-11-10 23:00:00 +0000 UTC",
  2510. }
  2511. `,
  2512. }}
  2513. }
  2514. func project3Tests() []test {
  2515. const label = "Project3"
  2516. allowVisibility := cmp.AllowUnexported(ts.Dirt{})
  2517. ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
  2518. transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt {
  2519. return &x
  2520. })
  2521. equalTable := cmp.Comparer(func(x, y ts.Table) bool {
  2522. tx, ok1 := x.(*ts.MockTable)
  2523. ty, ok2 := y.(*ts.MockTable)
  2524. if !ok1 || !ok2 {
  2525. panic("table type must be MockTable")
  2526. }
  2527. return cmp.Equal(tx.State(), ty.State())
  2528. })
  2529. createDirt := func() (d ts.Dirt) {
  2530. d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
  2531. d.SetTimestamp(12345)
  2532. d.Discord = 554
  2533. d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
  2534. d.SetWizard(map[string]*pb.Wizard{
  2535. "harry": {Stringer: pb.Stringer{X: "potter"}},
  2536. "albus": {Stringer: pb.Stringer{X: "dumbledore"}},
  2537. })
  2538. d.SetLastTime(54321)
  2539. return d
  2540. }
  2541. return []test{{
  2542. label: label,
  2543. x: createDirt(),
  2544. y: createDirt(),
  2545. wantPanic: "cannot handle unexported field",
  2546. }, {
  2547. label: label,
  2548. x: createDirt(),
  2549. y: createDirt(),
  2550. opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  2551. wantPanic: "cannot handle unexported field",
  2552. }, {
  2553. label: label,
  2554. x: createDirt(),
  2555. y: createDirt(),
  2556. opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  2557. }, {
  2558. label: label,
  2559. x: func() ts.Dirt {
  2560. d := createDirt()
  2561. d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
  2562. d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
  2563. return d
  2564. }(),
  2565. y: func() ts.Dirt {
  2566. d := createDirt()
  2567. d.Discord = 500
  2568. d.SetWizard(map[string]*pb.Wizard{
  2569. "harry": {Stringer: pb.Stringer{X: "otter"}},
  2570. })
  2571. return d
  2572. }(),
  2573. opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  2574. wantDiff: `
  2575. teststructs.Dirt{
  2576. - table: &teststructs.MockTable{state: []string{"a", "c"}},
  2577. + table: &teststructs.MockTable{state: []string{"a", "b", "c"}},
  2578. ts: 12345,
  2579. - Discord: 554,
  2580. + Discord: 500,
  2581. - Proto: testprotos.Dirt(Inverse(λ, s"blah")),
  2582. + Proto: testprotos.Dirt(Inverse(λ, s"proto")),
  2583. wizard: map[string]*testprotos.Wizard{
  2584. - "albus": s"dumbledore",
  2585. - "harry": s"potter",
  2586. + "harry": s"otter",
  2587. },
  2588. sadistic: nil,
  2589. lastTime: 54321,
  2590. ... // 1 ignored field
  2591. }
  2592. `,
  2593. }}
  2594. }
  2595. func project4Tests() []test {
  2596. const label = "Project4"
  2597. allowVisibility := cmp.AllowUnexported(
  2598. ts.Cartel{},
  2599. ts.Headquarter{},
  2600. ts.Poison{},
  2601. )
  2602. transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions {
  2603. return &x
  2604. })
  2605. createCartel := func() ts.Cartel {
  2606. var p ts.Poison
  2607. p.SetPoisonType(5)
  2608. p.SetExpiration(now)
  2609. p.SetManufacturer("acme")
  2610. var hq ts.Headquarter
  2611. hq.SetID(5)
  2612. hq.SetLocation("moon")
  2613. hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
  2614. hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
  2615. hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
  2616. hq.SetHorseBack("abcdef")
  2617. hq.SetStatus(44)
  2618. var c ts.Cartel
  2619. c.Headquarter = hq
  2620. c.SetSource("mars")
  2621. c.SetCreationTime(now)
  2622. c.SetBoss("al capone")
  2623. c.SetPoisons([]*ts.Poison{&p})
  2624. return c
  2625. }
  2626. return []test{{
  2627. label: label,
  2628. x: createCartel(),
  2629. y: createCartel(),
  2630. wantPanic: "cannot handle unexported field",
  2631. }, {
  2632. label: label,
  2633. x: createCartel(),
  2634. y: createCartel(),
  2635. opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
  2636. wantPanic: "cannot handle unexported field",
  2637. }, {
  2638. label: label,
  2639. x: createCartel(),
  2640. y: createCartel(),
  2641. opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
  2642. }, {
  2643. label: label,
  2644. x: func() ts.Cartel {
  2645. d := createCartel()
  2646. var p1, p2 ts.Poison
  2647. p1.SetPoisonType(1)
  2648. p1.SetExpiration(now)
  2649. p1.SetManufacturer("acme")
  2650. p2.SetPoisonType(2)
  2651. p2.SetManufacturer("acme2")
  2652. d.SetPoisons([]*ts.Poison{&p1, &p2})
  2653. return d
  2654. }(),
  2655. y: func() ts.Cartel {
  2656. d := createCartel()
  2657. d.SetSubDivisions([]string{"bravo", "charlie"})
  2658. d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
  2659. return d
  2660. }(),
  2661. opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
  2662. wantDiff: `
  2663. teststructs.Cartel{
  2664. Headquarter: teststructs.Headquarter{
  2665. id: 0x05,
  2666. location: "moon",
  2667. subDivisions: []string{
  2668. - "alpha",
  2669. "bravo",
  2670. "charlie",
  2671. },
  2672. incorporatedDate: s"0001-01-01 00:00:00 +0000 UTC",
  2673. metaData: s"metadata",
  2674. privateMessage: nil,
  2675. publicMessage: []uint8{
  2676. 0x01,
  2677. 0x02,
  2678. - 0x03,
  2679. + 0x04,
  2680. - 0x04,
  2681. + 0x03,
  2682. 0x05,
  2683. },
  2684. horseBack: "abcdef",
  2685. rattle: "",
  2686. ... // 5 identical fields
  2687. },
  2688. source: "mars",
  2689. creationDate: s"0001-01-01 00:00:00 +0000 UTC",
  2690. boss: "al capone",
  2691. lastCrimeDate: s"0001-01-01 00:00:00 +0000 UTC",
  2692. poisons: []*teststructs.Poison{
  2693. &{
  2694. - poisonType: 1,
  2695. + poisonType: 5,
  2696. expiration: s"2009-11-10 23:00:00 +0000 UTC",
  2697. manufacturer: "acme",
  2698. potency: 0,
  2699. },
  2700. - &{poisonType: 2, manufacturer: "acme2"},
  2701. },
  2702. }
  2703. `,
  2704. }}
  2705. }
  2706. // BenchmarkBytes benchmarks the performance of performing Equal or Diff on
  2707. // large slices of bytes.
  2708. func BenchmarkBytes(b *testing.B) {
  2709. // Create a list of PathFilters that never apply, but are evaluated.
  2710. const maxFilters = 5
  2711. var filters cmp.Options
  2712. errorIface := reflect.TypeOf((*error)(nil)).Elem()
  2713. for i := 0; i <= maxFilters; i++ {
  2714. filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool {
  2715. return p.Last().Type().AssignableTo(errorIface) // Never true
  2716. }, cmp.Ignore()))
  2717. }
  2718. type benchSize struct {
  2719. label string
  2720. size int64
  2721. }
  2722. for _, ts := range []benchSize{
  2723. {"4KiB", 1 << 12},
  2724. {"64KiB", 1 << 16},
  2725. {"1MiB", 1 << 20},
  2726. {"16MiB", 1 << 24},
  2727. } {
  2728. bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...)
  2729. by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...)
  2730. b.Run(ts.label, func(b *testing.B) {
  2731. // Iteratively add more filters that never apply, but are evaluated
  2732. // to measure the cost of simply evaluating each filter.
  2733. for i := 0; i <= maxFilters; i++ {
  2734. b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) {
  2735. b.ReportAllocs()
  2736. b.SetBytes(2 * ts.size)
  2737. for j := 0; j < b.N; j++ {
  2738. cmp.Equal(bx, by, filters[:i]...)
  2739. }
  2740. })
  2741. }
  2742. for i := 0; i <= maxFilters; i++ {
  2743. b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) {
  2744. b.ReportAllocs()
  2745. b.SetBytes(2 * ts.size)
  2746. for j := 0; j < b.N; j++ {
  2747. cmp.Diff(bx, by, filters[:i]...)
  2748. }
  2749. })
  2750. }
  2751. })
  2752. }
  2753. }