Add cmpopts helper package (#8)

This adds the following API surface:
	func EquateApprox(fraction, margin float64) cmp.Option
	func EquateEmpty() cmp.Option
	func EquateNaNs() cmp.Option
	func IgnoreFields(typ interface{}, names ...string) cmp.Option
	func IgnoreInterfaces(ifaces interface{}) cmp.Option
	func IgnoreTypes(typs ...interface{}) cmp.Option
	func IgnoreUnexported(typs ...interface{}) cmp.Option
	func SortMaps(less interface{}) cmp.Option
	func SortSlices(less interface{}) cmp.Option

Reviewed-By: bcmills@google.com
diff --git a/cmp/cmpopts/equate.go b/cmp/cmpopts/equate.go
new file mode 100644
index 0000000..cc39492
--- /dev/null
+++ b/cmp/cmpopts/equate.go
@@ -0,0 +1,89 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// Package cmpopts provides common options for the cmp package.
+package cmpopts
+
+import (
+	"math"
+	"reflect"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func equateAlways(_, _ interface{}) bool { return true }
+
+// EquateEmpty returns a Comparer option that determines all maps and slices
+// with a length of zero to be equal, regardless of whether they are nil.
+//
+// EquateEmpty can be used in conjuction with SortSlices and SortMaps.
+func EquateEmpty() cmp.Option {
+	return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
+}
+
+func isEmpty(x, y interface{}) bool {
+	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+	return (x != nil && y != nil && vx.Type() == vy.Type()) &&
+		(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
+		(vx.Len() == 0 && vy.Len() == 0)
+}
+
+// EquateApprox returns a Comparer option that determines float32 or float64
+// values to be equal if they are within a relative fraction or absolute margin.
+// This option is not used when either x or y is NaN or infinite.
+//
+// The fraction determines that the difference of two values must be within the
+// smaller fraction of the two values, while the margin determines that the two
+// values must be within some absolute margin.
+// To express only a fraction or only a margin, use 0 for the other parameter.
+// The fraction and margin must be non-negative.
+//
+// The mathematical expression used is equivalent to:
+//	|x-y| ≤ max(fraction*min(|x|, |y|), margin)
+//
+// EquateApprox can be used in conjuction with EquateNaNs.
+func EquateApprox(fraction, margin float64) cmp.Option {
+	if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
+		panic("margin or fraction must be a non-negative number")
+	}
+	a := approximator{fraction, margin}
+	return cmp.Options{
+		cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
+		cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
+	}
+}
+
+type approximator struct{ frac, marg float64 }
+
+func areRealF64s(x, y float64) bool {
+	return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
+}
+func areRealF32s(x, y float32) bool {
+	return areRealF64s(float64(x), float64(y))
+}
+func (a approximator) compareF64(x, y float64) bool {
+	relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
+	return math.Abs(x-y) <= math.Max(a.marg, relMarg)
+}
+func (a approximator) compareF32(x, y float32) bool {
+	return a.compareF64(float64(x), float64(y))
+}
+
+// EquateNaNs returns a Comparer option that determines float32 and float64
+// NaN values to be equal.
+//
+// EquateNaNs can be used in conjuction with EquateApprox.
+func EquateNaNs() cmp.Option {
+	return cmp.Options{
+		cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
+		cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
+	}
+}
+
+func areNaNsF64s(x, y float64) bool {
+	return math.IsNaN(x) && math.IsNaN(y)
+}
+func areNaNsF32s(x, y float32) bool {
+	return areNaNsF64s(float64(x), float64(y))
+}
diff --git a/cmp/cmpopts/ignore.go b/cmp/cmpopts/ignore.go
new file mode 100644
index 0000000..016891d
--- /dev/null
+++ b/cmp/cmpopts/ignore.go
@@ -0,0 +1,148 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// IgnoreFields returns an Option that ignores exported fields of the
+// given names on a single struct type.
+// The struct type is specified by passing in a value of that type.
+//
+// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
+// specific sub-field that is embedded or nested within the parent struct.
+//
+// This does not handle unexported fields; use IgnoreUnexported instead.
+func IgnoreFields(typ interface{}, names ...string) cmp.Option {
+	sf := newStructFilter(typ, names...)
+	return cmp.FilterPath(sf.filter, cmp.Ignore())
+}
+
+// IgnoreTypes returns an Option that ignores all values assignable to
+// certain types, which are specified by passing in a value of each type.
+func IgnoreTypes(typs ...interface{}) cmp.Option {
+	tf := newTypeFilter(typs...)
+	return cmp.FilterPath(tf.filter, cmp.Ignore())
+}
+
+type typeFilter []reflect.Type
+
+func newTypeFilter(typs ...interface{}) (tf typeFilter) {
+	for _, typ := range typs {
+		t := reflect.TypeOf(typ)
+		if t == nil {
+			// This occurs if someone tries to pass in sync.Locker(nil)
+			panic("cannot determine type; consider using IgnoreInterfaces")
+		}
+		tf = append(tf, t)
+	}
+	return tf
+}
+func (tf typeFilter) filter(p cmp.Path) bool {
+	if len(p) < 1 {
+		return false
+	}
+	t := p[len(p)-1].Type()
+	for _, ti := range tf {
+		if t.AssignableTo(ti) {
+			return true
+		}
+	}
+	return false
+}
+
+// IgnoreInterfaces returns an Option that ignores all values or references of
+// values assignable to certain interface types. These interfaces are specified
+// by passing in an anonymous struct with the interface types embedded in it.
+// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
+func IgnoreInterfaces(ifaces interface{}) cmp.Option {
+	tf := newIfaceFilter(ifaces)
+	return cmp.FilterPath(tf.filter, cmp.Ignore())
+}
+
+type ifaceFilter []reflect.Type
+
+func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
+	t := reflect.TypeOf(ifaces)
+	if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
+		panic("input must be an anonymous struct")
+	}
+	for i := 0; i < t.NumField(); i++ {
+		fi := t.Field(i)
+		switch {
+		case !fi.Anonymous:
+			panic("struct cannot have named fields")
+		case fi.Type.Kind() != reflect.Interface:
+			panic("embedded field must be an interface type")
+		case fi.Type.NumMethod() == 0:
+			// This matches everything; why would you ever want this?
+			panic("cannot ignore empty interface")
+		default:
+			tf = append(tf, fi.Type)
+		}
+	}
+	return tf
+}
+func (tf ifaceFilter) filter(p cmp.Path) bool {
+	if len(p) < 1 {
+		return false
+	}
+	t := p[len(p)-1].Type()
+	for _, ti := range tf {
+		if t.AssignableTo(ti) {
+			return true
+		}
+		if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
+			return true
+		}
+	}
+	return false
+}
+
+// IgnoreUnexported returns an Option that only ignores the immediate unexported
+// fields of a struct, including anonymous fields of unexported types.
+// In particular, unexported fields within the struct's exported fields
+// of struct types, including anonymous fields, will not be ignored unless the
+// type of the field itself is also passed to IgnoreUnexported.
+func IgnoreUnexported(typs ...interface{}) cmp.Option {
+	ux := newUnexportedFilter(typs...)
+	return cmp.FilterPath(ux.filter, cmp.Ignore())
+}
+
+type unexportedFilter struct{ m map[reflect.Type]bool }
+
+func newUnexportedFilter(typs ...interface{}) unexportedFilter {
+	ux := unexportedFilter{m: make(map[reflect.Type]bool)}
+	for _, typ := range typs {
+		t := reflect.TypeOf(typ)
+		if t == nil || t.Kind() != reflect.Struct {
+			panic(fmt.Sprintf("invalid struct type: %T", typ))
+		}
+		ux.m[t] = true
+	}
+	return ux
+}
+func (xf unexportedFilter) filter(p cmp.Path) bool {
+	if len(p) < 2 {
+		return false
+	}
+	sf, ok := p[len(p)-1].(cmp.StructField)
+	if !ok {
+		return false
+	}
+	return xf.m[p[len(p)-2].Type()] && !isExported(sf.Name())
+}
+
+// isExported reports whether the identifier is exported.
+func isExported(id string) bool {
+	r, _ := utf8.DecodeRuneInString(id)
+	return unicode.IsUpper(r)
+}
diff --git a/cmp/cmpopts/sort.go b/cmp/cmpopts/sort.go
new file mode 100644
index 0000000..e155467
--- /dev/null
+++ b/cmp/cmpopts/sort.go
@@ -0,0 +1,155 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// SortSlices returns a Transformer option that sorts all []V.
+// The less function must be of the form "func(T, T) bool" which is used to
+// sort any slice with element type V that is assignable to T.
+//
+// The less function must be:
+//	• Deterministic: less(x, y) == less(x, y)
+//	• Irreflexive: !less(x, x)
+//	• Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
+//
+// The less function does not have to be "total". That is, if !less(x, y) and
+// !less(y, x) for two elements x and y, their relative order is maintained.
+//
+// SortSlices can be used in conjuction with EquateEmpty.
+func SortSlices(less interface{}) cmp.Option {
+	vf := reflect.ValueOf(less)
+	if !isTTBoolFunc(vf.Type()) || vf.IsNil() {
+		panic(fmt.Sprintf("invalid less function: %T", less))
+	}
+	ss := sliceSorter{vf.Type().In(0), vf}
+	return cmp.FilterValues(ss.filter, cmp.Transformer("Sort", ss.sort))
+}
+
+type sliceSorter struct {
+	in  reflect.Type  // T
+	fnc reflect.Value // func(T, T) bool
+}
+
+func (ss sliceSorter) filter(x, y interface{}) bool {
+	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+	if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
+		!(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
+		(vx.Len() <= 1 && vy.Len() <= 1) {
+		return false
+	}
+	// Check whether the slices are already sorted to avoid an infinite
+	// recursion cycle applying the same transform to itself.
+	ok1 := sliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
+	ok2 := sliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
+	return !ok1 || !ok2
+}
+func (ss sliceSorter) sort(x interface{}) interface{} {
+	src := reflect.ValueOf(x)
+	dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
+	for i := 0; i < src.Len(); i++ {
+		dst.Index(i).Set(src.Index(i))
+	}
+	sortSliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
+	ss.checkSort(dst)
+	return dst.Interface()
+}
+func (ss sliceSorter) checkSort(v reflect.Value) {
+	start := -1 // Start of a sequence of equal elements.
+	for i := 1; i < v.Len(); i++ {
+		if ss.less(v, i-1, i) {
+			// Check that first and last elements in v[start:i] are equal.
+			if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
+				panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
+			}
+			start = -1
+		} else if start == -1 {
+			start = i
+		}
+	}
+}
+func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
+	vx, vy := v.Index(i), v.Index(j)
+	return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
+}
+
+// SortMaps returns a Transformer option that flattens map[K]V types to be a
+// sorted []struct{K, V}. The less function must be of the form
+// "func(T, T) bool" which is used to sort any map with key K that is
+// assignable to T.
+//
+// Flattening the map into a slice has the property that cmp.Equal is able to
+// use Comparers on K or the K.Equal method if it exists.
+//
+// The less function must be:
+//	• Deterministic: less(x, y) == less(x, y)
+//	• Irreflexive: !less(x, x)
+//	• Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
+//	• Total: if x != y, then either less(x, y) or less(y, x)
+//
+// SortMaps can be used in conjuction with EquateEmpty.
+func SortMaps(less interface{}) cmp.Option {
+	vf := reflect.ValueOf(less)
+	if !isTTBoolFunc(vf.Type()) || vf.IsNil() {
+		panic(fmt.Sprintf("invalid less function: %T", less))
+	}
+	ms := mapSorter{vf.Type().In(0), vf}
+	return cmp.FilterValues(ms.filter, cmp.Transformer("Sort", ms.sort))
+}
+
+type mapSorter struct {
+	in  reflect.Type  // T
+	fnc reflect.Value // func(T, T) bool
+}
+
+func (ms mapSorter) filter(x, y interface{}) bool {
+	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
+	return (x != nil && y != nil && vx.Type() == vy.Type()) &&
+		(vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
+		(vx.Len() != 0 || vy.Len() != 0)
+}
+func (ms mapSorter) sort(x interface{}) interface{} {
+	src := reflect.ValueOf(x)
+	outType := mapEntryType(src.Type())
+	dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
+	for i, k := range src.MapKeys() {
+		v := reflect.New(outType).Elem()
+		v.Field(0).Set(k)
+		v.Field(1).Set(src.MapIndex(k))
+		dst.Index(i).Set(v)
+	}
+	sortSlice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
+	ms.checkSort(dst)
+	return dst.Interface()
+}
+func (ms mapSorter) checkSort(v reflect.Value) {
+	for i := 1; i < v.Len(); i++ {
+		if !ms.less(v, i-1, i) {
+			panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
+		}
+	}
+}
+func (ms mapSorter) less(v reflect.Value, i, j int) bool {
+	vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
+	if !hasReflectStructOf {
+		vx, vy = vx.Elem(), vy.Elem()
+	}
+	return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
+}
+
+var boolType = reflect.TypeOf(true)
+
+// isTTBoolFunc reports whether f is of the form: func(T, T) bool.
+func isTTBoolFunc(t reflect.Type) bool {
+	if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
+		return false
+	}
+	return t.NumIn() == 2 && t.NumOut() == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType
+}
diff --git a/cmp/cmpopts/sort_go17.go b/cmp/cmpopts/sort_go17.go
new file mode 100644
index 0000000..839b88c
--- /dev/null
+++ b/cmp/cmpopts/sort_go17.go
@@ -0,0 +1,46 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// +build !go1.8
+
+package cmpopts
+
+import (
+	"reflect"
+	"sort"
+)
+
+const hasReflectStructOf = false
+
+func mapEntryType(reflect.Type) reflect.Type {
+	return reflect.TypeOf(struct{ K, V interface{} }{})
+}
+
+func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
+	return sort.IsSorted(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+func sortSlice(slice interface{}, less func(i, j int) bool) {
+	sort.Sort(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+func sortSliceStable(slice interface{}, less func(i, j int) bool) {
+	sort.Stable(reflectSliceSorter{reflect.ValueOf(slice), less})
+}
+
+type reflectSliceSorter struct {
+	slice reflect.Value
+	less  func(i, j int) bool
+}
+
+func (ss reflectSliceSorter) Len() int {
+	return ss.slice.Len()
+}
+func (ss reflectSliceSorter) Less(i, j int) bool {
+	return ss.less(i, j)
+}
+func (ss reflectSliceSorter) Swap(i, j int) {
+	vi := ss.slice.Index(i).Interface()
+	vj := ss.slice.Index(j).Interface()
+	ss.slice.Index(i).Set(reflect.ValueOf(vj))
+	ss.slice.Index(j).Set(reflect.ValueOf(vi))
+}
diff --git a/cmp/cmpopts/sort_go18.go b/cmp/cmpopts/sort_go18.go
new file mode 100644
index 0000000..2eeb3b2
--- /dev/null
+++ b/cmp/cmpopts/sort_go18.go
@@ -0,0 +1,31 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// +build go1.8
+
+package cmpopts
+
+import (
+	"reflect"
+	"sort"
+)
+
+const hasReflectStructOf = true
+
+func mapEntryType(t reflect.Type) reflect.Type {
+	return reflect.StructOf([]reflect.StructField{
+		reflect.StructField{Name: "K", Type: t.Key()},
+		reflect.StructField{Name: "V", Type: t.Elem()},
+	})
+}
+
+func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
+	return sort.SliceIsSorted(slice, less)
+}
+func sortSlice(slice interface{}, less func(i, j int) bool) {
+	sort.Slice(slice, less)
+}
+func sortSliceStable(slice interface{}, less func(i, j int) bool) {
+	sort.SliceStable(slice, less)
+}
diff --git a/cmp/cmpopts/struct_filter.go b/cmp/cmpopts/struct_filter.go
new file mode 100644
index 0000000..97f7079
--- /dev/null
+++ b/cmp/cmpopts/struct_filter.go
@@ -0,0 +1,182 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+// filterField returns a new Option where opt is only evaluated on paths that
+// include a specific exported field on a single struct type.
+// The struct type is specified by passing in a value of that type.
+//
+// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
+// specific sub-field that is embedded or nested within the parent struct.
+func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
+	// TODO: This is currently unexported over concerns of how helper filters
+	// can be composed together easily.
+	// TODO: Add tests for FilterField.
+
+	sf := newStructFilter(typ, name)
+	return cmp.FilterPath(sf.filter, opt)
+}
+
+type structFilter struct {
+	t  reflect.Type // The root struct type to match on
+	ft fieldTree    // Tree of fields to match on
+}
+
+func newStructFilter(typ interface{}, names ...string) structFilter {
+	// TODO: Perhaps allow * as a special identifier to allow ignoring any
+	// number of path steps until the next field match?
+	// This could be useful when a concrete struct gets transformed into
+	// an anonymous struct where it is not possible to specify that by type,
+	// but the transformer happens to provide guarantees about the names of
+	// the transformed fields.
+
+	t := reflect.TypeOf(typ)
+	if t == nil || t.Kind() != reflect.Struct {
+		panic(fmt.Sprintf("%T must be a struct", typ))
+	}
+	var ft fieldTree
+	for _, name := range names {
+		cname, err := canonicalName(t, name)
+		if err != nil {
+			panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
+		}
+		ft.insert(cname)
+	}
+	return structFilter{t, ft}
+}
+
+func (sf structFilter) filter(p cmp.Path) bool {
+	for i, ps := range p {
+		if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
+			return true
+		}
+	}
+	return false
+}
+
+// fieldTree represents a set of dot-separated identifiers.
+//
+// For example, inserting the following selectors:
+//	Foo
+//	Foo.Bar.Baz
+//	Foo.Buzz
+//	Nuka.Cola.Quantum
+//
+// Results in a tree of the form:
+//	{sub: {
+//		"Foo": {ok: true, sub: {
+//			"Bar": {sub: {
+//				"Baz": {ok: true},
+//			}},
+//			"Buzz": {ok: true},
+//		}},
+//		"Nuka": {sub: {
+//			"Cola": {sub: {
+//				"Quantum": {ok: true},
+//			}},
+//		}},
+//	}}
+type fieldTree struct {
+	ok  bool                 // Whether this is a specified node
+	sub map[string]fieldTree // The sub-tree of fields under this node
+}
+
+// insert inserts a sequence of field accesses into the tree.
+func (ft *fieldTree) insert(cname []string) {
+	if ft.sub == nil {
+		ft.sub = make(map[string]fieldTree)
+	}
+	if len(cname) == 0 {
+		ft.ok = true
+		return
+	}
+	sub := ft.sub[cname[0]]
+	sub.insert(cname[1:])
+	ft.sub[cname[0]] = sub
+}
+
+// matchPrefix reports whether any selector in the fieldTree matches
+// the start of path p.
+func (ft fieldTree) matchPrefix(p cmp.Path) bool {
+	for _, ps := range p {
+		switch ps := ps.(type) {
+		case cmp.StructField:
+			ft = ft.sub[ps.Name()]
+			if ft.ok {
+				return true
+			}
+			if len(ft.sub) == 0 {
+				return false
+			}
+		case cmp.Indirect:
+		default:
+			return false
+		}
+	}
+	return false
+}
+
+// canonicalName returns a list of identifiers where any struct field access
+// through an embedded field is expanded to include the names of the embedded
+// types themselves.
+//
+// For example, suppose field "Foo" is not directly in the parent struct,
+// but actually from an embedded struct of type "Bar". Then, the canonical name
+// of "Foo" is actually "Bar.Foo".
+//
+// Suppose field "Foo" is not directly in the parent struct, but actually
+// a field in two different embedded structs of types "Bar" and "Baz".
+// Then the selector "Foo" causes a panic since it is ambiguous which one it
+// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
+func canonicalName(t reflect.Type, sel string) ([]string, error) {
+	var name string
+	sel = strings.TrimPrefix(sel, ".")
+	if sel == "" {
+		return nil, fmt.Errorf("name must not be empty")
+	}
+	if i := strings.IndexByte(sel, '.'); i < 0 {
+		name, sel = sel, ""
+	} else {
+		name, sel = sel[:i], sel[i:]
+	}
+
+	// Type must be a struct or pointer to struct.
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() != reflect.Struct {
+		return nil, fmt.Errorf("%v must be a struct", t)
+	}
+
+	// Find the canonical name for this current field name.
+	// If the field exists in an embedded struct, then it will be expanded.
+	if !isExported(name) {
+		// Disallow unexported fields:
+		//	* To discourage people from actually touching unexported fields
+		//	* FieldByName is buggy (https://golang.org/issue/4876)
+		return []string{name}, fmt.Errorf("name must be exported")
+	}
+	sf, ok := t.FieldByName(name)
+	if !ok {
+		return []string{name}, fmt.Errorf("does not exist")
+	}
+	var ss []string
+	for i := range sf.Index {
+		ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
+	}
+	if sel == "" {
+		return ss, nil
+	}
+	ssPost, err := canonicalName(sf.Type, sel)
+	return append(ss, ssPost...), err
+}
diff --git a/cmp/cmpopts/util_test.go b/cmp/cmpopts/util_test.go
new file mode 100644
index 0000000..49ac0d6
--- /dev/null
+++ b/cmp/cmpopts/util_test.go
@@ -0,0 +1,996 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmpopts
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math"
+	"reflect"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+type (
+	MyInt    int
+	MyFloat  float32
+	MyTime   struct{ time.Time }
+	MyStruct struct {
+		A, B []int
+		C, D map[time.Time]string
+	}
+
+	Foo1 struct{ Alpha, Bravo, Charlie int }
+	Foo2 struct{ *Foo1 }
+	Foo3 struct{ *Foo2 }
+	Bar1 struct{ Foo3 }
+	Bar2 struct {
+		Bar1
+		*Foo3
+		Bravo float32
+	}
+	Bar3 struct {
+		Bar1
+		Bravo *Bar2
+		Delta struct{ Echo Foo1 }
+		*Foo3
+		Alpha string
+	}
+
+	privateStruct struct{ Public, private int }
+	PublicStruct  struct{ Public, private int }
+	ParentStruct  struct {
+		*privateStruct
+		*PublicStruct
+		Public  int
+		private int
+	}
+
+	Everything struct {
+		MyInt
+		MyFloat
+		MyTime
+		MyStruct
+		Bar3
+		ParentStruct
+	}
+
+	EmptyInterface interface{}
+)
+
+func TestOptions(t *testing.T) {
+	createBar3X := func() *Bar3 {
+		return &Bar3{
+			Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}},
+			Bravo: &Bar2{
+				Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}},
+				Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 5}}},
+				Bravo: 4,
+			},
+			Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}},
+			Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 1}}},
+			Alpha: "alpha",
+		}
+	}
+	createBar3Y := func() *Bar3 {
+		return &Bar3{
+			Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}},
+			Bravo: &Bar2{
+				Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}},
+				Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 6}}},
+				Bravo: 5,
+			},
+			Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}},
+			Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 2}}},
+			Alpha: "ALPHA",
+		}
+	}
+
+	tests := []struct {
+		label     string       // Test name
+		x, y      interface{}  // Input values to compare
+		opts      []cmp.Option // Input options
+		wantEqual bool         // Whether the inputs are equal
+		wantPanic bool         // Whether Equal should panic
+		reason    string       // The reason for the expected outcome
+	}{{
+		label:     "EquateEmpty",
+		x:         []int{},
+		y:         []int(nil),
+		wantEqual: false,
+		reason:    "not equal because empty non-nil and nil slice differ",
+	}, {
+		label:     "EquateEmpty",
+		x:         []int{},
+		y:         []int(nil),
+		opts:      []cmp.Option{EquateEmpty()},
+		wantEqual: true,
+		reason:    "equal because EquateEmpty equates empty slices",
+	}, {
+		label:     "SortSlices",
+		x:         []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+		y:         []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
+		wantEqual: false,
+		reason:    "not equal because element order differs",
+	}, {
+		label:     "SortSlices",
+		x:         []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+		y:         []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
+		opts:      []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
+		wantEqual: true,
+		reason:    "equal because SortSlices sorts the slices",
+	}, {
+		label:     "SortSlices",
+		x:         []MyInt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+		y:         []MyInt{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
+		opts:      []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
+		wantEqual: false,
+		reason:    "not equal because MyInt is not the same type as int",
+	}, {
+		label:     "SortSlices",
+		x:         []float64{0, 1, 1, 2, 2, 2},
+		y:         []float64{2, 0, 2, 1, 2, 1},
+		opts:      []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
+		wantEqual: true,
+		reason:    "equal even when sorted with duplicate elements",
+	}, {
+		label:     "SortSlices",
+		x:         []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
+		y:         []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
+		opts:      []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
+		wantPanic: true,
+		reason:    "panics because SortSlices used with non-transitive less function",
+	}, {
+		label: "SortSlices",
+		x:     []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
+		y:     []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
+		opts: []cmp.Option{SortSlices(func(x, y float64) bool {
+			return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
+		})},
+		wantEqual: false,
+		reason:    "no panics because SortSlices used with valid less function; not equal because NaN != NaN",
+	}, {
+		label: "SortSlices+EquateNaNs",
+		x:     []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, math.NaN(), 3, 4, 4, 4, 4},
+		y:     []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, math.NaN(), 2},
+		opts: []cmp.Option{
+			EquateNaNs(),
+			SortSlices(func(x, y float64) bool {
+				return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
+			}),
+		},
+		wantEqual: true,
+		reason:    "no panics because SortSlices used with valid less function; equal because EquateNaNs is used",
+	}, {
+		label: "SortMaps",
+		x: map[time.Time]string{
+			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
+			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
+			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
+		},
+		y: map[time.Time]string{
+			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
+			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
+			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
+		},
+		wantEqual: false,
+		reason:    "not equal because timezones differ",
+	}, {
+		label: "SortMaps",
+		x: map[time.Time]string{
+			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
+			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
+			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
+		},
+		y: map[time.Time]string{
+			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
+			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
+			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
+		},
+		opts:      []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
+		wantEqual: true,
+		reason:    "equal because SortMaps flattens to a slice where Time.Equal can be used",
+	}, {
+		label: "SortMaps",
+		x: map[MyTime]string{
+			MyTime{time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}: "0th birthday",
+			MyTime{time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)}: "1st birthday",
+			MyTime{time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC)}: "2nd birthday",
+		},
+		y: map[MyTime]string{
+			MyTime{time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "0th birthday",
+			MyTime{time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "1st birthday",
+			MyTime{time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "2nd birthday",
+		},
+		opts:      []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
+		wantEqual: false,
+		reason:    "not equal because MyTime is not assignable to time.Time",
+	}, {
+		label: "SortMaps",
+		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
+		// => {0, 1, 2, 3, -1, -2, -3},
+		y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
+		// => {0, 1, 2, 3, 100, 200, 300},
+		opts: []cmp.Option{SortMaps(func(a, b int) bool {
+			if -10 < a && a <= 0 {
+				a *= -100
+			}
+			if -10 < b && b <= 0 {
+				b *= -100
+			}
+			return a < b
+		})},
+		wantEqual: false,
+		reason:    "not equal because values differ even though SortMap provides valid ordering",
+	}, {
+		label: "SortMaps",
+		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
+		// => {0, 1, 2, 3, -1, -2, -3},
+		y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
+		// => {0, 1, 2, 3, 100, 200, 300},
+		opts: []cmp.Option{
+			SortMaps(func(x, y int) bool {
+				if -10 < x && x <= 0 {
+					x *= -100
+				}
+				if -10 < y && y <= 0 {
+					y *= -100
+				}
+				return x < y
+			}),
+			cmp.Comparer(func(x, y int) bool {
+				if -10 < x && x <= 0 {
+					x *= -100
+				}
+				if -10 < y && y <= 0 {
+					y *= -100
+				}
+				return x == y
+			}),
+		},
+		wantEqual: true,
+		reason:    "equal because Comparer used to equate differences",
+	}, {
+		label: "SortMaps",
+		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
+		y:     map[int]string{},
+		opts: []cmp.Option{SortMaps(func(x, y int) bool {
+			return x < y && x >= 0 && y >= 0
+		})},
+		wantPanic: true,
+		reason:    "panics because SortMaps used with non-transitive less function",
+	}, {
+		label: "SortMaps",
+		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
+		y:     map[int]string{},
+		opts: []cmp.Option{SortMaps(func(x, y int) bool {
+			return math.Abs(float64(x)) < math.Abs(float64(y))
+		})},
+		wantPanic: true,
+		reason:    "panics because SortMaps used with partial less function",
+	}, {
+		label: "EquateEmpty+SortSlices+SortMaps",
+		x: MyStruct{
+			A: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+			C: map[time.Time]string{
+				time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
+				time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
+			},
+			D: map[time.Time]string{},
+		},
+		y: MyStruct{
+			A: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
+			B: []int{},
+			C: map[time.Time]string{
+				time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
+				time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
+			},
+		},
+		opts: []cmp.Option{
+			EquateEmpty(),
+			SortSlices(func(x, y int) bool { return x < y }),
+			SortMaps(func(x, y time.Time) bool { return x.Before(y) }),
+		},
+		wantEqual: true,
+		reason:    "no panics because EquateEmpty should compose with the sort options",
+	}, {
+		label:     "EquateApprox",
+		x:         3.09,
+		y:         3.10,
+		wantEqual: false,
+		reason:    "not equal because floats do not exactly matches",
+	}, {
+		label:     "EquateApprox",
+		x:         3.09,
+		y:         3.10,
+		opts:      []cmp.Option{EquateApprox(0, 0)},
+		wantEqual: false,
+		reason:    "not equal because EquateApprox(0 ,0) is equivalent to using ==",
+	}, {
+		label:     "EquateApprox",
+		x:         3.09,
+		y:         3.10,
+		opts:      []cmp.Option{EquateApprox(0.003, 0.009)},
+		wantEqual: false,
+		reason:    "not equal because EquateApprox is too strict",
+	}, {
+		label:     "EquateApprox",
+		x:         3.09,
+		y:         3.10,
+		opts:      []cmp.Option{EquateApprox(0, 0.011)},
+		wantEqual: true,
+		reason:    "equal because margin is loose enough to match",
+	}, {
+		label:     "EquateApprox",
+		x:         3.09,
+		y:         3.10,
+		opts:      []cmp.Option{EquateApprox(0.004, 0)},
+		wantEqual: true,
+		reason:    "equal because fraction is loose enough to match",
+	}, {
+		label:     "EquateApprox",
+		x:         3.09,
+		y:         3.10,
+		opts:      []cmp.Option{EquateApprox(0.004, 0.011)},
+		wantEqual: true,
+		reason:    "equal because both the margin and fraction are loose enough to match",
+	}, {
+		label:     "EquateApprox",
+		x:         float32(3.09),
+		y:         float64(3.10),
+		opts:      []cmp.Option{EquateApprox(0.004, 0)},
+		wantEqual: false,
+		reason:    "not equal because the types differ",
+	}, {
+		label:     "EquateApprox",
+		x:         float32(3.09),
+		y:         float32(3.10),
+		opts:      []cmp.Option{EquateApprox(0.004, 0)},
+		wantEqual: true,
+		reason:    "equal because EquateApprox also applies on float32s",
+	}, {
+		label:     "EquateApprox",
+		x:         []float64{math.Inf(+1), math.Inf(-1)},
+		y:         []float64{math.Inf(+1), math.Inf(-1)},
+		opts:      []cmp.Option{EquateApprox(0, 1)},
+		wantEqual: true,
+		reason:    "equal because we fall back on == which matches Inf (EquateApprox does not apply on Inf) ",
+	}, {
+		label:     "EquateApprox",
+		x:         []float64{math.Inf(+1), -1e100},
+		y:         []float64{+1e100, math.Inf(-1)},
+		opts:      []cmp.Option{EquateApprox(0, 1)},
+		wantEqual: false,
+		reason:    "not equal because we fall back on == where Inf != 1e100 (EquateApprox does not apply on Inf)",
+	}, {
+		label:     "EquateApprox",
+		x:         float64(+1e100),
+		y:         float64(-1e100),
+		opts:      []cmp.Option{EquateApprox(math.Inf(+1), 0)},
+		wantEqual: true,
+		reason:    "equal because infinite fraction matches everything",
+	}, {
+		label:     "EquateApprox",
+		x:         float64(+1e100),
+		y:         float64(-1e100),
+		opts:      []cmp.Option{EquateApprox(0, math.Inf(+1))},
+		wantEqual: true,
+		reason:    "equal because infinite margin matches everything",
+	}, {
+		label:     "EquateApprox",
+		x:         math.Pi,
+		y:         math.Pi,
+		opts:      []cmp.Option{EquateApprox(0, 0)},
+		wantEqual: true,
+		reason:    "equal because EquateApprox(0, 0) is equivalent to ==",
+	}, {
+		label:     "EquateApprox",
+		x:         math.Pi,
+		y:         math.Nextafter(math.Pi, math.Inf(+1)),
+		opts:      []cmp.Option{EquateApprox(0, 0)},
+		wantEqual: false,
+		reason:    "not equal because EquateApprox(0, 0) is equivalent to ==",
+	}, {
+		label:     "EquateNaNs",
+		x:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
+		y:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
+		wantEqual: false,
+		reason:    "not equal because NaN != NaN",
+	}, {
+		label:     "EquateNaNs",
+		x:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
+		y:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
+		opts:      []cmp.Option{EquateNaNs()},
+		wantEqual: true,
+		reason:    "equal because EquateNaNs allows NaN == NaN",
+	}, {
+		label:     "EquateNaNs",
+		x:         []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
+		y:         []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
+		opts:      []cmp.Option{EquateNaNs()},
+		wantEqual: true,
+		reason:    "equal because EquateNaNs operates on float32",
+	}, {
+		label: "EquateApprox+EquateNaNs",
+		x:     []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.01, 5001},
+		y:     []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.02, 5002},
+		opts: []cmp.Option{
+			EquateNaNs(),
+			EquateApprox(0.01, 0),
+		},
+		wantEqual: true,
+		reason:    "equal because EquateNaNs and EquateApprox compose together",
+	}, {
+		label: "EquateApprox+EquateNaNs",
+		x:     []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001},
+		y:     []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002},
+		opts: []cmp.Option{
+			EquateNaNs(),
+			EquateApprox(0.01, 0),
+		},
+		wantEqual: false,
+		reason:    "not equal because EquateApprox and EquateNaNs do not apply on a named type",
+	}, {
+		label: "EquateApprox+EquateNaNs+Transform",
+		x:     []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001},
+		y:     []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002},
+		opts: []cmp.Option{
+			cmp.Transformer("", func(x MyFloat) float64 { return float64(x) }),
+			EquateNaNs(),
+			EquateApprox(0.01, 0),
+		},
+		wantEqual: true,
+		reason:    "equal because named type is transformed to float64",
+	}, {
+		label:     "IgnoreFields",
+		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
+		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
+		wantEqual: false,
+		reason:    "not equal because values do not match in deeply embedded field",
+	}, {
+		label:     "IgnoreFields",
+		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
+		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
+		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Alpha")},
+		wantEqual: true,
+		reason:    "equal because IgnoreField ignores deeply embedded field: Alpha",
+	}, {
+		label:     "IgnoreFields",
+		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
+		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
+		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")},
+		wantEqual: true,
+		reason:    "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
+	}, {
+		label:     "IgnoreFields",
+		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
+		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
+		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")},
+		wantEqual: true,
+		reason:    "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
+	}, {
+		label:     "IgnoreFields",
+		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
+		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
+		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")},
+		wantEqual: true,
+		reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
+	}, {
+		label:     "IgnoreFields",
+		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
+		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
+		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")},
+		wantEqual: true,
+		reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
+	}, {
+		label:     "IgnoreFields",
+		x:         createBar3X(),
+		y:         createBar3Y(),
+		wantEqual: false,
+		reason:    "not equal because many deeply nested or embedded fields differ",
+	}, {
+		label:     "IgnoreFields",
+		x:         createBar3X(),
+		y:         createBar3Y(),
+		opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
+		wantEqual: true,
+		reason:    "equal because IgnoreFields ignores fields at the highest levels",
+	}, {
+		label: "IgnoreFields",
+		x:     createBar3X(),
+		y:     createBar3Y(),
+		opts: []cmp.Option{
+			IgnoreFields(Bar3{},
+				"Bar1.Foo3.Bravo",
+				"Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
+				"Bravo.Foo3.Foo2.Foo1.Bravo",
+				"Bravo.Bravo",
+				"Delta.Echo.Charlie",
+				"Foo3.Foo2.Foo1.Alpha",
+				"Alpha",
+			),
+		},
+		wantEqual: true,
+		reason:    "equal because IgnoreFields ignores fields using fully-qualified field",
+	}, {
+		label: "IgnoreFields",
+		x:     createBar3X(),
+		y:     createBar3Y(),
+		opts: []cmp.Option{
+			IgnoreFields(Bar3{},
+				"Bar1.Foo3.Bravo",
+				"Bravo.Foo3.Foo2.Foo1.Bravo",
+				"Bravo.Bravo",
+				"Delta.Echo.Charlie",
+				"Foo3.Foo2.Foo1.Alpha",
+				"Alpha",
+			),
+		},
+		wantEqual: false,
+		reason:    "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
+	}, {
+		label:     "IgnoreFields",
+		x:         createBar3X(),
+		y:         createBar3Y(),
+		opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
+		wantEqual: false,
+		reason:    "not equal because highest-level field is not ignored: Foo3",
+	}, {
+		label:     "IgnoreTypes",
+		x:         []interface{}{5, "same"},
+		y:         []interface{}{6, "same"},
+		wantEqual: false,
+		reason:    "not equal because 5 != 6",
+	}, {
+		label:     "IgnoreTypes",
+		x:         []interface{}{5, "same"},
+		y:         []interface{}{6, "same"},
+		opts:      []cmp.Option{IgnoreTypes(0)},
+		wantEqual: true,
+		reason:    "equal because ints are ignored",
+	}, {
+		label:     "IgnoreTypes+IgnoreInterfaces",
+		x:         []interface{}{5, "same", new(bytes.Buffer)},
+		y:         []interface{}{6, "same", new(bytes.Buffer)},
+		opts:      []cmp.Option{IgnoreTypes(0)},
+		wantPanic: true,
+		reason:    "panics because bytes.Buffer has unexported fields",
+	}, {
+		label: "IgnoreTypes+IgnoreInterfaces",
+		x:     []interface{}{5, "same", new(bytes.Buffer)},
+		y:     []interface{}{6, "diff", new(bytes.Buffer)},
+		opts: []cmp.Option{
+			IgnoreTypes(0, ""),
+			IgnoreInterfaces(struct{ io.Reader }{}),
+		},
+		wantEqual: true,
+		reason:    "equal because bytes.Buffer is ignored by match on interface type",
+	}, {
+		label: "IgnoreTypes+IgnoreInterfaces",
+		x:     []interface{}{5, "same", new(bytes.Buffer)},
+		y:     []interface{}{6, "same", new(bytes.Buffer)},
+		opts: []cmp.Option{
+			IgnoreTypes(0, ""),
+			IgnoreInterfaces(struct {
+				io.Reader
+				io.Writer
+				fmt.Stringer
+			}{}),
+		},
+		wantEqual: true,
+		reason:    "equal because bytes.Buffer is ignored by match on multiple interface types",
+	}, {
+		label:     "IgnoreInterfaces",
+		x:         struct{ mu sync.Mutex }{},
+		y:         struct{ mu sync.Mutex }{},
+		wantPanic: true,
+		reason:    "panics because sync.Mutex has unexported fields",
+	}, {
+		label:     "IgnoreInterfaces",
+		x:         struct{ mu sync.Mutex }{},
+		y:         struct{ mu sync.Mutex }{},
+		opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
+		wantEqual: true,
+		reason:    "equal because IgnoreInterfaces applies on values (with pointer receiver)",
+	}, {
+		label:     "IgnoreInterfaces",
+		x:         struct{ mu *sync.Mutex }{},
+		y:         struct{ mu *sync.Mutex }{},
+		opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
+		wantEqual: true,
+		reason:    "equal because IgnoreInterfaces applies on pointers",
+	}, {
+		label:     "IgnoreUnexported",
+		x:         ParentStruct{Public: 1, private: 2},
+		y:         ParentStruct{Public: 1, private: -2},
+		opts:      []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
+		wantEqual: false,
+		reason:    "not equal because ParentStruct.private differs with AllowUnexported",
+	}, {
+		label:     "IgnoreUnexported",
+		x:         ParentStruct{Public: 1, private: 2},
+		y:         ParentStruct{Public: 1, private: -2},
+		opts:      []cmp.Option{IgnoreUnexported(ParentStruct{})},
+		wantEqual: true,
+		reason:    "equal because IgnoreUnexported ignored ParentStruct.private",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
+		opts: []cmp.Option{
+			cmp.AllowUnexported(PublicStruct{}),
+			IgnoreUnexported(ParentStruct{}),
+		},
+		wantEqual: true,
+		reason:    "equal because ParentStruct.private is ignored",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
+		opts: []cmp.Option{
+			cmp.AllowUnexported(PublicStruct{}),
+			IgnoreUnexported(ParentStruct{}),
+		},
+		wantEqual: false,
+		reason:    "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
+		opts: []cmp.Option{
+			IgnoreUnexported(ParentStruct{}, PublicStruct{}),
+		},
+		wantEqual: true,
+		reason:    "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
+		opts: []cmp.Option{
+			cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
+		},
+		wantEqual: false,
+		reason:    "not equal since ParentStruct.privateStruct differs",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
+		opts: []cmp.Option{
+			cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
+			IgnoreUnexported(ParentStruct{}),
+		},
+		wantEqual: true,
+		reason:    "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}},
+		opts: []cmp.Option{
+			cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
+			IgnoreUnexported(privateStruct{}),
+		},
+		wantEqual: true,
+		reason:    "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
+	}, {
+		label: "IgnoreUnexported",
+		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
+		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
+		opts: []cmp.Option{
+			cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
+			IgnoreUnexported(privateStruct{}),
+		},
+		wantEqual: false,
+		reason:    "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
+	}, {
+		label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
+		x: &Everything{
+			MyInt:   5,
+			MyFloat: 3.3,
+			MyTime:  MyTime{time.Now()},
+			Bar3:    *createBar3X(),
+			ParentStruct: ParentStruct{
+				Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
+			},
+		},
+		y: &Everything{
+			MyInt:   -5,
+			MyFloat: 3.3,
+			MyTime:  MyTime{time.Now()},
+			Bar3:    *createBar3Y(),
+			ParentStruct: ParentStruct{
+				Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
+			},
+		},
+		opts: []cmp.Option{
+			IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
+			IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
+			IgnoreTypes(MyInt(0), PublicStruct{}),
+			IgnoreUnexported(ParentStruct{}),
+		},
+		wantEqual: true,
+		reason:    "equal because all Ignore options can be composed together",
+	}}
+
+	for _, tt := range tests {
+		tRun(t, tt.label, func(t *testing.T) {
+			var gotEqual bool
+			var gotPanic string
+			func() {
+				defer func() {
+					if ex := recover(); ex != nil {
+						gotPanic = fmt.Sprint(ex)
+					}
+				}()
+				gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
+			}()
+			switch {
+			case gotPanic == "" && tt.wantPanic:
+				t.Errorf("expected Equal panic\nreason: %s", tt.reason)
+			case gotPanic != "" && !tt.wantPanic:
+				t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason)
+			case gotEqual != tt.wantEqual:
+				t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
+			}
+		})
+	}
+}
+
+func TestPanic(t *testing.T) {
+	args := func(x ...interface{}) []interface{} { return x }
+	tests := []struct {
+		label     string        // Test name
+		fnc       interface{}   // Option function to call
+		args      []interface{} // Arguments to pass in
+		wantPanic string        // Expected panic message
+		reason    string        // The reason for the expected outcome
+	}{{
+		label:  "EquateApprox",
+		fnc:    EquateApprox,
+		args:   args(0.0, 0.0),
+		reason: "zero margin and fraction is equivalent to exact equality",
+	}, {
+		label:     "EquateApprox",
+		fnc:       EquateApprox,
+		args:      args(-0.1, 0.0),
+		wantPanic: "margin or fraction must be a non-negative number",
+		reason:    "negative inputs are invalid",
+	}, {
+		label:     "EquateApprox",
+		fnc:       EquateApprox,
+		args:      args(0.0, -0.1),
+		wantPanic: "margin or fraction must be a non-negative number",
+		reason:    "negative inputs are invalid",
+	}, {
+		label:     "EquateApprox",
+		fnc:       EquateApprox,
+		args:      args(math.NaN(), 0.0),
+		wantPanic: "margin or fraction must be a non-negative number",
+		reason:    "NaN inputs are invalid",
+	}, {
+		label:  "EquateApprox",
+		fnc:    EquateApprox,
+		args:   args(1.0, 0.0),
+		reason: "fraction of 1.0 or greater is valid",
+	}, {
+		label:  "EquateApprox",
+		fnc:    EquateApprox,
+		args:   args(0.0, math.Inf(+1)),
+		reason: "margin of infinity is valid",
+	}, {
+		label:     "SortSlices",
+		fnc:       SortSlices,
+		args:      args(strings.Compare),
+		wantPanic: "invalid less function",
+		reason:    "func(x, y string) int is wrong signature for less",
+	}, {
+		label:     "SortSlices",
+		fnc:       SortSlices,
+		args:      args((func(_, _ int) bool)(nil)),
+		wantPanic: "invalid less function",
+		reason:    "nil value is not valid",
+	}, {
+		label:     "SortMaps",
+		fnc:       SortMaps,
+		args:      args(strings.Compare),
+		wantPanic: "invalid less function",
+		reason:    "func(x, y string) int is wrong signature for less",
+	}, {
+		label:     "SortMaps",
+		fnc:       SortMaps,
+		args:      args((func(_, _ int) bool)(nil)),
+		wantPanic: "invalid less function",
+		reason:    "nil value is not valid",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, ""),
+		wantPanic: "name must not be empty",
+		reason:    "empty selector is invalid",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, "."),
+		wantPanic: "name must not be empty",
+		reason:    "single dot selector is invalid",
+	}, {
+		label:  "IgnoreFields",
+		fnc:    IgnoreFields,
+		args:   args(Foo1{}, ".Alpha"),
+		reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, "Alpha."),
+		wantPanic: "name must not be empty",
+		reason:    "dot-suffix is invalid",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, "Alpha "),
+		wantPanic: "does not exist",
+		reason:    "identifiers must not have spaces",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, "Zulu"),
+		wantPanic: "does not exist",
+		reason:    "name of non-existent field is invalid",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, "Alpha.NoExist"),
+		wantPanic: "must be a struct",
+		reason:    "cannot select into a non-struct",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(&Foo1{}, "Alpha"),
+		wantPanic: "must be a struct",
+		reason:    "the type must be a struct (not pointer to a struct)",
+	}, {
+		label:     "IgnoreFields",
+		fnc:       IgnoreFields,
+		args:      args(Foo1{}, "unexported"),
+		wantPanic: "name must be exported",
+		reason:    "unexported fields must not be specified",
+	}, {
+		label:  "IgnoreTypes",
+		fnc:    IgnoreTypes,
+		reason: "empty input is valid",
+	}, {
+		label:     "IgnoreTypes",
+		fnc:       IgnoreTypes,
+		args:      args(nil),
+		wantPanic: "cannot determine type",
+		reason:    "input must not be nil value",
+	}, {
+		label:  "IgnoreTypes",
+		fnc:    IgnoreTypes,
+		args:   args(0, 0, 0),
+		reason: "duplicate inputs of the same type is valid",
+	}, {
+		label:     "IgnoreInterfaces",
+		fnc:       IgnoreInterfaces,
+		args:      args(nil),
+		wantPanic: "input must be an anonymous struct",
+		reason:    "input must not be nil value",
+	}, {
+		label:     "IgnoreInterfaces",
+		fnc:       IgnoreInterfaces,
+		args:      args(Foo1{}),
+		wantPanic: "input must be an anonymous struct",
+		reason:    "input must not be a named struct type",
+	}, {
+		label:     "IgnoreInterfaces",
+		fnc:       IgnoreInterfaces,
+		args:      args(struct{ _ io.Reader }{}),
+		wantPanic: "struct cannot have named fields",
+		reason:    "input must not have named fields",
+	}, {
+		label:     "IgnoreInterfaces",
+		fnc:       IgnoreInterfaces,
+		args:      args(struct{ Foo1 }{}),
+		wantPanic: "embedded field must be an interface type",
+		reason:    "field types must be interfaces",
+	}, {
+		label:     "IgnoreInterfaces",
+		fnc:       IgnoreInterfaces,
+		args:      args(struct{ EmptyInterface }{}),
+		wantPanic: "cannot ignore empty interface",
+		reason:    "field types must not be the empty interface",
+	}, {
+		label: "IgnoreInterfaces",
+		fnc:   IgnoreInterfaces,
+		args: args(struct {
+			io.Reader
+			io.Writer
+			io.Closer
+			io.ReadWriteCloser
+		}{}),
+		reason: "multiple interfaces may be specified, even if they overlap",
+	}, {
+		label:  "IgnoreUnexported",
+		fnc:    IgnoreUnexported,
+		reason: "empty input is valid",
+	}, {
+		label:     "IgnoreUnexported",
+		fnc:       IgnoreUnexported,
+		args:      args(nil),
+		wantPanic: "invalid struct type",
+		reason:    "input must not be nil value",
+	}, {
+		label:     "IgnoreUnexported",
+		fnc:       IgnoreUnexported,
+		args:      args(&Foo1{}),
+		wantPanic: "invalid struct type",
+		reason:    "input must be a struct type (not a pointer to a struct)",
+	}, {
+		label:  "IgnoreUnexported",
+		fnc:    IgnoreUnexported,
+		args:   args(Foo1{}, struct{ x, X int }{}),
+		reason: "input may be named or unnamed structs",
+	}}
+
+	for _, tt := range tests {
+		tRun(t, tt.label, func(t *testing.T) {
+			// Prepare function arguments.
+			vf := reflect.ValueOf(tt.fnc)
+			var vargs []reflect.Value
+			for i, arg := range tt.args {
+				if arg == nil {
+					tf := vf.Type()
+					if i == tf.NumIn()-1 && tf.IsVariadic() {
+						vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
+					} else {
+						vargs = append(vargs, reflect.Zero(tf.In(i)))
+					}
+				} else {
+					vargs = append(vargs, reflect.ValueOf(arg))
+				}
+			}
+
+			// Call the function and capture any panics.
+			var gotPanic string
+			func() {
+				defer func() {
+					if ex := recover(); ex != nil {
+						if s, ok := ex.(string); ok {
+							gotPanic = s
+						} else {
+							panic(ex)
+						}
+					}
+				}()
+				vf.Call(vargs)
+			}()
+
+			switch {
+			case tt.wantPanic == "" && gotPanic != "":
+				t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason)
+			case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic):
+				t.Errorf("panic message:\ngot:  %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason)
+			}
+		})
+	}
+}
+
+// TODO: Delete this hack when we drop Go1.6 support.
+func tRun(t *testing.T, name string, f func(t *testing.T)) {
+	type runner interface {
+		Run(string, func(t *testing.T)) bool
+	}
+	var ti interface{} = t
+	if r, ok := ti.(runner); ok {
+		r.Run(name, f)
+	} else {
+		t.Logf("Test: %s", name)
+		f(t)
+	}
+}
diff --git a/cmp/example_test.go b/cmp/example_test.go
index 8de65a1..e341bc6 100644
--- a/cmp/example_test.go
+++ b/cmp/example_test.go
@@ -14,9 +14,15 @@
 	"github.com/google/go-cmp/cmp"
 )
 
+// TODO: Re-write these examples in terms of how you actually use the
+// fundamental options and filters and not in terms of what cool things you can
+// do with them since that overlaps with cmp/cmpopts.
+
 // Approximate equality for floats can be handled by defining a custom
 // comparer on floats that determines two values to be equal if they are within
 // some range of each other.
+//
+// This example is for demonstrative purposes; use cmpopts.EquateApprox instead.
 func ExampleOption_approximateFloats() {
 	// This Comparer only operates on float64.
 	// To handle float32s, either define a similar function for that type
@@ -43,6 +49,8 @@
 
 // Normal floating-point arithmetic defines == to be false when comparing
 // NaN with itself. In certain cases, this is not the desired property.
+//
+// This example is for demonstrative purposes; use cmpopts.EquateNaNs instead.
 func ExampleOption_equalNaNs() {
 	// This Comparer only operates on float64.
 	// To handle float32s, either define a similar function for that type
@@ -68,6 +76,9 @@
 // To have floating-point comparisons combine both properties of NaN being
 // equal to itself and also approximate equality of values, filters are needed
 // to restrict the scope of the comparison so that they are composable.
+//
+// This example is for demonstrative purposes;
+// use cmpopts.EquateNaNs and cmpopts.EquateApprox instead.
 func ExampleOption_equalNaNsAndApproximateFloats() {
 	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
 
@@ -105,6 +116,8 @@
 
 // Sometimes, an empty map or slice is considered equal to an allocated one
 // of zero length.
+//
+// This example is for demonstrative purposes; use cmpopts.EquateEmpty instead.
 func ExampleOption_equalEmpty() {
 	alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
 
@@ -137,6 +150,8 @@
 // Two slices may be considered equal if they have the same elements,
 // regardless of the order that they appear in. Transformations can be used
 // to sort the slice.
+//
+// This example is for demonstrative purposes; use cmpopts.SortSlices instead.
 func ExampleOption_sortedSlice() {
 	// This Transformer sorts a []int.
 	// Since the transformer transforms []int into []int, there is problem where
diff --git a/cmp/options.go b/cmp/options.go
index 0248efb..a01fcb5 100644
--- a/cmp/options.go
+++ b/cmp/options.go
@@ -11,12 +11,15 @@
 	"strings"
 )
 
-// Option configures for specific behavior of Diff and Equal. In particular,
+// Option configures for specific behavior of Equal and Diff. In particular,
 // the fundamental Option functions (Ignore, Transformer, and Comparer),
 // configure how equality is determined.
 //
 // The fundamental options may be composed with filters (FilterPath and
 // FilterValues) to control the scope over which they are applied.
+//
+// The cmp/cmpopts package provides helper functions for creating options that
+// may be used with Equal and Diff.
 type Option interface {
 	// Prevent Option from being equivalent to interface{}, which provides
 	// a small type checking benefit by preventing Equal(opt, x, y).