File: identical_to.go

package info (click to toggle)
golang-github-jacobsa-oglematchers 0.0~git20150320-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 568 kB
  • sloc: makefile: 3
file content (134 lines) | stat: -rw-r--r-- 3,459 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oglematchers

import (
	"errors"
	"fmt"
	"reflect"
)

// Is the type comparable according to the definition here?
//
//     http://weekly.golang.org/doc/go_spec.html#Comparison_operators
//
func isComparable(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Array:
		return isComparable(t.Elem())

	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			if !isComparable(t.Field(i).Type) {
				return false
			}
		}

		return true

	case reflect.Slice, reflect.Map, reflect.Func:
		return false
	}

	return true
}

// Should the supplied type be allowed as an argument to IdenticalTo?
func isLegalForIdenticalTo(t reflect.Type) (bool, error) {
	// Allow the zero type.
	if t == nil {
		return true, nil
	}

	// Reference types are always okay; we compare pointers.
	switch t.Kind() {
	case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
		return true, nil
	}

	// Reject other non-comparable types.
	if !isComparable(t) {
		return false, errors.New(fmt.Sprintf("%v is not comparable", t))
	}

	return true, nil
}

// IdenticalTo(x) returns a matcher that matches values v with type identical
// to x such that:
//
//  1. If v and x are of a reference type (slice, map, function, channel), then
//     they are either both nil or are references to the same object.
//
//  2. Otherwise, if v and x are not of a reference type but have a valid type,
//     then v == x.
//
// If v and x are both the invalid type (which results from the predeclared nil
// value, or from nil interface variables), then the matcher is satisfied.
//
// This function will panic if x is of a value type that is not comparable. For
// example, x cannot be an array of functions.
func IdenticalTo(x interface{}) Matcher {
	t := reflect.TypeOf(x)

	// Reject illegal arguments.
	if ok, err := isLegalForIdenticalTo(t); !ok {
		panic("IdenticalTo: " + err.Error())
	}

	return &identicalToMatcher{x}
}

type identicalToMatcher struct {
	x interface{}
}

func (m *identicalToMatcher) Description() string {
	t := reflect.TypeOf(m.x)
	return fmt.Sprintf("identical to <%v> %v", t, m.x)
}

func (m *identicalToMatcher) Matches(c interface{}) error {
	// Make sure the candidate's type is correct.
	t := reflect.TypeOf(m.x)
	if ct := reflect.TypeOf(c); t != ct {
		return NewFatalError(fmt.Sprintf("which is of type %v", ct))
	}

	// Special case: two values of the invalid type are always identical.
	if t == nil {
		return nil
	}

	// Handle reference types.
	switch t.Kind() {
	case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
		xv := reflect.ValueOf(m.x)
		cv := reflect.ValueOf(c)
		if xv.Pointer() == cv.Pointer() {
			return nil
		}

		return errors.New("which is not an identical reference")
	}

	// Are the values equal?
	if m.x == c {
		return nil
	}

	return errors.New("")
}