Skip to content

Commit

Permalink
New helper methods for generating readable loggable strings (hashicor…
Browse files Browse the repository at this point in the history
  • Loading branch information
ncabatoff authored Jun 1, 2023
1 parent e32cf52 commit 65157a6
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
81 changes: 81 additions & 0 deletions sdk/helper/testhelpers/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package testhelpers

import (
"crypto/sha256"
"fmt"
"reflect"

"github.com/mitchellh/go-testing-interface"
"github.com/mitchellh/mapstructure"
)

// ToMap renders an input value of any type as a map. This is intended for
// logging human-readable data dumps in test logs, so it uses the `json`
// tags on struct fields: this makes it easy to exclude `"-"` values that
// are typically not interesting, respect omitempty, etc.
//
// We also replace any []byte fields with a hash of their value.
// This is usually sufficient for test log purposes, and is a lot more readable
// than a big array of individual byte values like Go would normally stringify a
// byte slice.
func ToMap(in any) (map[string]any, error) {
temp := make(map[string]any)
cfg := &mapstructure.DecoderConfig{
TagName: "json",
IgnoreUntaggedFields: true,
Result: &temp,
}
md, err := mapstructure.NewDecoder(cfg)
if err != nil {
return nil, err
}
err = md.Decode(in)
if err != nil {
return nil, err
}

// mapstructure doesn't call the DecodeHook for each field when doing
// struct->map conversions, but it does for map->map, so call it a second
// time to convert each []byte field.
out := make(map[string]any)
md2, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &out,
DecodeHook: func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
if from.Kind() != reflect.Slice || from.Elem().Kind() != reflect.Uint8 {
return data, nil
}
b := data.([]byte)
return fmt.Sprintf("%x", sha256.Sum256(b)), nil
},
})
if err != nil {
return nil, err
}
err = md2.Decode(temp)
if err != nil {
return nil, err
}

return out, nil
}

// ToString renders its input using ToMap, and returns a string containing the
// result or an error if that fails.
func ToString(in any) string {
m, err := ToMap(in)
if err != nil {
return err.Error()
}
return fmt.Sprintf("%v", m)
}

// StringOrDie renders its input using ToMap, and returns a string containing the
// result. If rendering yields an error, calls t.Fatal.
func StringOrDie(t testing.T, in any) string {
t.Helper()
m, err := ToMap(in)
if err != nil {
t.Fatal(err)
}
return fmt.Sprintf("%v", m)
}
45 changes: 45 additions & 0 deletions sdk/helper/testhelpers/output_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package testhelpers

import (
"fmt"
"reflect"
"testing"
)

func TestToMap(t *testing.T) {
type s struct {
A string `json:"a"`
B []byte `json:"b"`
C map[string]string `json:"c"`
D string `json:"-"`
}
type args struct {
in s
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "basic",
args: args{s{A: "a", B: []byte("bytes"), C: map[string]string{"k": "v"}, D: "d"}},
want: "map[a:a b:277089d91c0bdf4f2e6862ba7e4a07605119431f5d13f726dd352b06f1b206a9 c:map[k:v]]",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m, err := ToMap(&tt.args.in)
if (err != nil) != tt.wantErr {
t.Errorf("ToMap() error = %v, wantErr %v", err, tt.wantErr)
return
}
got := fmt.Sprintf("%s", m)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToMap() got = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 65157a6

Please sign in to comment.