How to test:
$ docker ps --format '{{json .}}'
$ docker network ls --format '{{json .}}'
$ docker volume ls --format '{{json .}}'
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
| ... | ... |
@@ -2,6 +2,7 @@ package formatter |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "encoding/json" |
|
| 5 | 6 |
"fmt" |
| 6 | 7 |
"strings" |
| 7 | 8 |
"testing" |
| ... | ... |
@@ -323,3 +324,49 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
| 323 | 323 |
out.Reset() |
| 324 | 324 |
} |
| 325 | 325 |
} |
| 326 |
+ |
|
| 327 |
+func TestContainerContextWriteJSON(t *testing.T) {
|
|
| 328 |
+ unix := time.Now().Add(-65 * time.Second).Unix() |
|
| 329 |
+ containers := []types.Container{
|
|
| 330 |
+ {ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unix},
|
|
| 331 |
+ {ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unix},
|
|
| 332 |
+ } |
|
| 333 |
+ expectedCreated := time.Unix(unix, 0).String() |
|
| 334 |
+ expectedJSONs := []map[string]interface{}{
|
|
| 335 |
+ {"Command": "\"\"", "CreatedAt": expectedCreated, "ID": "containerID1", "Image": "ubuntu", "Labels": "", "LocalVolumes": "0", "Mounts": "", "Names": "foobar_baz", "Ports": "", "RunningFor": "About a minute", "Size": "0 B", "Status": ""},
|
|
| 336 |
+ {"Command": "\"\"", "CreatedAt": expectedCreated, "ID": "containerID2", "Image": "ubuntu", "Labels": "", "LocalVolumes": "0", "Mounts": "", "Names": "foobar_bar", "Ports": "", "RunningFor": "About a minute", "Size": "0 B", "Status": ""},
|
|
| 337 |
+ } |
|
| 338 |
+ out := bytes.NewBufferString("")
|
|
| 339 |
+ err := ContainerWrite(Context{Format: "{{json .}}", Output: out}, containers)
|
|
| 340 |
+ if err != nil {
|
|
| 341 |
+ t.Fatal(err) |
|
| 342 |
+ } |
|
| 343 |
+ for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
|
| 344 |
+ t.Logf("Output: line %d: %s", i, line)
|
|
| 345 |
+ var m map[string]interface{}
|
|
| 346 |
+ if err := json.Unmarshal([]byte(line), &m); err != nil {
|
|
| 347 |
+ t.Fatal(err) |
|
| 348 |
+ } |
|
| 349 |
+ assert.DeepEqual(t, m, expectedJSONs[i]) |
|
| 350 |
+ } |
|
| 351 |
+} |
|
| 352 |
+ |
|
| 353 |
+func TestContainerContextWriteJSONField(t *testing.T) {
|
|
| 354 |
+ containers := []types.Container{
|
|
| 355 |
+ {ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu"},
|
|
| 356 |
+ {ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu"},
|
|
| 357 |
+ } |
|
| 358 |
+ out := bytes.NewBufferString("")
|
|
| 359 |
+ err := ContainerWrite(Context{Format: "{{json .ID}}", Output: out}, containers)
|
|
| 360 |
+ if err != nil {
|
|
| 361 |
+ t.Fatal(err) |
|
| 362 |
+ } |
|
| 363 |
+ for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
|
| 364 |
+ t.Logf("Output: line %d: %s", i, line)
|
|
| 365 |
+ var s string |
|
| 366 |
+ if err := json.Unmarshal([]byte(line), &s); err != nil {
|
|
| 367 |
+ t.Fatal(err) |
|
| 368 |
+ } |
|
| 369 |
+ assert.Equal(t, s, containers[i].ID) |
|
| 370 |
+ } |
|
| 371 |
+} |
| ... | ... |
@@ -2,6 +2,7 @@ package formatter |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "encoding/json" |
|
| 5 | 6 |
"strings" |
| 6 | 7 |
"testing" |
| 7 | 8 |
|
| ... | ... |
@@ -160,3 +161,48 @@ foobar_bar |
| 160 | 160 |
} |
| 161 | 161 |
} |
| 162 | 162 |
} |
| 163 |
+ |
|
| 164 |
+func TestNetworkContextWriteJSON(t *testing.T) {
|
|
| 165 |
+ networks := []types.NetworkResource{
|
|
| 166 |
+ {ID: "networkID1", Name: "foobar_baz"},
|
|
| 167 |
+ {ID: "networkID2", Name: "foobar_bar"},
|
|
| 168 |
+ } |
|
| 169 |
+ expectedJSONs := []map[string]interface{}{
|
|
| 170 |
+ {"Driver": "", "ID": "networkID1", "IPv6": "false", "Internal": "false", "Labels": "", "Name": "foobar_baz", "Scope": ""},
|
|
| 171 |
+ {"Driver": "", "ID": "networkID2", "IPv6": "false", "Internal": "false", "Labels": "", "Name": "foobar_bar", "Scope": ""},
|
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ out := bytes.NewBufferString("")
|
|
| 175 |
+ err := NetworkWrite(Context{Format: "{{json .}}", Output: out}, networks)
|
|
| 176 |
+ if err != nil {
|
|
| 177 |
+ t.Fatal(err) |
|
| 178 |
+ } |
|
| 179 |
+ for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
|
| 180 |
+ t.Logf("Output: line %d: %s", i, line)
|
|
| 181 |
+ var m map[string]interface{}
|
|
| 182 |
+ if err := json.Unmarshal([]byte(line), &m); err != nil {
|
|
| 183 |
+ t.Fatal(err) |
|
| 184 |
+ } |
|
| 185 |
+ assert.DeepEqual(t, m, expectedJSONs[i]) |
|
| 186 |
+ } |
|
| 187 |
+} |
|
| 188 |
+ |
|
| 189 |
+func TestNetworkContextWriteJSONField(t *testing.T) {
|
|
| 190 |
+ networks := []types.NetworkResource{
|
|
| 191 |
+ {ID: "networkID1", Name: "foobar_baz"},
|
|
| 192 |
+ {ID: "networkID2", Name: "foobar_bar"},
|
|
| 193 |
+ } |
|
| 194 |
+ out := bytes.NewBufferString("")
|
|
| 195 |
+ err := NetworkWrite(Context{Format: "{{json .ID}}", Output: out}, networks)
|
|
| 196 |
+ if err != nil {
|
|
| 197 |
+ t.Fatal(err) |
|
| 198 |
+ } |
|
| 199 |
+ for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
|
| 200 |
+ t.Logf("Output: line %d: %s", i, line)
|
|
| 201 |
+ var s string |
|
| 202 |
+ if err := json.Unmarshal([]byte(line), &s); err != nil {
|
|
| 203 |
+ t.Fatal(err) |
|
| 204 |
+ } |
|
| 205 |
+ assert.Equal(t, s, networks[i].ID) |
|
| 206 |
+ } |
|
| 207 |
+} |
| 163 | 208 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,65 @@ |
| 0 |
+package formatter |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "reflect" |
|
| 6 |
+ "unicode" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func marshalJSON(x interface{}) ([]byte, error) {
|
|
| 10 |
+ m, err := marshalMap(x) |
|
| 11 |
+ if err != nil {
|
|
| 12 |
+ return nil, err |
|
| 13 |
+ } |
|
| 14 |
+ return json.Marshal(m) |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// marshalMap marshals x to map[string]interface{}
|
|
| 18 |
+func marshalMap(x interface{}) (map[string]interface{}, error) {
|
|
| 19 |
+ val := reflect.ValueOf(x) |
|
| 20 |
+ if val.Kind() != reflect.Ptr {
|
|
| 21 |
+ return nil, fmt.Errorf("expected a pointer to a struct, got %v", val.Kind())
|
|
| 22 |
+ } |
|
| 23 |
+ if val.IsNil() {
|
|
| 24 |
+ return nil, fmt.Errorf("expxected a pointer to a struct, got nil pointer")
|
|
| 25 |
+ } |
|
| 26 |
+ valElem := val.Elem() |
|
| 27 |
+ if valElem.Kind() != reflect.Struct {
|
|
| 28 |
+ return nil, fmt.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
|
|
| 29 |
+ } |
|
| 30 |
+ typ := val.Type() |
|
| 31 |
+ m := make(map[string]interface{})
|
|
| 32 |
+ for i := 0; i < val.NumMethod(); i++ {
|
|
| 33 |
+ k, v, err := marshalForMethod(typ.Method(i), val.Method(i)) |
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ return nil, err |
|
| 36 |
+ } |
|
| 37 |
+ if k != "" {
|
|
| 38 |
+ m[k] = v |
|
| 39 |
+ } |
|
| 40 |
+ } |
|
| 41 |
+ return m, nil |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+var unmarshallableNames = map[string]struct{}{"FullHeader": {}}
|
|
| 45 |
+ |
|
| 46 |
+// marshalForMethod returns the map key and the map value for marshalling the method. |
|
| 47 |
+// It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()")
|
|
| 48 |
+func marshalForMethod(typ reflect.Method, val reflect.Value) (string, interface{}, error) {
|
|
| 49 |
+ if val.Kind() != reflect.Func {
|
|
| 50 |
+ return "", nil, fmt.Errorf("expected func, got %v", val.Kind())
|
|
| 51 |
+ } |
|
| 52 |
+ name, numIn, numOut := typ.Name, val.Type().NumIn(), val.Type().NumOut() |
|
| 53 |
+ _, blackListed := unmarshallableNames[name] |
|
| 54 |
+ // FIXME: In text/template, (numOut == 2) is marshallable, |
|
| 55 |
+ // if the type of the second param is error. |
|
| 56 |
+ marshallable := unicode.IsUpper(rune(name[0])) && !blackListed && |
|
| 57 |
+ numIn == 0 && numOut == 1 |
|
| 58 |
+ if !marshallable {
|
|
| 59 |
+ return "", nil, nil |
|
| 60 |
+ } |
|
| 61 |
+ result := val.Call(make([]reflect.Value, numIn)) |
|
| 62 |
+ intf := result[0].Interface() |
|
| 63 |
+ return name, intf, nil |
|
| 64 |
+} |
| 0 | 65 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,66 @@ |
| 0 |
+package formatter |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+type dummy struct {
|
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+func (d *dummy) Func1() string {
|
|
| 11 |
+ return "Func1" |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+func (d *dummy) func2() string {
|
|
| 15 |
+ return "func2(should not be marshalled)" |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func (d *dummy) Func3() (string, int) {
|
|
| 19 |
+ return "Func3(should not be marshalled)", -42 |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func (d *dummy) Func4() int {
|
|
| 23 |
+ return 4 |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+type dummyType string |
|
| 27 |
+ |
|
| 28 |
+func (d *dummy) Func5() dummyType {
|
|
| 29 |
+ return dummyType("Func5")
|
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (d *dummy) FullHeader() string {
|
|
| 33 |
+ return "FullHeader(should not be marshalled)" |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+var dummyExpected = map[string]interface{}{
|
|
| 37 |
+ "Func1": "Func1", |
|
| 38 |
+ "Func4": 4, |
|
| 39 |
+ "Func5": dummyType("Func5"),
|
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+func TestMarshalMap(t *testing.T) {
|
|
| 43 |
+ d := dummy{}
|
|
| 44 |
+ m, err := marshalMap(&d) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ t.Fatal(err) |
|
| 47 |
+ } |
|
| 48 |
+ if !reflect.DeepEqual(dummyExpected, m) {
|
|
| 49 |
+ t.Fatalf("expected %+v, got %+v",
|
|
| 50 |
+ dummyExpected, m) |
|
| 51 |
+ } |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+func TestMarshalMapBad(t *testing.T) {
|
|
| 55 |
+ if _, err := marshalMap(nil); err == nil {
|
|
| 56 |
+ t.Fatal("expected an error (argument is nil)")
|
|
| 57 |
+ } |
|
| 58 |
+ if _, err := marshalMap(dummy{}); err == nil {
|
|
| 59 |
+ t.Fatal("expected an error (argument is non-pointer)")
|
|
| 60 |
+ } |
|
| 61 |
+ x := 42 |
|
| 62 |
+ if _, err := marshalMap(&x); err == nil {
|
|
| 63 |
+ t.Fatal("expected an error (argument is a pointer to non-struct)")
|
|
| 64 |
+ } |
|
| 65 |
+} |
| ... | ... |
@@ -139,6 +139,10 @@ type serviceInspectContext struct {
|
| 139 | 139 |
subContext |
| 140 | 140 |
} |
| 141 | 141 |
|
| 142 |
+func (ctx *serviceInspectContext) MarshalJSON() ([]byte, error) {
|
|
| 143 |
+ return marshalJSON(ctx) |
|
| 144 |
+} |
|
| 145 |
+ |
|
| 142 | 146 |
func (ctx *serviceInspectContext) ID() string {
|
| 143 | 147 |
return ctx.Service.ID |
| 144 | 148 |
} |
| ... | ... |
@@ -2,6 +2,7 @@ package formatter |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "encoding/json" |
|
| 5 | 6 |
"strings" |
| 6 | 7 |
"testing" |
| 7 | 8 |
|
| ... | ... |
@@ -142,3 +143,47 @@ foobar_bar |
| 142 | 142 |
} |
| 143 | 143 |
} |
| 144 | 144 |
} |
| 145 |
+ |
|
| 146 |
+func TestVolumeContextWriteJSON(t *testing.T) {
|
|
| 147 |
+ volumes := []*types.Volume{
|
|
| 148 |
+ {Driver: "foo", Name: "foobar_baz"},
|
|
| 149 |
+ {Driver: "bar", Name: "foobar_bar"},
|
|
| 150 |
+ } |
|
| 151 |
+ expectedJSONs := []map[string]interface{}{
|
|
| 152 |
+ {"Driver": "foo", "Labels": "", "Links": "N/A", "Mountpoint": "", "Name": "foobar_baz", "Scope": "", "Size": "N/A"},
|
|
| 153 |
+ {"Driver": "bar", "Labels": "", "Links": "N/A", "Mountpoint": "", "Name": "foobar_bar", "Scope": "", "Size": "N/A"},
|
|
| 154 |
+ } |
|
| 155 |
+ out := bytes.NewBufferString("")
|
|
| 156 |
+ err := VolumeWrite(Context{Format: "{{json .}}", Output: out}, volumes)
|
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ t.Fatal(err) |
|
| 159 |
+ } |
|
| 160 |
+ for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
|
| 161 |
+ t.Logf("Output: line %d: %s", i, line)
|
|
| 162 |
+ var m map[string]interface{}
|
|
| 163 |
+ if err := json.Unmarshal([]byte(line), &m); err != nil {
|
|
| 164 |
+ t.Fatal(err) |
|
| 165 |
+ } |
|
| 166 |
+ assert.DeepEqual(t, m, expectedJSONs[i]) |
|
| 167 |
+ } |
|
| 168 |
+} |
|
| 169 |
+ |
|
| 170 |
+func TestVolumeContextWriteJSONField(t *testing.T) {
|
|
| 171 |
+ volumes := []*types.Volume{
|
|
| 172 |
+ {Driver: "foo", Name: "foobar_baz"},
|
|
| 173 |
+ {Driver: "bar", Name: "foobar_bar"},
|
|
| 174 |
+ } |
|
| 175 |
+ out := bytes.NewBufferString("")
|
|
| 176 |
+ err := VolumeWrite(Context{Format: "{{json .Name}}", Output: out}, volumes)
|
|
| 177 |
+ if err != nil {
|
|
| 178 |
+ t.Fatal(err) |
|
| 179 |
+ } |
|
| 180 |
+ for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
|
| 181 |
+ t.Logf("Output: line %d: %s", i, line)
|
|
| 182 |
+ var s string |
|
| 183 |
+ if err := json.Unmarshal([]byte(line), &s); err != nil {
|
|
| 184 |
+ t.Fatal(err) |
|
| 185 |
+ } |
|
| 186 |
+ assert.Equal(t, s, volumes[i].Name) |
|
| 187 |
+ } |
|
| 188 |
+} |
| ... | ... |
@@ -2,15 +2,17 @@ package service |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "encoding/json" |
|
| 5 | 6 |
"strings" |
| 6 | 7 |
"testing" |
| 7 | 8 |
"time" |
| 8 | 9 |
|
| 9 | 10 |
"github.com/docker/docker/api/types/swarm" |
| 10 | 11 |
"github.com/docker/docker/cli/command/formatter" |
| 12 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 11 | 13 |
) |
| 12 | 14 |
|
| 13 |
-func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
|
|
| 15 |
+func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) string {
|
|
| 14 | 16 |
b := new(bytes.Buffer) |
| 15 | 17 |
|
| 16 | 18 |
endpointSpec := &swarm.EndpointSpec{
|
| ... | ... |
@@ -29,8 +31,8 @@ func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
|
| 29 | 29 |
ID: "de179gar9d0o7ltdybungplod", |
| 30 | 30 |
Meta: swarm.Meta{
|
| 31 | 31 |
Version: swarm.Version{Index: 315},
|
| 32 |
- CreatedAt: time.Now(), |
|
| 33 |
- UpdatedAt: time.Now(), |
|
| 32 |
+ CreatedAt: now, |
|
| 33 |
+ UpdatedAt: now, |
|
| 34 | 34 |
}, |
| 35 | 35 |
Spec: swarm.ServiceSpec{
|
| 36 | 36 |
Annotations: swarm.Annotations{
|
| ... | ... |
@@ -73,14 +75,14 @@ func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
|
| 73 | 73 |
}, |
| 74 | 74 |
}, |
| 75 | 75 |
UpdateStatus: swarm.UpdateStatus{
|
| 76 |
- StartedAt: time.Now(), |
|
| 77 |
- CompletedAt: time.Now(), |
|
| 76 |
+ StartedAt: now, |
|
| 77 |
+ CompletedAt: now, |
|
| 78 | 78 |
}, |
| 79 | 79 |
} |
| 80 | 80 |
|
| 81 | 81 |
ctx := formatter.Context{
|
| 82 | 82 |
Output: b, |
| 83 |
- Format: formatter.NewServiceFormat("pretty"),
|
|
| 83 |
+ Format: format, |
|
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 | 86 |
err := formatter.ServiceInspectWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, func(ref string) (interface{}, []byte, error) {
|
| ... | ... |
@@ -89,8 +91,39 @@ func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
|
| 89 | 89 |
if err != nil {
|
| 90 | 90 |
t.Fatal(err) |
| 91 | 91 |
} |
| 92 |
+ return b.String() |
|
| 93 |
+} |
|
| 92 | 94 |
|
| 93 |
- if strings.Contains(b.String(), "UpdateStatus") {
|
|
| 95 |
+func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
|
|
| 96 |
+ s := formatServiceInspect(t, formatter.NewServiceFormat("pretty"), time.Now())
|
|
| 97 |
+ if strings.Contains(s, "UpdateStatus") {
|
|
| 94 | 98 |
t.Fatal("Pretty print failed before parsing UpdateStatus")
|
| 95 | 99 |
} |
| 96 | 100 |
} |
| 101 |
+ |
|
| 102 |
+func TestJSONFormatWithNoUpdateConfig(t *testing.T) {
|
|
| 103 |
+ now := time.Now() |
|
| 104 |
+ // s1: [{"ID":..}]
|
|
| 105 |
+ // s2: {"ID":..}
|
|
| 106 |
+ s1 := formatServiceInspect(t, formatter.NewServiceFormat(""), now)
|
|
| 107 |
+ t.Log("// s1")
|
|
| 108 |
+ t.Logf("%s", s1)
|
|
| 109 |
+ s2 := formatServiceInspect(t, formatter.NewServiceFormat("{{json .}}"), now)
|
|
| 110 |
+ t.Log("// s2")
|
|
| 111 |
+ t.Logf("%s", s2)
|
|
| 112 |
+ var m1Wrap []map[string]interface{}
|
|
| 113 |
+ if err := json.Unmarshal([]byte(s1), &m1Wrap); err != nil {
|
|
| 114 |
+ t.Fatal(err) |
|
| 115 |
+ } |
|
| 116 |
+ if len(m1Wrap) != 1 {
|
|
| 117 |
+ t.Fatalf("strange s1=%s", s1)
|
|
| 118 |
+ } |
|
| 119 |
+ m1 := m1Wrap[0] |
|
| 120 |
+ t.Logf("m1=%+v", m1)
|
|
| 121 |
+ var m2 map[string]interface{}
|
|
| 122 |
+ if err := json.Unmarshal([]byte(s2), &m2); err != nil {
|
|
| 123 |
+ t.Fatal(err) |
|
| 124 |
+ } |
|
| 125 |
+ t.Logf("m2=%+v", m2)
|
|
| 126 |
+ assert.DeepEqual(t, m2, m1) |
|
| 127 |
+} |
| ... | ... |
@@ -4,6 +4,7 @@ package assert |
| 4 | 4 |
import ( |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"path/filepath" |
| 7 |
+ "reflect" |
|
| 7 | 8 |
"runtime" |
| 8 | 9 |
"strings" |
| 9 | 10 |
) |
| ... | ... |
@@ -44,6 +45,14 @@ func NilError(t TestingT, err error) {
|
| 44 | 44 |
} |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
+// DeepEqual compare the actual value to the expected value and fails the test if |
|
| 48 |
+// they are not "deeply equal". |
|
| 49 |
+func DeepEqual(t TestingT, actual, expected interface{}) {
|
|
| 50 |
+ if !reflect.DeepEqual(actual, expected) {
|
|
| 51 |
+ fatal(t, "Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual) |
|
| 52 |
+ } |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 47 | 55 |
// Error asserts that error is not nil, and contains the expected text, |
| 48 | 56 |
// otherwise it fails the test. |
| 49 | 57 |
func Error(t TestingT, err error, contains string) {
|