Skip to content

Commit

Permalink
reflect: add support for DeepEqual
Browse files Browse the repository at this point in the history
The implementation has been mostly copied from the Go reference
implementation with some small changes to fit TinyGo.

Source: https://github.com/golang/go/blob/77a11c05d6a6f766c75f804ea9b8796f9a9f85a3/src/reflect/deepequal.go

In addition, this commit also contains the following:

  - A set of tests copied from the Go reflect package.
  - An increased stack size for the riscv-qemu and hifive1-qemu targets
    (because they otherwise fail to run the tests). Because these
    targets are only used for testing, this seems fine to me.
  • Loading branch information
aykevl authored and deadprogram committed Nov 12, 2021
1 parent 5866a47 commit 335fb71
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 7 deletions.
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,13 @@ TEST_PACKAGES = \
index/suffixarray \
math \
math/cmplx \
net/mail \
reflect \
testing \
testing/iotest \
text/scanner \
text/scanner \
unicode \
unicode/utf16 \
unicode/utf8 \

# Test known-working standard library packages.
Expand Down
188 changes: 188 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright 2009 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 file.

package reflect_test

import (
"math"
. "reflect"
"testing"
)

type Basic struct {
x int
y float32
}

type NotBasic Basic

type DeepEqualTest struct {
a, b interface{}
eq bool
}

// Simple functions for DeepEqual tests.
var (
fn1 func() // nil.
fn2 func() // nil.
fn3 = func() { fn1() } // Not nil.
)

type self struct{}

type Loopy interface{}

var loopy1, loopy2 Loopy
var cycleMap1, cycleMap2, cycleMap3 map[string]interface{}

type structWithSelfPtr struct {
p *structWithSelfPtr
s string
}

func init() {
loopy1 = &loopy2
loopy2 = &loopy1

cycleMap1 = map[string]interface{}{}
cycleMap1["cycle"] = cycleMap1
cycleMap2 = map[string]interface{}{}
cycleMap2["cycle"] = cycleMap2
cycleMap3 = map[string]interface{}{}
cycleMap3["different"] = cycleMap3
}

// Note: all tests involving maps have been commented out because they aren't
// supported yet.
var deepEqualTests = []DeepEqualTest{
// Equalities
{nil, nil, true},
{1, 1, true},
{int32(1), int32(1), true},
{0.5, 0.5, true},
{float32(0.5), float32(0.5), true},
{"hello", "hello", true},
{make([]int, 10), make([]int, 10), true},
{&[3]int{1, 2, 3}, &[3]int{1, 2, 3}, true},
{Basic{1, 0.5}, Basic{1, 0.5}, true},
{error(nil), error(nil), true},
//{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
{fn1, fn2, true},
{[]byte{1, 2, 3}, []byte{1, 2, 3}, true},
{[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true},
{MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true},

// Inequalities
{1, 2, false},
{int32(1), int32(2), false},
{0.5, 0.6, false},
{float32(0.5), float32(0.6), false},
{"hello", "hey", false},
{make([]int, 10), make([]int, 11), false},
{&[3]int{1, 2, 3}, &[3]int{1, 2, 4}, false},
{Basic{1, 0.5}, Basic{1, 0.6}, false},
{Basic{1, 0}, Basic{2, 0}, false},
//{map[int]string{1: "one", 3: "two"}, map[int]string{2: "two", 1: "one"}, false},
//{map[int]string{1: "one", 2: "txo"}, map[int]string{2: "two", 1: "one"}, false},
//{map[int]string{1: "one"}, map[int]string{2: "two", 1: "one"}, false},
//{map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false},
{nil, 1, false},
{1, nil, false},
{fn1, fn3, false},
{fn3, fn3, false},
{[][]int{{1}}, [][]int{{2}}, false},
{&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false},

// Fun with floating point.
{math.NaN(), math.NaN(), false},
{&[1]float64{math.NaN()}, &[1]float64{math.NaN()}, false},
{&[1]float64{math.NaN()}, self{}, true},
{[]float64{math.NaN()}, []float64{math.NaN()}, false},
{[]float64{math.NaN()}, self{}, true},
//{map[float64]float64{math.NaN(): 1}, map[float64]float64{1: 2}, false},
//{map[float64]float64{math.NaN(): 1}, self{}, true},

// Nil vs empty: not the same.
{[]int{}, []int(nil), false},
{[]int{}, []int{}, true},
{[]int(nil), []int(nil), true},
//{map[int]int{}, map[int]int(nil), false},
//{map[int]int{}, map[int]int{}, true},
//{map[int]int(nil), map[int]int(nil), true},

// Mismatched types
{1, 1.0, false},
{int32(1), int64(1), false},
{0.5, "hello", false},
{[]int{1, 2, 3}, [3]int{1, 2, 3}, false},
{&[3]interface{}{1, 2, 4}, &[3]interface{}{1, 2, "s"}, false},
{Basic{1, 0.5}, NotBasic{1, 0.5}, false},
{map[uint]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, false},
{[]byte{1, 2, 3}, []MyByte{1, 2, 3}, false},
{[]MyByte{1, 2, 3}, MyBytes{1, 2, 3}, false},
{[]byte{1, 2, 3}, MyBytes{1, 2, 3}, false},

// Possible loops.
{&loopy1, &loopy1, true},
{&loopy1, &loopy2, true},
//{&cycleMap1, &cycleMap2, true},
{&cycleMap1, &cycleMap3, false},
}

func TestDeepEqual(t *testing.T) {
for _, test := range deepEqualTests {
if test.b == (self{}) {
test.b = test.a
}
if r := DeepEqual(test.a, test.b); r != test.eq {
t.Errorf("DeepEqual(%#v, %#v) = %v, want %v", test.a, test.b, r, test.eq)
}
}
}

type Recursive struct {
x int
r *Recursive
}

func TestDeepEqualRecursiveStruct(t *testing.T) {
a, b := new(Recursive), new(Recursive)
*a = Recursive{12, a}
*b = Recursive{12, b}
if !DeepEqual(a, b) {
t.Error("DeepEqual(recursive same) = false, want true")
}
}

type _Complex struct {
a int
b [3]*_Complex
c *string
d map[float64]float64
}

func TestDeepEqualComplexStruct(t *testing.T) {
m := make(map[float64]float64)
stra, strb := "hello", "hello"
a, b := new(_Complex), new(_Complex)
*a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m}
*b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m}
if !DeepEqual(a, b) {
t.Error("DeepEqual(complex same) = false, want true")
}
}

func TestDeepEqualComplexStructInequality(t *testing.T) {
m := make(map[float64]float64)
stra, strb := "hello", "helloo" // Difference is here
a, b := new(_Complex), new(_Complex)
*a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m}
*b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m}
if DeepEqual(a, b) {
t.Error("DeepEqual(complex different) = true, want false")
}
}

type MyBytes []byte
type MyByte byte
Loading

0 comments on commit 335fb71

Please sign in to comment.