github.com/algorand/go-algorand-sdk@v1.24.0/test/utilities.go (about)

     1  package test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"sort"
    14  	"strings"
    15  
    16  	sdk_json "github.com/algorand/go-algorand-sdk/encoding/json"
    17  	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
    18  )
    19  
    20  func VerifyResponse(expectedFile string, actual string) error {
    21  	jsonfile, err := os.Open(expectedFile)
    22  	if err != nil {
    23  		return err
    24  	}
    25  	fileBytes, err := ioutil.ReadAll(jsonfile)
    26  	if err != nil {
    27  		return err
    28  	}
    29  
    30  	var expectedString string
    31  	// No processing needed for json
    32  	if strings.HasSuffix(expectedFile, ".json") {
    33  		expectedString = string(fileBytes)
    34  	}
    35  	// Convert message pack to json for comparison
    36  	if strings.HasSuffix(expectedFile, ".base64") {
    37  		data, err := base64.StdEncoding.DecodeString(string(fileBytes))
    38  		if err != nil {
    39  			return fmt.Errorf("failed to decode '%s' from base64: %v", expectedFile, err)
    40  		}
    41  		generic := make(map[string]interface{})
    42  		err = msgpack.Decode(data, generic)
    43  		if err != nil {
    44  			return fmt.Errorf("failed to decode '%s' from message pack: %v", expectedFile, err)
    45  		}
    46  		expectedString = string(sdk_json.Encode(generic))
    47  	}
    48  
    49  	err = EqualJson2(expectedString, actual)
    50  	if err != nil {
    51  		fmt.Printf("EXPECTED:\n%v\n", expectedString)
    52  		fmt.Printf("ACTUAL:\n%v\n", actual)
    53  	}
    54  	return err
    55  }
    56  
    57  // EqualJson2 compares two json strings.
    58  // returns true if considered equal, false otherwise.
    59  // The error returns the difference.
    60  // For reference: j1 is the baseline, j2 is the test
    61  func EqualJson2(j1, j2 string) (err error) {
    62  	var expected map[string]interface{}
    63  	json.Unmarshal([]byte(j1), &expected)
    64  
    65  	var actual map[string]interface{}
    66  	json.Unmarshal([]byte(j2), &actual)
    67  
    68  	err = recursiveCompare("root", expected, actual)
    69  
    70  	if err != nil {
    71  		log.Printf("expected:\n%s", j1)
    72  		log.Printf("actual:\n%s", j2)
    73  	}
    74  	return err
    75  }
    76  
    77  type ValueType int
    78  
    79  const (
    80  	OBJECT ValueType = iota
    81  	ARRAY
    82  	VALUE
    83  	NUMBER
    84  	BOOL
    85  	STRING
    86  	MISSING
    87  )
    88  
    89  func getType(val interface{}) ValueType {
    90  	if val == nil {
    91  		return MISSING
    92  	}
    93  	switch val.(type) {
    94  	case map[string]interface{}:
    95  		return OBJECT
    96  	case []interface{}:
    97  		return ARRAY
    98  	case string:
    99  		return STRING
   100  	case bool:
   101  		return BOOL
   102  	case float64:
   103  		return NUMBER
   104  	case nil:
   105  		return MISSING
   106  	default:
   107  		return VALUE
   108  	}
   109  }
   110  
   111  // binaryOrStringEqual checks combinations of string / base64 decoded strings
   112  // to see if the inputs are equal.
   113  // The decoding process doesn't seem to distinguish between string and binary, but the encoding process
   114  // does. So sometimes the string will be base64 encoded and need to compare against the decoded string
   115  // value.
   116  func binaryOrStringEqual(s1, s2 string) bool {
   117  	if s1 == s2 {
   118  		return true
   119  	}
   120  	if val, err := base64.StdEncoding.DecodeString(s1); err == nil {
   121  		if string(val) == s2 {
   122  			return true
   123  		}
   124  	}
   125  	if val, err := base64.StdEncoding.DecodeString(s2); err == nil {
   126  		if string(val) == s1 {
   127  			return true
   128  		}
   129  	}
   130  	return false
   131  }
   132  
   133  func sortArray(arr []interface{}, field string) {
   134  	sort.SliceStable(arr, func(i, j int) bool {
   135  		// literal type case
   136  		if field == "" {
   137  			vi := fmt.Sprintf("%v", arr[i])
   138  			vj := fmt.Sprintf("%v", arr[j])
   139  			return strings.Compare(vi, vj) < 0
   140  		}
   141  		// object case
   142  		vali := arr[i].(map[string]interface{})[field]
   143  		valj := arr[i].(map[string]interface{})[field]
   144  		vi := fmt.Sprintf("%v", vali)
   145  		vj := fmt.Sprintf("%v", valj)
   146  		return strings.Compare(vi, vj) < 0
   147  	})
   148  }
   149  
   150  func getFirstField(ob interface{}) string {
   151  	if ob == nil || getType(ob) != OBJECT {
   152  		return ""
   153  	}
   154  	for k, _ := range ob.(map[string]interface{}) {
   155  		return k
   156  	}
   157  	return ""
   158  }
   159  
   160  func recursiveCompare(field string, expected, actual interface{}) error {
   161  	expectedType := getType(expected)
   162  	actualType := getType(actual)
   163  
   164  	// If both were nil, just return
   165  	if expectedType == MISSING && actualType == MISSING {
   166  		return nil
   167  	}
   168  
   169  	var keyType ValueType
   170  
   171  	if expectedType == MISSING || actualType == MISSING {
   172  		if expectedType == MISSING {
   173  			keyType = actualType
   174  		}
   175  		if actualType == MISSING {
   176  			keyType = expectedType
   177  		}
   178  	} else {
   179  		// If both are present, make sure they are the same
   180  		if expectedType != actualType {
   181  			return errors.New("Type mismatch")
   182  		}
   183  		keyType = expectedType
   184  	}
   185  
   186  	switch keyType {
   187  	case ARRAY:
   188  		var expectedArr []interface{}
   189  		var actualArr []interface{}
   190  
   191  		expectedSize := 0
   192  		if expectedType != MISSING {
   193  			expectedArr = expected.([]interface{})
   194  			expectedSize = len(expectedArr)
   195  		}
   196  		actualSize := 0
   197  		if actualType != MISSING {
   198  			actualArr = actual.([]interface{})
   199  			actualSize = len(actualArr)
   200  		}
   201  
   202  		if expectedSize != actualSize {
   203  			return fmt.Errorf("failed to match array sizes: %s", field)
   204  		}
   205  
   206  		//sortField := getFirstField(expected)
   207  		//sortArray(actualArr, sortField)
   208  		//sortArray(expectedArr, sortField)
   209  
   210  		// n^2 baby! Just make sure every element has a match somewhere in the other list.
   211  		// getting the sort function to work would be better...
   212  		var err error
   213  		for i := 0; i < expectedSize; i++ {
   214  			for j := 0; j < len(actualArr); j++ {
   215  				err = recursiveCompare(fmt.Sprintf("%s[%d]", field, i), expectedArr[i], actualArr[j])
   216  				if err == nil {
   217  					break
   218  				}
   219  			}
   220  			if err != nil {
   221  				return err
   222  			}
   223  		}
   224  		return err
   225  
   226  	case OBJECT:
   227  		//log.Printf("%s{...} - object\n", field)
   228  
   229  		// Recursively compare each key value
   230  		// Pass nil's to the compare function to handle zero values on a type by type basis.
   231  
   232  		// Go happily creates complex zero value objects, so go ahead and recursively compare nil against defaults
   233  
   234  		// If they are both missing what are we even doing here. Return with no error.
   235  		if expectedType == MISSING && actualType == MISSING {
   236  			return nil
   237  		}
   238  
   239  		var expectedObject map[string]interface{}
   240  		var actualObject map[string]interface{}
   241  
   242  		keys := make(map[string]bool)
   243  		if expectedType != MISSING {
   244  			expectedObject = expected.(map[string]interface{})
   245  			for k, _ := range expectedObject {
   246  				keys[k] = true
   247  			}
   248  		}
   249  		if actualType != MISSING {
   250  			actualObject = actual.(map[string]interface{})
   251  			for k, _ := range actualObject {
   252  				keys[k] = true
   253  			}
   254  		}
   255  		for k, _ := range keys {
   256  			var err error
   257  			err = recursiveCompare(fmt.Sprintf("%s.%s", field, k), expectedObject[k], actualObject[k])
   258  			if err != nil {
   259  				return err
   260  			}
   261  		}
   262  
   263  	case NUMBER:
   264  		// Compare numbers, if missing treat as zero
   265  		expectedNum := float64(0)
   266  		if expectedType != MISSING {
   267  			expectedNum = expected.(float64)
   268  		}
   269  		actualNum := float64(0)
   270  		if actualType != MISSING {
   271  			actualNum = actual.(float64)
   272  		}
   273  		//log.Printf("%s - number %f == %f\n", field, expectedNum, actualNum)
   274  		if expectedNum != actualNum {
   275  			return fmt.Errorf("failed to match field %s, %f != %f", field, expectedNum, actualNum)
   276  		}
   277  
   278  	case BOOL:
   279  		// Compare bools, if missing treat as false
   280  		expectedBool := false
   281  		if expectedType != MISSING {
   282  			expectedBool = expected.(bool)
   283  		}
   284  		actualBool := false
   285  		if actualType != MISSING {
   286  			actualBool = actual.(bool)
   287  		}
   288  		//log.Printf("%s - bool %t == %t\n", field, expectedBool, actualBool)
   289  		if expectedBool != actualBool {
   290  			return fmt.Errorf("failed to match field %s, %t != %t", field, expectedBool, actualBool)
   291  		}
   292  
   293  	case STRING:
   294  		// Compare strings, if missing treat as an empty string.
   295  		// Note: I think binary ends up in here, it may need some special handling.
   296  		expectedStr := ""
   297  		if expectedType != MISSING {
   298  			expectedStr = expected.(string)
   299  		}
   300  		actualStr := ""
   301  		if actualType != MISSING {
   302  			actualStr = actual.(string)
   303  		}
   304  
   305  		//log.Printf("%s - string %s == %s\n", field, expectedStr, actualStr)
   306  		if !binaryOrStringEqual(expectedStr, actualStr) {
   307  			return fmt.Errorf("failed to match field %s, %s != %s", field, expectedStr, actualStr)
   308  		}
   309  
   310  	default:
   311  		return fmt.Errorf("unhandled type %v at %s", keyType, field)
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  func sliceOfBytesEqual(expected [][]byte, actual [][]byte) error {
   318  	if len(expected) != len(actual) {
   319  		return fmt.Errorf("expected length (%d) does not match actual length (%d)", len(expected), len(actual))
   320  	}
   321  
   322  	for i, expectedElement := range expected {
   323  		actualElement := actual[i]
   324  		if !bytes.Equal(expectedElement, actualElement) {
   325  			return fmt.Errorf("elements at index %d are unequal. Expected %s, got %s", i, hex.EncodeToString(expectedElement), hex.EncodeToString(actualElement))
   326  		}
   327  	}
   328  
   329  	return nil
   330  }