Skip to content
Snippets Groups Projects
Commit 34c99693 authored by Thorsten Alteholz's avatar Thorsten Alteholz
Browse files

Import Upstream version 0.0~git20160510.d5467c1

parents
No related branches found
No related tags found
No related merge requests found
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
LICENSE 0 → 100644
Copyright (c) 2015, Rinat Abdullin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.PHONY: test
default: test
test:
go vet && go test
install: test
go install
Readme.md 0 → 100644
Structural equality library for Golang.
## Story
While we were working on [HappyPancake](http://abdullin.com/happypancake/) project, [Pieter](https://twitter.com/pjvds) always wanted to have a better assertion library for our event-driven specifications.
Months later, awesome folks from [@DDDBE](https://twitter.com/dddbe) community presented me with some Trappist Beer. Thanks to it (and some spare time on the weekend), this assertion library was finally written.
## How it works
You can define expectations on objects (e.g. API responses or expected events) by creating an instance of `seq.Map`, which is provided by the package `github.com/abdullin/seq`.
Maps can be nested or they could have flat paths. Values could be represented with strings, primitive types, instances of `seq.Map` or JSON-serializable objects.
Consider following types:
```go
type Robot struct {
Legs int `json:"legs"`
Arms int `json:"arms"`
Name string `json:"name"`
}
type Party struct {
Rating []int `json:"rating"`
Seating map[string]*Robot `json:"seating"`
}
```
Let's imagine that our JSON API returns `Party` object, which we want to verify. We could define our expectation like this:
```go
expect := seq.Map{
// array
"rating.len": 3,
"rating[1]": 5,
// flat path with value terminator
"seating.front.name": "R2D2",
"seating.front.arms": "1",
"seating.front.legs": 3,
// flat path with map terminator
"seating.right": seq.Map{
"name": "C3PO",
},
// flat path with object terminator
"seating.back": &Robot{
Name: "Marvin",
Legs: 2,
Arms: 2,
},
}
```
Once you have the expectation, you could compare it with an actual object. Here is an example of a valid object:
```go
actual := &Party{
Rating: []int{4, 5, 4},
Seating: map[string]*Robot{
"front": &Robot{
Name: "R2D2",
Arms: 1,
Legs: 3,
},
"back": &Robot{
Name: "Marvin",
Legs: 2,
Arms: 2,
},
"right": &Robot{
Name: "C3PO",
Legs: 2,
Arms: 2,
},
},
}
result := expect.Test(actual)
```
Result value would contain `Issues []seq.Issue` with any differences and could be checked like this:
```go
if !result.Ok() {
fmt.Println("Differences")
for _, v := range result.Issues {
fmt.Println(v.String())
}
}
```
If actual object has some invalid or missing properties, then result will have nice error messages. Consider this object:
```go
actual := &Party{
Seating: map[string]*Robot{
"front": &Robot{
Name: "R2D2",
Arms: 1,
},
"back": &Robot{
Name: "Marvin",
Arms: 3,
},
"right": &Robot{
Name: "C4PO",
Legs: 2,
Arms: 3,
},
},
}
```
If verified against the original expectation, `result.Issues` would contain these error messages:
```
Expected rating.len to be '3' but got nothing
Expected seating.back.legs to be '2' but got '0'
Expected seating.front.legs to be '3' but got '0'
Expected rating[1] to be '5' but got nothing
Expected seating.back.arms to be '2' but got '3'
Expected seating.right.name to be 'C3PO' but got 'C4PO'
```
Check out the [unit tests](https://github.com/abdullin/seq/blob/master/seq_test.go) for more examples.
## Feedback
Feedback is welcome and appreciated!
diff.go 0 → 100644
package seq
import "strings"
func hasNestedObject(actual map[string]string, key string) bool {
for k, _ := range actual {
if strings.HasPrefix(k, key) {
return true
}
}
return false
}
func diff(expected, actual map[string]string) *Result {
res := NewResult()
for ek, ev := range expected {
var av, ok = actual[ek]
if !ok {
if hasNestedObject(actual, ek) {
res.AddIssue(ek, ev, "{Object}")
} else {
res.AddIssue(ek, ev, "nothing")
}
} else if av != ev {
res.AddIssue(ek, ev, av)
}
}
return res
}
package seq
import (
"fmt"
"strconv"
"strings"
)
func propertyPath(key string, name string) string {
if key == "" {
return name
}
if strings.HasPrefix(name, "[") {
return key + name
}
return key + "." + name
}
func flatten(key string, x interface{}) map[string]string {
var res = make(map[string]string)
switch vv := x.(type) {
case string:
res[key] = vv
case []interface{}:
res[propertyPath(key, "length")] = strconv.Itoa(len(vv))
for ii, iv := range vv {
var prefix = fmt.Sprintf("%s[%v]", key, ii)
for ivk, ivv := range flatten(prefix, iv) {
res[ivk] = ivv
}
}
case Map:
for ik, iv := range vv {
var prefix = propertyPath(key, ik)
for ivk, ivv := range flatten(prefix, iv) {
res[ivk] = ivv
}
}
case map[string]interface{}:
for ik, iv := range vv {
var prefix = propertyPath(key, ik)
for ivk, ivv := range flatten(prefix, iv) {
res[ivk] = ivv
}
}
default:
res[key] = string(marshal(vv))
}
return res
}
package seq
import (
"encoding/json"
"fmt"
)
func unmarshal(d []byte, i interface{}) {
err := json.Unmarshal(d, i)
if err != nil {
panic(fmt.Sprintf("Failed to unmarshal '%s': %s", string(d), err))
}
}
func marshal(i interface{}) []byte {
b, err := json.Marshal(i)
if err != nil {
panic("Failed to marshal")
}
return b
}
func marshalIndent(i interface{}) []byte {
b, err := json.MarshalIndent(i, "", " ")
if err != nil {
panic("Failed to marshal")
}
return b
}
func isSlice(object interface{}) bool {
t := fmt.Sprintf("%T", object)
return t[0] == '['
}
func objectToMap(object interface{}) interface{} {
b := marshal(object)
if isSlice(object) {
var out []interface{}
unmarshal(b, &out)
return out
} else {
var out map[string]interface{}
unmarshal(b, &out)
return out
}
}
package seq
import "fmt"
type Result struct {
Issues []Issue
}
type Issue struct {
Path string
ExpectedValue string
ActualValue string
}
func (r *Result) Ok() bool {
return len(r.Issues) == 0
}
func (d *Issue) String() string {
return fmt.Sprintf("Expected '%s' to be '%v' but got '%s'",
d.Path,
d.ExpectedValue,
d.ActualValue,
)
}
func NewResult() *Result {
return &Result{}
}
func (r *Result) AddIssue(key, expected, actual string) {
r.Issues = append(r.Issues, Issue{key, expected, actual})
}
seq.go 0 → 100644
package seq
import "fmt"
func Test(expected, actual interface{}) *Result {
eMap := flatten("", objectToMap(expected))
aMap := flatten("", objectToMap(actual))
result := diff(eMap, aMap)
return result
}
type Map map[string]interface{}
func (m Map) Test(actual interface{}) *Result {
return Test(m, actual)
}
func debug(m map[string]string) {
for k, v := range m {
fmt.Println(k, ":", v)
}
}
package seq
import "testing"
type CPU struct {
Brand string `json:"brand"`
Model string `json:"model"`
}
type Robot struct {
Legs int `json:"legs"`
Arms int `json:"arms"`
Name string `json:"name"`
LikesGold bool `json:"likesGold"`
CPU CPU `json:"cpu"`
}
type Party struct {
Rating []int `json:"rating"`
Seating map[string]*Robot `json:"seating"`
}
func TestBool(t *testing.T) {
type dto struct {
Boolean bool `json:"boolean"`
}
result := Test(Map{"boolean": true}, &dto{true})
expectOk(result, t)
}
func TestNestedObject(t *testing.T) {
expect := Map{
"cpu.brand": "intel",
}
actual := Robot{
Legs: 2,
Arms: 2,
CPU: CPU{
Brand: "intel",
},
}
result := expect.Test(actual)
expectOk(result, t)
}
func TestPartialSimpleObject(t *testing.T) {
expect := Map{
"legs": 2,
}
actual := Robot{
Legs: 2,
Arms: 2,
}
result := expect.Test(actual)
expectOk(result, t)
}
func TestExactSimpleObject(t *testing.T) {
expect := Map{
"legs": 2,
"arms": 2,
"name": "benny",
"likesGold": false,
}
actual := Robot{
Legs: 2,
Arms: 2,
Name: "benny",
LikesGold: false,
}
result := expect.Test(actual)
expectOk(result, t)
}
func TestWrongSimpleObject(t *testing.T) {
expect := Map{
"legs": 2,
"arms": 2,
"name": "benny",
}
actual := Robot{
Legs: 2,
Arms: 2,
Name: "Bender",
LikesGold: true,
}
result := expect.Test(actual)
expectFail(result, t)
}
func TestSimpleArray(t *testing.T) {
expect := Map{
"[0]": Map{
"name": "R2D2",
},
"[1]": Map{
"name": "C3PO",
},
}
result := expect.Test([]*Robot{
&Robot{Name: "R2D2"},
&Robot{Name: "C3PO"},
})
expectOk(result, t)
}
func TestIndexSpecifierFolding(t *testing.T) {
type item struct {
Name string `json:"name"`
}
type dto struct {
Items []item `json:"items"`
}
actual := dto{
Items: []item{
item{Name: "this"},
},
}
expect := Map{
"items": Map{
"length": 1,
"[0]": Map{
"name": "this",
},
},
}
result := Test(expect, actual)
expectOk(result, t)
}
func TestExactComplexObject(t *testing.T) {
actual := &Party{
Rating: []int{4, 5, 4},
Seating: map[string]*Robot{
"front": &Robot{
Name: "R2D2",
Arms: 1,
Legs: 3,
},
"back": &Robot{
Name: "Marvin",
Legs: 2,
Arms: 2,
},
"right": &Robot{
Name: "C3PO",
Legs: 2,
Arms: 2,
},
},
}
expect := Map{
// array
"rating.length": 3,
"rating[1]": 5,
// flat path with value terminator
"seating.front.name": "R2D2",
"seating.front.arms": "1",
"seating.front.legs": 3,
// flat path with map terminator
"seating.right": Map{
"name": "C3PO",
},
// flat path with object terminator
"seating.back": &Robot{
Name: "Marvin",
Legs: 2,
Arms: 2,
},
}
result := expect.Test(actual)
expectOk(result, t)
}
func expectOk(result *Result, t *testing.T) {
if result.Ok() {
return
}
t.Error("Test should pass")
for i, l := range result.Issues {
t.Log(i, l)
}
}
func expectFail(result *Result, t *testing.T) {
if !result.Ok() {
return
}
t.Error("Test should fail")
for i, l := range result.Issues {
t.Log(i, l)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment