github.com/mavryk-network/mvgo@v1.19.9/micheline/value_test.go (about)

     1  // Copyright (c) 2021 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  //
     4  
     5  // Run with tracing enabled
     6  // go test -tags trace ./micheline/
     7  
     8  package micheline
     9  
    10  import (
    11  	"encoding/hex"
    12  	"encoding/json"
    13  	"flag"
    14  	"fmt"
    15  	"io"
    16  	"io/fs"
    17  	"os"
    18  	"path/filepath"
    19  	"reflect"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/pmezard/go-difflib/difflib"
    24  )
    25  
    26  const testDataRootPathPrefix = "testdata"
    27  
    28  var (
    29  	testfiles = make(map[string][]string)
    30  	testmask  string
    31  )
    32  
    33  type testcase struct {
    34  	Name      string          `json:"name"`
    35  	NoUnpack  bool            `json:"no_unpack"`
    36  	Type      json.RawMessage `json:"type"`
    37  	Value     json.RawMessage `json:"value"`
    38  	Key       json.RawMessage `json:"key"`
    39  	TypeHex   string          `json:"type_hex"`
    40  	ValueHex  string          `json:"value_hex"`
    41  	KeyHex    string          `json:"key_hex"`
    42  	WantValue json.RawMessage `json:"want_value"`
    43  	WantKey   json.RawMessage `json:"want_key"`
    44  }
    45  
    46  func TestMain(m *testing.M) {
    47  	// call flag.Parse() here if TestMain uses flags
    48  	flag.StringVar(&testmask, "only", "", "limit test to contract or op")
    49  	flag.Parse()
    50  	os.Exit(m.Run())
    51  }
    52  
    53  func scanTestFiles(t *testing.T, category string) {
    54  	if len(testfiles[category]) > 0 {
    55  		return
    56  	}
    57  	testfiles[category] = make([]string, 0)
    58  	// find all test data directories
    59  	testPaths := make([]string, 0)
    60  	_ = filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    61  		if err != nil {
    62  			return err
    63  		}
    64  		if !d.IsDir() || path == "." {
    65  			return nil
    66  		}
    67  		if strings.HasPrefix(filepath.Base(path), testDataRootPathPrefix) {
    68  			testPaths = append(testPaths, path)
    69  		}
    70  		return fs.SkipDir
    71  	})
    72  	// load tests from subdirs
    73  	for _, testPath := range testPaths {
    74  		err := filepath.WalkDir(
    75  			filepath.Join(testPath, category),
    76  			func(path string, d fs.DirEntry, err error) error {
    77  				if err != nil {
    78  					return err
    79  				}
    80  				if d.IsDir() || !strings.HasSuffix(path, "json") {
    81  					return nil
    82  				}
    83  				if testmask != "" && !strings.Contains(path, testmask) {
    84  					return nil
    85  				}
    86  				testfiles[category] = append(testfiles[category], path)
    87  				return nil
    88  			})
    89  		if err != nil {
    90  			t.Logf("WARN: loading testfiles from %s: %v", testPath, err)
    91  		}
    92  	}
    93  }
    94  
    95  func jsonDiff(t *testing.T, a, b []byte) bool {
    96  	var ja, jb interface{}
    97  	if err := json.Unmarshal(a, &ja); err != nil {
    98  		t.Error(err)
    99  	}
   100  	if err := json.Unmarshal(b, &jb); err != nil {
   101  		t.Error(err)
   102  	}
   103  	if reflect.DeepEqual(ja, jb) {
   104  		return true
   105  	}
   106  
   107  	// log line-wise differences
   108  	textA, _ := json.MarshalIndent(ja, "", "  ")
   109  	textB, _ := json.MarshalIndent(jb, "", "  ")
   110  	diff := difflib.UnifiedDiff{
   111  		A:        difflib.SplitLines(string(textA)),
   112  		B:        difflib.SplitLines(string(textB)),
   113  		FromFile: "GOT",
   114  		ToFile:   "WANT",
   115  		Context:  3,
   116  	}
   117  	text, _ := difflib.GetUnifiedDiffString(diff)
   118  	t.Log("DIFF:\n" + text)
   119  	return false
   120  }
   121  
   122  func loadNextTestFile(category string, offset int, val interface{}) (int, error) {
   123  	files, ok := testfiles[category]
   124  	if !ok {
   125  		return 0, fmt.Errorf("invalid category %s", category)
   126  	}
   127  	if len(files) <= offset {
   128  		return offset, io.EOF
   129  	}
   130  	buf, err := os.ReadFile(files[offset])
   131  	if err != nil {
   132  		return offset + 1, err
   133  	}
   134  	err = json.Unmarshal(buf, val)
   135  	return offset + 1, err
   136  }
   137  
   138  func checkTypeEncoding(t *testing.T, test testcase) Type {
   139  	// decode type (hex & json and compare trees)
   140  	buf, err := hex.DecodeString(test.TypeHex)
   141  	if err != nil {
   142  		t.Errorf("invalid binary type: %v", err)
   143  		t.FailNow()
   144  	}
   145  	typ1 := Type{}
   146  	if err := typ1.UnmarshalBinary(buf); err != nil {
   147  		t.Errorf("invalid binary type: %v", err)
   148  		t.FailNow()
   149  	}
   150  	typ2 := Type{}
   151  	if err := typ2.UnmarshalJSON(test.Type); err != nil {
   152  		t.Errorf("invalid json type: %v", err)
   153  		t.FailNow()
   154  	}
   155  	// compare prim trees
   156  	if !typ1.IsEqualWithAnno(typ2) {
   157  		b1, _ := typ1.MarshalBinary()
   158  		b2, _ := typ2.MarshalBinary()
   159  		t.Errorf("bigmap type decoding mismatch:\n  want=%s %x\n  have=%s %x",
   160  			typ1.Dump(), b1, typ2.Dump(), b2)
   161  	}
   162  	return typ1
   163  }
   164  
   165  func checkValueEncoding(t *testing.T, test testcase) Prim {
   166  	// decode value (hex & json and compare trees)
   167  	buf, err := hex.DecodeString(test.ValueHex)
   168  	if err != nil {
   169  		t.Errorf("invalid binary value: %v", err)
   170  		t.FailNow()
   171  	}
   172  	val1 := Prim{}
   173  	if err := val1.UnmarshalBinary(buf); err != nil {
   174  		t.Errorf("invalid binary value: %v", err)
   175  		t.FailNow()
   176  	}
   177  	val2 := Prim{}
   178  	if err := val2.UnmarshalJSON(test.Value); err != nil {
   179  		t.Errorf("invalid json value: %v", err)
   180  		t.FailNow()
   181  	}
   182  	if !val1.IsEqualWithAnno(val2) {
   183  		b1, _ := val1.MarshalBinary()
   184  		b2, _ := val2.MarshalBinary()
   185  		t.Errorf("json/hex value mismatch:\n  A=%s %x\n  B=%s %x",
   186  			val1.Dump(), b1, val2.Dump(), b2)
   187  		t.FailNow()
   188  	}
   189  	return val1
   190  }
   191  
   192  func checkKeyEncoding(t *testing.T, test testcase) Prim {
   193  	// decode key (hex & json and compare trees)
   194  	buf, err := hex.DecodeString(test.KeyHex)
   195  	if err != nil {
   196  		t.Errorf("invalid binary key: %v", err)
   197  		t.FailNow()
   198  	}
   199  	key1 := Prim{}
   200  	if err := key1.UnmarshalBinary(buf); err != nil {
   201  		t.Errorf("invalid binary key: %v", err)
   202  		t.FailNow()
   203  	}
   204  	key2 := Prim{}
   205  	if err := key2.UnmarshalJSON(test.Key); err != nil {
   206  		t.Errorf("invalid json key: %v", err)
   207  		t.FailNow()
   208  	}
   209  	// compare prim trees
   210  	if !key1.IsEqualWithAnno(key2) {
   211  		t.Errorf("json/hex key mismatch:\n  A=%s\n  B=%s\n  a=%#v\n  b=%#v",
   212  			key1.Dump(), key2.Dump(), key1, key2)
   213  		t.FailNow()
   214  	}
   215  	return key1
   216  }
   217  
   218  func TestBigmapValues(t *testing.T) {
   219  	var (
   220  		next int
   221  		err  error
   222  	)
   223  	scanTestFiles(t, "bigmap")
   224  	trace = t.Logf
   225  	// dbg = t.Logf
   226  	for {
   227  		var tests []testcase
   228  		next, err = loadNextTestFile("bigmap", next, &tests)
   229  		if err != nil {
   230  			if err == io.EOF {
   231  				break
   232  			}
   233  			t.Error(err)
   234  			if len(tests) == 0 {
   235  				break
   236  			}
   237  			continue
   238  		}
   239  		for _, test := range tests {
   240  			t.Run(test.Name, func(t *testing.T) {
   241  				typ1 := checkTypeEncoding(t, test)
   242  				key1 := checkKeyEncoding(t, test)
   243  				val1 := checkValueEncoding(t, test)
   244  
   245  				// test bigmap key
   246  				k, err := NewKey(
   247  					typ1.Left(), // from binary (use key type)
   248  					key1,        // from binary
   249  				)
   250  				if err != nil {
   251  					t.Logf("typ: %s", typ1.Left().Dump())
   252  					t.Logf("key: %s", key1.Dump())
   253  					t.Errorf("key render error: %v", err)
   254  				}
   255  				// try unpack
   256  				if k.IsPacked() && !test.NoUnpack {
   257  					up, err := k.Unpack()
   258  					if err != nil {
   259  						t.Errorf("key unpack error: %v", err)
   260  					}
   261  					k = up
   262  				}
   263  				buf, err := k.MarshalJSON()
   264  				if err != nil {
   265  					t.Errorf("value render error: %v", err)
   266  				}
   267  				if !jsonDiff(t, buf, test.WantKey) {
   268  					t.Error("key render mismatch!")
   269  					t.FailNow()
   270  				}
   271  
   272  				// test bigmap value
   273  				v := Value{
   274  					Type:   typ1.Right(), // from binary (use value type)
   275  					Value:  val1,         // from binary
   276  					Render: RENDER_TYPE_FAIL,
   277  				}
   278  				if v.IsPackedAny() && !test.NoUnpack {
   279  					up, err := v.UnpackAll()
   280  					if err != nil {
   281  						t.Errorf("value unpack error: %v", err)
   282  					}
   283  					v = up
   284  				}
   285  
   286  				buf, err = v.MarshalJSON()
   287  				if err != nil {
   288  					t.Errorf("value render error: %v", err)
   289  				}
   290  				if !jsonDiff(t, buf, test.WantValue) {
   291  					t.Error("value render mismatch!")
   292  					t.FailNow()
   293  				}
   294  			})
   295  		}
   296  	}
   297  }
   298  
   299  func TestStorageValues(t *testing.T) {
   300  	var (
   301  		next int
   302  		err  error
   303  	)
   304  	scanTestFiles(t, "storage")
   305  	trace = t.Logf
   306  	// dbg = t.Logf
   307  	for {
   308  		var tests []testcase
   309  		next, err = loadNextTestFile("storage", next, &tests)
   310  		if err != nil {
   311  			if err == io.EOF {
   312  				break
   313  			}
   314  			t.Error(err)
   315  			if len(tests) == 0 {
   316  				break
   317  			}
   318  			continue
   319  		}
   320  		for _, test := range tests {
   321  			t.Run(test.Name, func(t *testing.T) {
   322  				typ1 := checkTypeEncoding(t, test)
   323  				val1 := checkValueEncoding(t, test)
   324  
   325  				// test storage value
   326  				v := Value{
   327  					Type:   typ1, // from binary
   328  					Value:  val1, // from binary
   329  					Render: RENDER_TYPE_FAIL,
   330  				}
   331  				if v.IsPackedAny() && !test.NoUnpack {
   332  					up, err := v.UnpackAll()
   333  					if err != nil {
   334  						t.Errorf("value unpack error: %v", err)
   335  						t.FailNow()
   336  					}
   337  					v = up
   338  				}
   339  
   340  				buf, err := v.MarshalJSON()
   341  				if err != nil {
   342  					t.Errorf("value render error: %v", err)
   343  					t.FailNow()
   344  				}
   345  				if !jsonDiff(t, buf, test.WantValue) {
   346  					t.Error("value render mismatch, see log for details")
   347  					t.FailNow()
   348  				}
   349  			})
   350  		}
   351  	}
   352  }
   353  
   354  func TestParamsValues(t *testing.T) {
   355  	var (
   356  		next int
   357  		err  error
   358  	)
   359  	scanTestFiles(t, "params")
   360  	trace = t.Logf
   361  	// dbg = t.Logf
   362  	for {
   363  		var tests []testcase
   364  		next, err = loadNextTestFile("params", next, &tests)
   365  		if err != nil {
   366  			if err == io.EOF {
   367  				break
   368  			}
   369  			t.Error(err)
   370  			if len(tests) == 0 {
   371  				break
   372  			}
   373  			continue
   374  		}
   375  		for _, test := range tests {
   376  			t.Run(test.Name, func(t *testing.T) {
   377  				typ1 := checkTypeEncoding(t, test)
   378  				val1 := checkValueEncoding(t, test)
   379  
   380  				// test storage value
   381  				v := Value{
   382  					Type:   typ1, // from binary
   383  					Value:  val1, // from binary
   384  					Render: RENDER_TYPE_FAIL,
   385  				}
   386  				if v.IsPackedAny() && !test.NoUnpack {
   387  					up, err := v.UnpackAll()
   388  					if err != nil {
   389  						t.Errorf("value unpack error: %v", err)
   390  					}
   391  					v = up
   392  				}
   393  
   394  				buf, err := v.MarshalJSON()
   395  				if err != nil {
   396  					t.Errorf("value render error: %v", err)
   397  				}
   398  				if !jsonDiff(t, buf, test.WantValue) {
   399  					t.Error("value render mismatch, see log for details")
   400  					t.FailNow()
   401  				}
   402  			})
   403  		}
   404  	}
   405  }