github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/cmp/example_test.go (about)

     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  
     5  package cmp_test
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/integration-system/go-cmp/cmp"
    15  )
    16  
    17  // TODO: Re-write these examples in terms of how you actually use the
    18  // fundamental options and filters and not in terms of what cool things you can
    19  // do with them since that overlaps with cmp/cmpopts.
    20  
    21  // Use Diff for printing out human-readable errors for test cases comparing
    22  // nested or structured data.
    23  func ExampleDiff_testing() {
    24  	// Code under test:
    25  	type ShipManifest struct {
    26  		Name     string
    27  		Crew     map[string]string
    28  		Androids int
    29  		Stolen   bool
    30  	}
    31  
    32  	// AddCrew tries to add the given crewmember to the manifest.
    33  	AddCrew := func(m *ShipManifest, name, title string) {
    34  		if m.Crew == nil {
    35  			m.Crew = make(map[string]string)
    36  		}
    37  		m.Crew[title] = name
    38  	}
    39  
    40  	// Test function:
    41  	tests := []struct {
    42  		desc        string
    43  		before      *ShipManifest
    44  		name, title string
    45  		after       *ShipManifest
    46  	}{
    47  		{
    48  			desc:   "add to empty",
    49  			before: &ShipManifest{},
    50  			name:   "Zaphod Beeblebrox",
    51  			title:  "Galactic President",
    52  			after: &ShipManifest{
    53  				Crew: map[string]string{
    54  					"Zaphod Beeblebrox": "Galactic President",
    55  				},
    56  			},
    57  		},
    58  		{
    59  			desc: "add another",
    60  			before: &ShipManifest{
    61  				Crew: map[string]string{
    62  					"Zaphod Beeblebrox": "Galactic President",
    63  				},
    64  			},
    65  			name:  "Trillian",
    66  			title: "Human",
    67  			after: &ShipManifest{
    68  				Crew: map[string]string{
    69  					"Zaphod Beeblebrox": "Galactic President",
    70  					"Trillian":          "Human",
    71  				},
    72  			},
    73  		},
    74  		{
    75  			desc: "overwrite",
    76  			before: &ShipManifest{
    77  				Crew: map[string]string{
    78  					"Zaphod Beeblebrox": "Galactic President",
    79  				},
    80  			},
    81  			name:  "Zaphod Beeblebrox",
    82  			title: "Just this guy, you know?",
    83  			after: &ShipManifest{
    84  				Crew: map[string]string{
    85  					"Zaphod Beeblebrox": "Just this guy, you know?",
    86  				},
    87  			},
    88  		},
    89  	}
    90  
    91  	var t fakeT
    92  	for _, test := range tests {
    93  		AddCrew(test.before, test.name, test.title)
    94  		if diff := cmp.Diff(test.before, test.after); diff != "" {
    95  			t.Errorf("%s: after AddCrew, manifest differs: (-want +got)\n%s", test.desc, diff)
    96  		}
    97  	}
    98  
    99  	// Output:
   100  	// add to empty: after AddCrew, manifest differs: (-want +got)
   101  	// {*cmp_test.ShipManifest}.Crew["Galactic President"]:
   102  	// 	-: "Zaphod Beeblebrox"
   103  	// 	+: <non-existent>
   104  	// {*cmp_test.ShipManifest}.Crew["Zaphod Beeblebrox"]:
   105  	// 	-: <non-existent>
   106  	// 	+: "Galactic President"
   107  	//
   108  	// add another: after AddCrew, manifest differs: (-want +got)
   109  	// {*cmp_test.ShipManifest}.Crew["Human"]:
   110  	// 	-: "Trillian"
   111  	// 	+: <non-existent>
   112  	// {*cmp_test.ShipManifest}.Crew["Trillian"]:
   113  	// 	-: <non-existent>
   114  	// 	+: "Human"
   115  	//
   116  	// overwrite: after AddCrew, manifest differs: (-want +got)
   117  	// {*cmp_test.ShipManifest}.Crew["Just this guy, you know?"]:
   118  	// 	-: "Zaphod Beeblebrox"
   119  	// 	+: <non-existent>
   120  	// {*cmp_test.ShipManifest}.Crew["Zaphod Beeblebrox"]:
   121  	// 	-: "Galactic President"
   122  	// 	+: "Just this guy, you know?"
   123  }
   124  
   125  // Approximate equality for floats can be handled by defining a custom
   126  // comparer on floats that determines two values to be equal if they are within
   127  // some range of each other.
   128  //
   129  // This example is for demonstrative purposes; use cmpopts.EquateApprox instead.
   130  func ExampleOption_approximateFloats() {
   131  	// This Comparer only operates on float64.
   132  	// To handle float32s, either define a similar function for that type
   133  	// or use a Transformer to convert float32s into float64s.
   134  	opt := cmp.Comparer(func(x, y float64) bool {
   135  		delta := math.Abs(x - y)
   136  		mean := math.Abs(x+y) / 2.0
   137  		return delta/mean < 0.00001
   138  	})
   139  
   140  	x := []float64{1.0, 1.1, 1.2, math.Pi}
   141  	y := []float64{1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
   142  	z := []float64{1.0, 1.1, 1.2, 3.1415}        // Diverges too far from Pi
   143  
   144  	fmt.Println(cmp.Equal(x, y, opt))
   145  	fmt.Println(cmp.Equal(y, z, opt))
   146  	fmt.Println(cmp.Equal(z, x, opt))
   147  
   148  	// Output:
   149  	// true
   150  	// false
   151  	// false
   152  }
   153  
   154  // Normal floating-point arithmetic defines == to be false when comparing
   155  // NaN with itself. In certain cases, this is not the desired property.
   156  //
   157  // This example is for demonstrative purposes; use cmpopts.EquateNaNs instead.
   158  func ExampleOption_equalNaNs() {
   159  	// This Comparer only operates on float64.
   160  	// To handle float32s, either define a similar function for that type
   161  	// or use a Transformer to convert float32s into float64s.
   162  	opt := cmp.Comparer(func(x, y float64) bool {
   163  		return (math.IsNaN(x) && math.IsNaN(y)) || x == y
   164  	})
   165  
   166  	x := []float64{1.0, math.NaN(), math.E, -0.0, +0.0}
   167  	y := []float64{1.0, math.NaN(), math.E, -0.0, +0.0}
   168  	z := []float64{1.0, math.NaN(), math.Pi, -0.0, +0.0} // Pi constant instead of E
   169  
   170  	fmt.Println(cmp.Equal(x, y, opt))
   171  	fmt.Println(cmp.Equal(y, z, opt))
   172  	fmt.Println(cmp.Equal(z, x, opt))
   173  
   174  	// Output:
   175  	// true
   176  	// false
   177  	// false
   178  }
   179  
   180  // To have floating-point comparisons combine both properties of NaN being
   181  // equal to itself and also approximate equality of values, filters are needed
   182  // to restrict the scope of the comparison so that they are composable.
   183  //
   184  // This example is for demonstrative purposes;
   185  // use cmpopts.EquateNaNs and cmpopts.EquateApprox instead.
   186  func ExampleOption_equalNaNsAndApproximateFloats() {
   187  	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
   188  
   189  	opts := cmp.Options{
   190  		// This option declares that a float64 comparison is equal only if
   191  		// both inputs are NaN.
   192  		cmp.FilterValues(func(x, y float64) bool {
   193  			return math.IsNaN(x) && math.IsNaN(y)
   194  		}, alwaysEqual),
   195  
   196  		// This option declares approximate equality on float64s only if
   197  		// both inputs are not NaN.
   198  		cmp.FilterValues(func(x, y float64) bool {
   199  			return !math.IsNaN(x) && !math.IsNaN(y)
   200  		}, cmp.Comparer(func(x, y float64) bool {
   201  			delta := math.Abs(x - y)
   202  			mean := math.Abs(x+y) / 2.0
   203  			return delta/mean < 0.00001
   204  		})),
   205  	}
   206  
   207  	x := []float64{math.NaN(), 1.0, 1.1, 1.2, math.Pi}
   208  	y := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
   209  	z := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.1415}        // Diverges too far from Pi
   210  
   211  	fmt.Println(cmp.Equal(x, y, opts))
   212  	fmt.Println(cmp.Equal(y, z, opts))
   213  	fmt.Println(cmp.Equal(z, x, opts))
   214  
   215  	// Output:
   216  	// true
   217  	// false
   218  	// false
   219  }
   220  
   221  // Sometimes, an empty map or slice is considered equal to an allocated one
   222  // of zero length.
   223  //
   224  // This example is for demonstrative purposes; use cmpopts.EquateEmpty instead.
   225  func ExampleOption_equalEmpty() {
   226  	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
   227  
   228  	// This option handles slices and maps of any type.
   229  	opt := cmp.FilterValues(func(x, y interface{}) bool {
   230  		vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
   231  		return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) &&
   232  			(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
   233  			(vx.Len() == 0 && vy.Len() == 0)
   234  	}, alwaysEqual)
   235  
   236  	type S struct {
   237  		A []int
   238  		B map[string]bool
   239  	}
   240  	x := S{nil, make(map[string]bool, 100)}
   241  	y := S{make([]int, 0, 200), nil}
   242  	z := S{[]int{0}, nil} // []int has a single element (i.e., not empty)
   243  
   244  	fmt.Println(cmp.Equal(x, y, opt))
   245  	fmt.Println(cmp.Equal(y, z, opt))
   246  	fmt.Println(cmp.Equal(z, x, opt))
   247  
   248  	// Output:
   249  	// true
   250  	// false
   251  	// false
   252  }
   253  
   254  // Two slices may be considered equal if they have the same elements,
   255  // regardless of the order that they appear in. Transformations can be used
   256  // to sort the slice.
   257  //
   258  // This example is for demonstrative purposes; use cmpopts.SortSlices instead.
   259  func ExampleOption_sortedSlice() {
   260  	// This Transformer sorts a []int.
   261  	trans := cmp.Transformer("Sort", func(in []int) []int {
   262  		out := append([]int(nil), in...) // Copy input to avoid mutating it
   263  		sort.Ints(out)
   264  		return out
   265  	})
   266  
   267  	x := struct{ Ints []int }{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
   268  	y := struct{ Ints []int }{[]int{2, 8, 0, 9, 6, 1, 4, 7, 3, 5}}
   269  	z := struct{ Ints []int }{[]int{0, 0, 1, 2, 3, 4, 5, 6, 7, 8}}
   270  
   271  	fmt.Println(cmp.Equal(x, y, trans))
   272  	fmt.Println(cmp.Equal(y, z, trans))
   273  	fmt.Println(cmp.Equal(z, x, trans))
   274  
   275  	// Output:
   276  	// true
   277  	// false
   278  	// false
   279  }
   280  
   281  type otherString string
   282  
   283  func (x otherString) Equal(y otherString) bool {
   284  	return strings.ToLower(string(x)) == strings.ToLower(string(y))
   285  }
   286  
   287  // If the Equal method defined on a type is not suitable, the type can be be
   288  // dynamically transformed to be stripped of the Equal method (or any method
   289  // for that matter).
   290  func ExampleOption_avoidEqualMethod() {
   291  	// Suppose otherString.Equal performs a case-insensitive equality,
   292  	// which is too loose for our needs.
   293  	// We can avoid the methods of otherString by declaring a new type.
   294  	type myString otherString
   295  
   296  	// This transformer converts otherString to myString, allowing Equal to use
   297  	// other Options to determine equality.
   298  	trans := cmp.Transformer("", func(in otherString) myString {
   299  		return myString(in)
   300  	})
   301  
   302  	x := []otherString{"foo", "bar", "baz"}
   303  	y := []otherString{"fOO", "bAr", "Baz"} // Same as before, but with different case
   304  
   305  	fmt.Println(cmp.Equal(x, y))        // Equal because of case-insensitivity
   306  	fmt.Println(cmp.Equal(x, y, trans)) // Not equal because of more exact equality
   307  
   308  	// Output:
   309  	// true
   310  	// false
   311  }
   312  
   313  func roundF64(z float64) float64 {
   314  	if z < 0 {
   315  		return math.Ceil(z - 0.5)
   316  	}
   317  	return math.Floor(z + 0.5)
   318  }
   319  
   320  // The complex numbers complex64 and complex128 can really just be decomposed
   321  // into a pair of float32 or float64 values. It would be convenient to be able
   322  // define only a single comparator on float64 and have float32, complex64, and
   323  // complex128 all be able to use that comparator. Transformations can be used
   324  // to handle this.
   325  func ExampleOption_transformComplex() {
   326  	opts := []cmp.Option{
   327  		// This transformer decomposes complex128 into a pair of float64s.
   328  		cmp.Transformer("T1", func(in complex128) (out struct{ Real, Imag float64 }) {
   329  			out.Real, out.Imag = real(in), imag(in)
   330  			return out
   331  		}),
   332  		// This transformer converts complex64 to complex128 to allow the
   333  		// above transform to take effect.
   334  		cmp.Transformer("T2", func(in complex64) complex128 {
   335  			return complex128(in)
   336  		}),
   337  		// This transformer converts float32 to float64.
   338  		cmp.Transformer("T3", func(in float32) float64 {
   339  			return float64(in)
   340  		}),
   341  		// This equality function compares float64s as rounded integers.
   342  		cmp.Comparer(func(x, y float64) bool {
   343  			return roundF64(x) == roundF64(y)
   344  		}),
   345  	}
   346  
   347  	x := []interface{}{
   348  		complex128(3.0), complex64(5.1 + 2.9i), float32(-1.2), float64(12.3),
   349  	}
   350  	y := []interface{}{
   351  		complex128(3.1), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
   352  	}
   353  	z := []interface{}{
   354  		complex128(3.8), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
   355  	}
   356  
   357  	fmt.Println(cmp.Equal(x, y, opts...))
   358  	fmt.Println(cmp.Equal(y, z, opts...))
   359  	fmt.Println(cmp.Equal(z, x, opts...))
   360  
   361  	// Output:
   362  	// true
   363  	// false
   364  	// false
   365  }
   366  
   367  type fakeT struct{}
   368  
   369  func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }