0.3.0 unit tests: exec, httpjson, and haproxy

This commit is contained in:
Cameron Sparr 2016-01-06 16:11:16 -07:00
parent c4a7711e02
commit 524fddedb4
8 changed files with 221 additions and 377 deletions

View File

@ -51,7 +51,7 @@ func (f *JSONFlattener) FlattenJSON(
}
case float64:
f.Fields[fieldname] = t
case bool, string, []interface{}:
case bool, string, []interface{}, nil:
// ignored types
return nil
default:

View File

@ -1,10 +1,12 @@
package aerospike
import (
"reflect"
"testing"
"github.com/influxdb/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestAerospikeStatistics(t *testing.T) {
@ -60,55 +62,57 @@ func TestReadAerospikeStatsNoNamespace(t *testing.T) {
acc.AssertContainsTaggedFields(t, "aerospike", fields, tags)
}
// func TestReadAerospikeStatsNamespace(t *testing.T) {
// var acc testutil.Accumulator
// stats := map[string]string{
// "stat_write_errs": "12345",
// "stat_read_reqs": "12345",
// }
// readAerospikeStats(stats, &acc, "host1", "test")
func TestReadAerospikeStatsNamespace(t *testing.T) {
var acc testutil.Accumulator
stats := map[string]string{
"stat_write_errs": "12345",
"stat_read_reqs": "12345",
}
readAerospikeStats(stats, &acc, "host1", "test")
// tags := map[string]string{
// "aerospike_host": "host1",
// "namespace": "test",
// }
// for k := range stats {
// assert.True(t, acc.ValidateTaggedValue(k, int64(12345), tags) == nil)
// }
// }
fields := map[string]interface{}{
"stat_write_errs": int64(12345),
"stat_read_reqs": int64(12345),
}
tags := map[string]string{
"aerospike_host": "host1",
"namespace": "test",
}
acc.AssertContainsTaggedFields(t, "aerospike", fields, tags)
}
// func TestAerospikeUnmarshalList(t *testing.T) {
// i := map[string]string{
// "test": "one;two;three",
// }
func TestAerospikeUnmarshalList(t *testing.T) {
i := map[string]string{
"test": "one;two;three",
}
// expected := []string{"one", "two", "three"}
expected := []string{"one", "two", "three"}
// list, err := unmarshalListInfo(i, "test2")
// assert.True(t, err != nil)
list, err := unmarshalListInfo(i, "test2")
assert.True(t, err != nil)
// list, err = unmarshalListInfo(i, "test")
// assert.True(t, err == nil)
// equal := true
// for ix := range expected {
// if list[ix] != expected[ix] {
// equal = false
// break
// }
// }
// assert.True(t, equal)
// }
list, err = unmarshalListInfo(i, "test")
assert.True(t, err == nil)
equal := true
for ix := range expected {
if list[ix] != expected[ix] {
equal = false
break
}
}
assert.True(t, equal)
}
// func TestAerospikeUnmarshalMap(t *testing.T) {
// i := map[string]string{
// "test": "key1=value1;key2=value2",
// }
func TestAerospikeUnmarshalMap(t *testing.T) {
i := map[string]string{
"test": "key1=value1;key2=value2",
}
// expected := map[string]string{
// "key1": "value1",
// "key2": "value2",
// }
// m, err := unmarshalMapInfo(i, "test")
// assert.True(t, err == nil)
// assert.True(t, reflect.DeepEqual(m, expected))
// }
expected := map[string]string{
"key1": "value1",
"key2": "value2",
}
m, err := unmarshalMapInfo(i, "test")
assert.True(t, err == nil)
assert.True(t, reflect.DeepEqual(m, expected))
}

View File

@ -2,12 +2,11 @@ package exec
import (
"fmt"
"testing"
"github.com/influxdb/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"math"
"testing"
"time"
)
// Midnight 9/22/2015
@ -37,10 +36,6 @@ type runnerMock struct {
err error
}
type clockMock struct {
now time.Time
}
func newRunnerMock(out []byte, err error) Runner {
return &runnerMock{
out: out,
@ -48,215 +43,56 @@ func newRunnerMock(out []byte, err error) Runner {
}
}
func (r runnerMock) Run(command *Command) ([]byte, error) {
func (r runnerMock) Run(e *Exec) ([]byte, error) {
if r.err != nil {
return nil, r.err
}
return r.out, nil
}
func newClockMock(now time.Time) Clock {
return &clockMock{now: now}
}
func (c clockMock) Now() time.Time {
return c.now
}
func TestExec(t *testing.T) {
runner := newRunnerMock([]byte(validJson), nil)
clock := newClockMock(time.Unix(baseTimeSeconds+20, 0))
command := Command{
Command: "testcommand arg1",
Name: "mycollector",
Interval: 10,
lastRunAt: time.Unix(baseTimeSeconds, 0),
}
e := &Exec{
runner: runner,
clock: clock,
Commands: []*Command{&command},
runner: newRunnerMock([]byte(validJson), nil),
Command: "testcommand arg1",
Name: "mycollector",
}
var acc testutil.Accumulator
initialPoints := len(acc.Points)
err := e.Gather(&acc)
deltaPoints := len(acc.Points) - initialPoints
require.NoError(t, err)
assert.Equal(t, acc.NFields(), 4, "non-numeric measurements should be ignored")
checkFloat := []struct {
name string
value float64
}{
{"mycollector_num_processes", 82},
{"mycollector_cpu_used", 8234},
{"mycollector_cpu_free", 32},
{"mycollector_percent", 0.81},
fields := map[string]interface{}{
"num_processes": float64(82),
"cpu_used": float64(8234),
"cpu_free": float64(32),
"percent": float64(0.81),
}
for _, c := range checkFloat {
assert.True(t, acc.CheckValue(c.name, c.value))
}
assert.Equal(t, deltaPoints, 4, "non-numeric measurements should be ignored")
acc.AssertContainsFields(t, "exec_mycollector", fields)
}
func TestExecMalformed(t *testing.T) {
runner := newRunnerMock([]byte(malformedJson), nil)
clock := newClockMock(time.Unix(baseTimeSeconds+20, 0))
command := Command{
Command: "badcommand arg1",
Name: "mycollector",
Interval: 10,
lastRunAt: time.Unix(baseTimeSeconds, 0),
}
e := &Exec{
runner: runner,
clock: clock,
Commands: []*Command{&command},
runner: newRunnerMock([]byte(malformedJson), nil),
Command: "badcommand arg1",
Name: "mycollector",
}
var acc testutil.Accumulator
initialPoints := len(acc.Points)
err := e.Gather(&acc)
deltaPoints := len(acc.Points) - initialPoints
require.Error(t, err)
assert.Equal(t, deltaPoints, 0, "No new points should have been added")
assert.Equal(t, acc.NFields(), 0, "No new points should have been added")
}
func TestCommandError(t *testing.T) {
runner := newRunnerMock(nil, fmt.Errorf("exit status code 1"))
clock := newClockMock(time.Unix(baseTimeSeconds+20, 0))
command := Command{
Command: "badcommand",
Name: "mycollector",
Interval: 10,
lastRunAt: time.Unix(baseTimeSeconds, 0),
}
e := &Exec{
runner: runner,
clock: clock,
Commands: []*Command{&command},
runner: newRunnerMock(nil, fmt.Errorf("exit status code 1")),
Command: "badcommand",
Name: "mycollector",
}
var acc testutil.Accumulator
initialPoints := len(acc.Points)
err := e.Gather(&acc)
deltaPoints := len(acc.Points) - initialPoints
require.Error(t, err)
assert.Equal(t, deltaPoints, 0, "No new points should have been added")
}
func TestExecNotEnoughTime(t *testing.T) {
runner := newRunnerMock([]byte(validJson), nil)
clock := newClockMock(time.Unix(baseTimeSeconds+5, 0))
command := Command{
Command: "testcommand arg1",
Name: "mycollector",
Interval: 10,
lastRunAt: time.Unix(baseTimeSeconds, 0),
}
e := &Exec{
runner: runner,
clock: clock,
Commands: []*Command{&command},
}
var acc testutil.Accumulator
initialPoints := len(acc.Points)
err := e.Gather(&acc)
deltaPoints := len(acc.Points) - initialPoints
require.NoError(t, err)
assert.Equal(t, deltaPoints, 0, "No new points should have been added")
}
func TestExecUninitializedLastRunAt(t *testing.T) {
runner := newRunnerMock([]byte(validJson), nil)
clock := newClockMock(time.Unix(baseTimeSeconds, 0))
command := Command{
Command: "testcommand arg1",
Name: "mycollector",
Interval: math.MaxInt32,
// Uninitialized lastRunAt should default to time.Unix(0, 0), so this should
// run no matter what the interval is
}
e := &Exec{
runner: runner,
clock: clock,
Commands: []*Command{&command},
}
var acc testutil.Accumulator
initialPoints := len(acc.Points)
err := e.Gather(&acc)
deltaPoints := len(acc.Points) - initialPoints
require.NoError(t, err)
checkFloat := []struct {
name string
value float64
}{
{"mycollector_num_processes", 82},
{"mycollector_cpu_used", 8234},
{"mycollector_cpu_free", 32},
{"mycollector_percent", 0.81},
}
for _, c := range checkFloat {
assert.True(t, acc.CheckValue(c.name, c.value))
}
assert.Equal(t, deltaPoints, 4, "non-numeric measurements should be ignored")
}
func TestExecOneNotEnoughTimeAndOneEnoughTime(t *testing.T) {
runner := newRunnerMock([]byte(validJson), nil)
clock := newClockMock(time.Unix(baseTimeSeconds+5, 0))
notEnoughTimeCommand := Command{
Command: "testcommand arg1",
Name: "mycollector",
Interval: 10,
lastRunAt: time.Unix(baseTimeSeconds, 0),
}
enoughTimeCommand := Command{
Command: "testcommand arg1",
Name: "mycollector",
Interval: 3,
lastRunAt: time.Unix(baseTimeSeconds, 0),
}
e := &Exec{
runner: runner,
clock: clock,
Commands: []*Command{&notEnoughTimeCommand, &enoughTimeCommand},
}
var acc testutil.Accumulator
initialPoints := len(acc.Points)
err := e.Gather(&acc)
deltaPoints := len(acc.Points) - initialPoints
require.NoError(t, err)
checkFloat := []struct {
name string
value float64
}{
{"mycollector_num_processes", 82},
{"mycollector_cpu_used", 8234},
{"mycollector_cpu_free", 32},
{"mycollector_percent", 0.81},
}
for _, c := range checkFloat {
assert.True(t, acc.CheckValue(c.name, c.value))
}
assert.Equal(t, deltaPoints, 4, "Only one command should have been run")
assert.Equal(t, acc.NFields(), 0, "No new points should have been added")
}

View File

@ -91,7 +91,7 @@ var sampleConfig = `
# If no servers are specified, then default to 127.0.0.1:1936
servers = ["http://myhaproxy.com:1936", "http://anotherhaproxy.com:1936"]
# Or you can also use local socket(not work yet)
# servers = ["socket:/run/haproxy/admin.sock"]
# servers = ["socket://run/haproxy/admin.sock"]
`
func (r *haproxy) SampleConfig() string {

View File

@ -47,52 +47,39 @@ func TestHaproxyGeneratesMetricsWithAuthentication(t *testing.T) {
"sv": "host0",
}
assert.NoError(t, acc.ValidateTaggedValue("stot", uint64(171014), tags))
checkInt := []struct {
name string
value uint64
}{
{"qmax", 81},
{"scur", 288},
{"smax", 713},
{"bin", 5557055817},
{"bout", 24096715169},
{"dreq", 1102},
{"dresp", 80},
{"ereq", 95740},
{"econ", 0},
{"eresp", 0},
{"wretr", 17},
{"wredis", 19},
{"active_servers", 1},
{"backup_servers", 0},
{"downtime", 0},
{"throttle", 13},
{"lbtot", 114},
{"rate", 18},
{"rate_max", 102},
{"check_duration", 1},
{"http_response.1xx", 0},
{"http_response.2xx", 1314093},
{"http_response.3xx", 537036},
{"http_response.4xx", 123452},
{"http_response.5xx", 11966},
{"req_rate", 35},
{"req_rate_max", 140},
{"req_tot", 1987928},
{"cli_abort", 0},
{"srv_abort", 0},
{"qtime", 0},
{"ctime", 2},
{"rtime", 23},
{"ttime", 545},
}
for _, c := range checkInt {
assert.Equal(t, true, acc.CheckValue(c.name, c.value))
fields := map[string]interface{}{
"active_servers": uint64(1),
"backup_servers": uint64(0),
"bin": uint64(510913516),
"bout": uint64(2193856571),
"check_duration": uint64(10),
"cli_abort": uint64(73),
"ctime": uint64(2),
"downtime": uint64(0),
"dresp": uint64(0),
"econ": uint64(0),
"eresp": uint64(1),
"http_response.1xx": uint64(0),
"http_response.2xx": uint64(119534),
"http_response.3xx": uint64(48051),
"http_response.4xx": uint64(2345),
"http_response.5xx": uint64(1056),
"lbtot": uint64(171013),
"qcur": uint64(0),
"qmax": uint64(0),
"qtime": uint64(0),
"rate": uint64(3),
"rate_max": uint64(12),
"rtime": uint64(312),
"scur": uint64(1),
"smax": uint64(32),
"srv_abort": uint64(1),
"stot": uint64(171014),
"ttime": uint64(2341),
"wredis": uint64(0),
"wretr": uint64(1),
}
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
//Here, we should get error because we don't pass authentication data
r = &haproxy{
@ -124,10 +111,39 @@ func TestHaproxyGeneratesMetricsWithoutAuthentication(t *testing.T) {
"sv": "host0",
}
assert.NoError(t, acc.ValidateTaggedValue("stot", uint64(171014), tags))
assert.NoError(t, acc.ValidateTaggedValue("scur", uint64(1), tags))
assert.NoError(t, acc.ValidateTaggedValue("rate", uint64(3), tags))
assert.Equal(t, true, acc.CheckValue("bin", uint64(5557055817)))
fields := map[string]interface{}{
"active_servers": uint64(1),
"backup_servers": uint64(0),
"bin": uint64(510913516),
"bout": uint64(2193856571),
"check_duration": uint64(10),
"cli_abort": uint64(73),
"ctime": uint64(2),
"downtime": uint64(0),
"dresp": uint64(0),
"econ": uint64(0),
"eresp": uint64(1),
"http_response.1xx": uint64(0),
"http_response.2xx": uint64(119534),
"http_response.3xx": uint64(48051),
"http_response.4xx": uint64(2345),
"http_response.5xx": uint64(1056),
"lbtot": uint64(171013),
"qcur": uint64(0),
"qmax": uint64(0),
"qtime": uint64(0),
"rate": uint64(3),
"rate_max": uint64(12),
"rtime": uint64(312),
"scur": uint64(1),
"smax": uint64(32),
"srv_abort": uint64(1),
"stot": uint64(171014),
"ttime": uint64(2341),
"wredis": uint64(0),
"wretr": uint64(1),
}
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
}
//When not passing server config, we default to localhost

View File

@ -153,7 +153,7 @@ func (h *HttpJson) gatherServer(
} else {
msrmnt_name = "httpjson_" + h.Name
}
acc.AddFields(msrmnt_name, f.Fields, nil)
acc.AddFields(msrmnt_name, f.Fields, tags)
return nil
}

View File

@ -1,7 +1,6 @@
package httpjson
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -35,6 +34,11 @@ const validJSONTags = `
"build": "123"
}`
var expectedFields = map[string]interface{}{
"parent_child": float64(3),
"integer": float64(4),
}
const invalidJSON = "I don't think this is JSON"
const empty = ""
@ -76,37 +80,36 @@ func (c mockHTTPClient) MakeRequest(req *http.Request) (*http.Response, error) {
//
// Returns:
// *HttpJson: Pointer to an HttpJson object that uses the generated mock HTTP client
func genMockHttpJson(response string, statusCode int) *HttpJson {
return &HttpJson{
client: mockHTTPClient{responseBody: response, statusCode: statusCode},
Services: []Service{
Service{
Servers: []string{
"http://server1.example.com/metrics/",
"http://server2.example.com/metrics/",
},
Name: "my_webapp",
Method: "GET",
Parameters: map[string]string{
"httpParam1": "12",
"httpParam2": "the second parameter",
},
func genMockHttpJson(response string, statusCode int) []*HttpJson {
return []*HttpJson{
&HttpJson{
client: mockHTTPClient{responseBody: response, statusCode: statusCode},
Servers: []string{
"http://server1.example.com/metrics/",
"http://server2.example.com/metrics/",
},
Service{
Servers: []string{
"http://server3.example.com/metrics/",
"http://server4.example.com/metrics/",
},
Name: "other_webapp",
Method: "POST",
Parameters: map[string]string{
"httpParam1": "12",
"httpParam2": "the second parameter",
},
TagKeys: []string{
"role",
"build",
},
Name: "my_webapp",
Method: "GET",
Parameters: map[string]string{
"httpParam1": "12",
"httpParam2": "the second parameter",
},
},
&HttpJson{
client: mockHTTPClient{responseBody: response, statusCode: statusCode},
Servers: []string{
"http://server3.example.com/metrics/",
"http://server4.example.com/metrics/",
},
Name: "other_webapp",
Method: "POST",
Parameters: map[string]string{
"httpParam1": "12",
"httpParam2": "the second parameter",
},
TagKeys: []string{
"role",
"build",
},
},
}
@ -116,28 +119,15 @@ func genMockHttpJson(response string, statusCode int) *HttpJson {
func TestHttpJson200(t *testing.T) {
httpjson := genMockHttpJson(validJSON, 200)
var acc testutil.Accumulator
err := httpjson.Gather(&acc)
require.NoError(t, err)
assert.Equal(t, 8, len(acc.Points))
for _, service := range httpjson.Services {
for _, service := range httpjson {
var acc testutil.Accumulator
err := service.Gather(&acc)
require.NoError(t, err)
assert.Equal(t, 4, acc.NFields())
for _, srv := range service.Servers {
require.NoError(t,
acc.ValidateTaggedValue(
fmt.Sprintf("%s_parent_child", service.Name),
3.0,
map[string]string{"server": srv},
),
)
require.NoError(t,
acc.ValidateTaggedValue(
fmt.Sprintf("%s_integer", service.Name),
4.0,
map[string]string{"server": srv},
),
)
tags := map[string]string{"server": srv}
mname := "httpjson_" + service.Name
acc.AssertContainsTaggedFields(t, mname, expectedFields, tags)
}
}
}
@ -147,28 +137,22 @@ func TestHttpJson500(t *testing.T) {
httpjson := genMockHttpJson(validJSON, 500)
var acc testutil.Accumulator
err := httpjson.Gather(&acc)
err := httpjson[0].Gather(&acc)
assert.NotNil(t, err)
// 4 error lines for (2 urls) * (2 services)
assert.Equal(t, len(strings.Split(err.Error(), "\n")), 4)
assert.Equal(t, 0, len(acc.Points))
assert.Equal(t, 0, acc.NFields())
}
// Test response to HTTP 405
func TestHttpJsonBadMethod(t *testing.T) {
httpjson := genMockHttpJson(validJSON, 200)
httpjson.Services[0].Method = "NOT_A_REAL_METHOD"
httpjson[0].Method = "NOT_A_REAL_METHOD"
var acc testutil.Accumulator
err := httpjson.Gather(&acc)
err := httpjson[0].Gather(&acc)
assert.NotNil(t, err)
// 2 error lines for (2 urls) * (1 falied service)
assert.Equal(t, len(strings.Split(err.Error(), "\n")), 2)
// (2 measurements) * (2 servers) * (1 successful service)
assert.Equal(t, 4, len(acc.Points))
assert.Equal(t, 0, acc.NFields())
}
// Test response to malformed JSON
@ -176,12 +160,10 @@ func TestHttpJsonBadJson(t *testing.T) {
httpjson := genMockHttpJson(invalidJSON, 200)
var acc testutil.Accumulator
err := httpjson.Gather(&acc)
err := httpjson[0].Gather(&acc)
assert.NotNil(t, err)
// 4 error lines for (2 urls) * (2 services)
assert.Equal(t, len(strings.Split(err.Error(), "\n")), 4)
assert.Equal(t, 0, len(acc.Points))
assert.Equal(t, 0, acc.NFields())
}
// Test response to empty string as response objectgT
@ -189,34 +171,27 @@ func TestHttpJsonEmptyResponse(t *testing.T) {
httpjson := genMockHttpJson(empty, 200)
var acc testutil.Accumulator
err := httpjson.Gather(&acc)
err := httpjson[0].Gather(&acc)
assert.NotNil(t, err)
// 4 error lines for (2 urls) * (2 services)
assert.Equal(t, len(strings.Split(err.Error(), "\n")), 4)
assert.Equal(t, 0, len(acc.Points))
assert.Equal(t, 0, acc.NFields())
}
// Test that the proper values are ignored or collected
func TestHttpJson200Tags(t *testing.T) {
httpjson := genMockHttpJson(validJSONTags, 200)
var acc testutil.Accumulator
err := httpjson.Gather(&acc)
require.NoError(t, err)
assert.Equal(t, 4, len(acc.Points))
for _, service := range httpjson.Services {
for _, service := range httpjson {
if service.Name == "other_webapp" {
var acc testutil.Accumulator
err := service.Gather(&acc)
require.NoError(t, err)
assert.Equal(t, 2, acc.NFields())
for _, srv := range service.Servers {
require.NoError(t,
acc.ValidateTaggedValue(
fmt.Sprintf("%s_value", service.Name),
15.0,
map[string]string{"server": srv, "role": "master", "build": "123"},
),
)
tags := map[string]string{"server": srv, "role": "master", "build": "123"}
fields := map[string]interface{}{"value": float64(15)}
mname := "httpjson_" + service.Name
acc.AssertContainsTaggedFields(t, mname, fields, tags)
}
}
}

View File

@ -1,6 +1,7 @@
package testutil
import (
"encoding/json"
"fmt"
"reflect"
"sync"
@ -25,7 +26,9 @@ func (p *Point) String() string {
// Accumulator defines a mocked out accumulator
type Accumulator struct {
sync.Mutex
Points []*Point
debug bool
}
// Add adds a measurement point to the accumulator
@ -59,6 +62,14 @@ func (a *Accumulator) AddFields(
t = time.Now()
}
if a.debug {
pretty, _ := json.MarshalIndent(fields, "", " ")
prettyTags, _ := json.MarshalIndent(tags, "", " ")
msg := fmt.Sprintf("Adding Measurement [%s]\nFields:%s\nTags:%s\n",
measurement, string(pretty), string(prettyTags))
fmt.Print(msg)
}
p := &Point{
Measurement: measurement,
Fields: fields,
@ -66,10 +77,7 @@ func (a *Accumulator) AddFields(
Time: t,
}
a.Points = append(
a.Points,
p,
)
a.Points = append(a.Points, p)
}
func (a *Accumulator) SetDefaultTags(tags map[string]string) {
@ -91,11 +99,12 @@ func (a *Accumulator) SetPrefix(prefix string) {
func (a *Accumulator) Debug() bool {
// stub for implementing Accumulator interface.
return true
return a.debug
}
func (a *Accumulator) SetDebug(debug bool) {
// stub for implementing Accumulator interface.
a.debug = debug
}
// Get gets the specified measurement point from the accumulator
@ -134,8 +143,10 @@ func (a *Accumulator) AssertContainsTaggedFields(
if p.Measurement == measurement {
if !reflect.DeepEqual(fields, p.Fields) {
msg := fmt.Sprintf("Actual:\n %v (%T) \nExpected:\n %v (%T)",
p.Fields, p.Fields, fields, fields)
pActual, _ := json.MarshalIndent(p.Fields, "", " ")
pExp, _ := json.MarshalIndent(fields, "", " ")
msg := fmt.Sprintf("Actual:\n%s\n(%T) \nExpected:\n%s\n(%T)",
string(pActual), p.Fields, string(pExp), fields)
assert.Fail(t, msg)
}
return
@ -153,8 +164,10 @@ func (a *Accumulator) AssertContainsFields(
for _, p := range a.Points {
if p.Measurement == measurement {
if !reflect.DeepEqual(fields, p.Fields) {
msg := fmt.Sprintf("Actual:\n %v (%T) \nExpected:\n %v (%T)",
p.Fields, p.Fields, fields, fields)
pActual, _ := json.MarshalIndent(p.Fields, "", " ")
pExp, _ := json.MarshalIndent(fields, "", " ")
msg := fmt.Sprintf("Actual:\n%s\n(%T) \nExpected:\n%s\n(%T)",
string(pActual), p.Fields, string(pExp), fields)
assert.Fail(t, msg)
}
return