...

Source file src/github.com/aws/smithy-go/testing/struct.go

Documentation: github.com/aws/smithy-go/testing

     1  package testing
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"math"
     9  	"reflect"
    10  
    11  	"github.com/aws/smithy-go/document"
    12  	"github.com/aws/smithy-go/middleware"
    13  )
    14  
    15  // CompareValues compares two values to determine if they are equal,
    16  // specialized for comparison of SDK operation output types.
    17  //
    18  // CompareValues expects the two values to be of the same underlying type.
    19  // Doing otherwise will result in undefined behavior.
    20  //
    21  // The third variadic argument is vestigial from a previous implementation that
    22  // depended on go-cmp. Values passed therein have no effect.
    23  func CompareValues(expect, actual interface{}, _ ...interface{}) error {
    24  	return deepEqual(reflect.ValueOf(expect), reflect.ValueOf(actual), "<root>")
    25  }
    26  
    27  func deepEqual(expect, actual reflect.Value, path string) error {
    28  	if et, at := expect.Kind(), actual.Kind(); et != at {
    29  		return fmt.Errorf("%s: kind %s != %s", path, et, at)
    30  	}
    31  
    32  	// there are a handful of short-circuit cases here within the context of
    33  	// operation responses:
    34  	//   - ResultMetadata     (we don't care)
    35  	//   - document.Interface (check for marshaled []byte equality)
    36  	//   - io.Reader          (check for Read() []byte equality)
    37  	ei, ai := expect.Interface(), actual.Interface()
    38  	if _, _, ok := asMetadatas(ei, ai); ok {
    39  		return nil
    40  	}
    41  	if e, a, ok := asDocuments(ei, ai); ok {
    42  		if !compareDocumentTypes(e, a) {
    43  			return fmt.Errorf("%s: document values unequal", path)
    44  		}
    45  		return nil
    46  	}
    47  	if e, a, ok := asReaders(ei, ai); ok {
    48  		if err := CompareReaders(e, a); err != nil {
    49  			return fmt.Errorf("%s: %w", path, err)
    50  		}
    51  		return nil
    52  	}
    53  
    54  	switch expect.Kind() {
    55  	case reflect.Pointer:
    56  		if expect.Type() != actual.Type() {
    57  			return fmt.Errorf("%s: type mismatch", path)
    58  		}
    59  
    60  		expect = deref(expect)
    61  		actual = deref(actual)
    62  		ek, ak := expect.Kind(), actual.Kind()
    63  		if ek == reflect.Invalid || ak == reflect.Invalid {
    64  			// one was a nil pointer, so they both must be nil
    65  			if ek == ak {
    66  				return nil
    67  			}
    68  			return fmt.Errorf("%s: %s != %s", path, fmtNil(ek), fmtNil(ak))
    69  		}
    70  		if err := deepEqual(expect, actual, path); err != nil {
    71  			return err
    72  		}
    73  		return nil
    74  	case reflect.Slice:
    75  		if expect.Len() != actual.Len() {
    76  			return fmt.Errorf("%s: slice length unequal", path)
    77  		}
    78  		for i := 0; i < expect.Len(); i++ {
    79  			ipath := fmt.Sprintf("%s[%d]", path, i)
    80  			if err := deepEqual(expect.Index(i), actual.Index(i), ipath); err != nil {
    81  				return err
    82  			}
    83  		}
    84  		return nil
    85  	case reflect.Map:
    86  		if expect.Len() != actual.Len() {
    87  			return fmt.Errorf("%s: map length unequal", path)
    88  		}
    89  		for _, k := range expect.MapKeys() {
    90  			kpath := fmt.Sprintf("%s[%q]", path, k.String())
    91  			if err := deepEqual(expect.MapIndex(k), actual.MapIndex(k), kpath); err != nil {
    92  				return err
    93  			}
    94  		}
    95  		return nil
    96  	case reflect.Struct:
    97  		for i := 0; i < expect.NumField(); i++ {
    98  			if !expect.Field(i).CanInterface() {
    99  				continue // unexported
   100  			}
   101  			fpath := fmt.Sprintf("%s.%s", path, expect.Type().Field(i).Name)
   102  			if err := deepEqual(expect.Field(i), actual.Field(i), fpath); err != nil {
   103  				return err
   104  			}
   105  		}
   106  		return nil
   107  	case reflect.Float32, reflect.Float64:
   108  		// NaN != NaN by definition but we just care about bitwise equality
   109  		ef, af := math.Float64bits(expect.Float()), math.Float64bits(actual.Float())
   110  		if ef != af {
   111  			return fmt.Errorf("%s: float 0x%x != 0x%x", path, ef, af)
   112  		}
   113  		return nil
   114  	default:
   115  		// everything else is just scalars and can be delegated
   116  		if !reflect.DeepEqual(ei, ai) {
   117  			return fmt.Errorf("%s: %v != %v", path, ei, ai)
   118  		}
   119  		return nil
   120  	}
   121  }
   122  
   123  func asMetadatas(i, j interface{}) (ii, jj middleware.Metadata, ok bool) {
   124  	ii, iok := i.(middleware.Metadata)
   125  	jj, jok := j.(middleware.Metadata)
   126  	return ii, jj, iok || jok
   127  }
   128  
   129  func asDocuments(i, j interface{}) (ii, jj documentInterface, ok bool) {
   130  	ii, iok := i.(documentInterface)
   131  	jj, jok := j.(documentInterface)
   132  	return ii, jj, iok || jok
   133  }
   134  
   135  func asReaders(i, j interface{}) (ii, jj io.Reader, ok bool) {
   136  	ii, iok := i.(io.Reader)
   137  	jj, jok := j.(io.Reader)
   138  	return ii, jj, iok || jok
   139  }
   140  
   141  func deref(v reflect.Value) reflect.Value {
   142  	switch v.Kind() {
   143  	case reflect.Interface, reflect.Ptr:
   144  		for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
   145  			v = v.Elem()
   146  		}
   147  	}
   148  	return v
   149  }
   150  
   151  type documentInterface interface {
   152  	document.Marshaler
   153  	document.Unmarshaler
   154  }
   155  
   156  func compareDocumentTypes(x documentInterface, y documentInterface) bool {
   157  	if x == nil {
   158  		x = nopMarshaler{}
   159  	}
   160  	if y == nil {
   161  		y = nopMarshaler{}
   162  	}
   163  
   164  	xBytes, err := x.MarshalSmithyDocument()
   165  	if err != nil {
   166  		panic(fmt.Sprintf("MarshalSmithyDocument error: %v", err))
   167  	}
   168  	yBytes, err := y.MarshalSmithyDocument()
   169  	if err != nil {
   170  		panic(fmt.Sprintf("MarshalSmithyDocument error: %v", err))
   171  	}
   172  	return JSONEqual(xBytes, yBytes) == nil
   173  }
   174  
   175  // CompareReaders two io.Reader values together to determine if they are equal.
   176  // Will read the contents of the readers until they are empty.
   177  func CompareReaders(expect, actual io.Reader) error {
   178  	if expect == nil {
   179  		expect = nopReader{}
   180  	}
   181  	if actual == nil {
   182  		actual = nopReader{}
   183  	}
   184  
   185  	e, err := io.ReadAll(expect)
   186  	if err != nil {
   187  		return fmt.Errorf("failed to read expect body, %w", err)
   188  	}
   189  
   190  	a, err := io.ReadAll(actual)
   191  	if err != nil {
   192  		return fmt.Errorf("failed to read actual body, %w", err)
   193  	}
   194  
   195  	if !bytes.Equal(e, a) {
   196  		return fmt.Errorf("bytes do not match\nexpect:\n%s\nactual:\n%s",
   197  			hex.Dump(e), hex.Dump(a))
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  func fmtNil(k reflect.Kind) string {
   204  	if k == reflect.Invalid {
   205  		return "nil"
   206  	}
   207  	return "non-nil"
   208  }
   209  
   210  type nopReader struct{}
   211  
   212  func (nopReader) Read(p []byte) (int, error) { return 0, io.EOF }
   213  
   214  type nopMarshaler struct{}
   215  
   216  func (nopMarshaler) MarshalSmithyDocument() ([]byte, error)      { return nil, nil }
   217  func (nopMarshaler) UnmarshalSmithyDocument(v interface{}) error { return nil }
   218  

View as plain text