technology from back to front

Some reflective testing with gocheck

Last time
I wrote about custom gocheck checkers and wrote a checker that checked if a
slice of int contained a specific int – it would be nice to have a generic
contains checker and using the go reflection we can write one.

Our earlier checker was built upon this function:

func contains(slice []int, value int) bool {
  for _, v := range slice {
    if v == value {
      return true
    }
  }
  return false
}

Go doesn't have generics so we have to use reflection to build a contains
function that will work for a slice of any type. In go you define
"any" type using interface{}, the empty interface.
So we would like to build a function with this signature:

func contains(slice, value interface{}) bool

If we have a slice of int and we check if it contains a type that isn't int
that should return false. We will write a small function to determine if a
slice or an array contains a specific type, this will be the first place we
use the reflect package to examine types. Here is the code:

func containsType(slice, value interface{}) bool {
  switch v := reflect.ValueOf(slice); v.Kind() {
  case reflect.Slice, reflect.Array:
    return v.Type().Elem() == reflect.TypeOf(value)
  }
  return false
}

This function takes a slice and a value both of type interface{}
so they can hold any type. The switch and case statement check if the parameter
advertised as a slice is actually a slice, or an array. If that is true it
compares the type held by the slice with the type of the value. The real work
here is carried out by the reflect.Value struct returned by the reflect.ValueOf
function. The reflect.Value struct contains parameters which describe any go
type. In this function, Kind() allows us to determine that we have been given
an array or a struct, and Type().Elem() allows us to determine what type the
slice contains.

Now we can write the contains function like this:

func contains(slice, value interface{}) bool {
  if containsType(slice, value) {
    switch c := reflect.ValueOf(slice); c.Kind() {
    case reflect.Slice, reflect.Array:
      for i := 0; i < c.Len(); i++ {
        if reflect.DeepEqual(c.Index(i).Interface(), value) {
          return true
        }
      }
    }
  }

  return false
}

This uses the containsType function and is structured in a similar fashion.
The main magic is the reflect.DeepEqual function, this does a recursive equals
check with reflection across arbitary structures.

We can now replace the contains function in last months code and write some
tests:

func (s *S) TestContains(c *C) {
  a := []int{2, 3, 4}
  c.Check(a, Contains, a[0])
  c.Check(a, Contains, a[1])
  c.Check(a, Contains, a[2])
  c.Check(a, Contains, 2)
  c.Check(a, Contains, 3)
  c.Check(a, Contains, 4)
  c.Check(a, Not(Contains), 5)
  c.Check(a, Not(Contains), a)
  c.Check(a, Not(Contains), "x")
}

by
tim
on
30/09/11
 
 


seven + 8 =

2000-14 LShift Ltd, 1st Floor, Hoxton Point, 6 Rufus Street, London, N1 6PE, UK+44 (0)20 7729 7060   Contact us