github.com/cheng762/platon-go@v1.8.17-0.20190529111256-7deff2d7be26/cmd/wasm/unittest_runner.go (about) 1 package main 2 3 import ( 4 "github.com/PlatONnetwork/PlatON-Go/common" 5 "github.com/PlatONnetwork/PlatON-Go/core/vm" 6 "github.com/PlatONnetwork/PlatON-Go/life/compiler" 7 "github.com/PlatONnetwork/PlatON-Go/life/exec" 8 "github.com/PlatONnetwork/PlatON-Go/life/resolver" 9 "github.com/PlatONnetwork/PlatON-Go/log" 10 "bytes" 11 "encoding/hex" 12 "encoding/json" 13 "encoding/binary" 14 "errors" 15 "fmt" 16 "github.com/syndtr/goleveldb/leveldb" 17 "gopkg.in/urfave/cli.v1" 18 "io/ioutil" 19 "math/big" 20 "os" 21 "path" 22 "regexp" 23 "strconv" 24 "strings" 25 ) 26 27 // The runner used in the unit test is mainly responsible for testing the Platonlib c++ library. 28 // The wasm file is executed according to the dir scan directory. 29 // The wasm is entered from the main entry. 30 // The db is created according to --outdir. 31 // The test tool judges the test result based on the log information. 32 var ( 33 resultReg = regexp.MustCompile(`([\d]+)\s+tests,\s+([\d]+)\s+assertions,\s+([\d]+)\s+failures`) 34 35 testDBName = "/testdb" 36 //dbPathFlag = cli.StringFlag{ 37 // Name: "dbpath", 38 // Usage: "unittest leveldb path", 39 //} 40 // 41 //logFileFlag = cli.StringFlag{ 42 // Name: "logfile", 43 // Usage: "unittest log path", 44 //} 45 46 testDirFlag = cli.StringFlag{ 47 Name: "dir", 48 Usage: "unittest directory", 49 } 50 51 outDirFlag = cli.StringFlag{ 52 Name: "outdir", 53 Usage: "unittest output directory", 54 } 55 ) 56 57 var unittestCommand = cli.Command{ 58 Action: unittestCmd, 59 Name: "unittest", 60 Usage: "executes the given unit tests", 61 ArgsUsage: "<dir>", 62 Flags: []cli.Flag{ 63 testDirFlag, 64 outDirFlag, 65 }, 66 } 67 68 func unittestCmd(ctx *cli.Context) error { 69 testDir := ctx.String(testDirFlag.Name) 70 outDir := ctx.String(outDirFlag.Name) 71 72 dbPath := outDir + testDBName 73 74 logStream := bytes.NewBuffer(make([]byte, 65535)) 75 76 err := runTestDir(testDir, dbPath, logStream) 77 78 if err != nil { 79 return err 80 } 81 82 fmt.Println("all test pass") 83 84 return nil 85 } 86 87 func runTestDir(testDir, dbPath string, logStream *bytes.Buffer) (retErr error) { 88 89 defer func() { 90 if err := recover(); err != nil { 91 fmt.Println(logStream.String()) 92 retErr = err.(error) 93 } 94 }() 95 files, err := ioutil.ReadDir(testDir) 96 97 if err != nil { 98 return errors.New(fmt.Sprintf("read unittest dir failed :%v", err)) 99 } 100 101 for _, fi := range files { 102 logStream.Reset() 103 104 if fi.IsDir() { 105 runTestDir(testDir+"/"+fi.Name(), dbPath, logStream) 106 } else if path.Ext(fi.Name()) == ".wasm" { 107 fmt.Println("exec unittest file:" + fi.Name()) 108 os.RemoveAll(dbPath) 109 db, err := leveldb.OpenFile(dbPath, nil) 110 if err != nil { 111 return errors.New(fmt.Sprintf("open leveldb %s failed :%v", dbPath, err)) 112 } 113 114 code, err := ioutil.ReadFile(testDir + "/" + fi.Name()) 115 116 if err != nil { 117 return errors.New(fmt.Sprintf("open test file %s error :%v", fi.Name(), err)) 118 } 119 120 runTest(code, db, logStream) 121 122 states := resultReg.FindStringSubmatch(logStream.String()) 123 //fmt.Println("[",logStream.String(), "]") 124 if len(states) != 4 { 125 fmt.Println(logStream.String()) 126 return errors.New(fmt.Sprintf("%s unittest output result error : need 3 states such as []tests []assertions []failures", fi.Name())) 127 } 128 129 if states[3] != "0" { 130 return errors.New(fmt.Sprintf("unittest :%s error \n, %s", fi.Name(), logStream.String())) 131 } 132 133 db.Close() 134 135 } 136 } 137 return nil 138 } 139 140 func newContext(logStream *bytes.Buffer) *exec.VMContext { 141 logger := log.New("wasm") 142 logger.SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(logStream, log.FormatFunc(func(r *log.Record) []byte { 143 return []byte(r.Msg) 144 })))) 145 146 wasmLog := vm.NewWasmLogger(vm.Config{Debug: true}, logger) 147 148 return &exec.VMContext{ 149 Config: exec.VMConfig{ 150 EnableJIT: false, 151 DynamicMemoryPages: 1, 152 MaxMemoryPages: 256, 153 MaxTableSize: 65536, 154 MaxValueSlots: 10000, 155 MaxCallStackDepth: 512, 156 DefaultMemoryPages: 128, 157 DefaultTableSize: 65536, 158 GasLimit: 1000000000000, 159 }, 160 Addr: [20]byte{}, 161 GasLimit: 1000000000000, 162 StateDB: nil, 163 Log: wasmLog, 164 } 165 } 166 167 func runMain(wasm *exec.VirtualMachine) error { 168 entryID, ok := wasm.GetFunctionExport("_Z4mainiPPc") 169 if !ok { 170 return errors.New("find main error") 171 } 172 173 _, err := wasm.Run(entryID, 0, 0) 174 175 if logger, ok := wasm.Context.Log.(*vm.WasmLogger); ok { 176 logger.Flush() 177 } 178 179 wasm.Stop() 180 if err != nil { 181 return err 182 } 183 184 return nil 185 } 186 187 func runModule(m *compiler.Module, functionCode []compiler.InterpreterCode, db *leveldb.DB, logStream *bytes.Buffer) error { 188 context := newContext(logStream) 189 190 wasm, err := exec.NewVirtualMachineWithModule(m, functionCode, context, newUnitTestResolver(db, logStream), nil) 191 192 if err != nil { 193 return err 194 } 195 return runMain(wasm) 196 } 197 198 func runTest(code []byte, db *leveldb.DB, logStream *bytes.Buffer) error { 199 context := newContext(logStream) 200 201 202 wasm, err := exec.NewVirtualMachine(code, context, newUnitTestResolver(db, logStream), nil) 203 204 if err != nil { 205 return err 206 } 207 return runMain(wasm) 208 } 209 210 type UnitTestResolver struct { 211 db *leveldb.DB 212 resolver exec.ImportResolver 213 funcs map[string]map[string]*exec.FunctionImport 214 logStream *bytes.Buffer 215 } 216 217 func newUnitTestResolver(ldb *leveldb.DB, logStream *bytes.Buffer) *UnitTestResolver { 218 219 constGasFunc := func(vm *exec.VirtualMachine) (uint64, error) { 220 return 1, nil 221 } 222 223 resolver := &UnitTestResolver{ 224 db: ldb, 225 resolver: resolver.NewResolver(0x01), 226 logStream: logStream, 227 } 228 resolver.funcs = map[string]map[string]*exec.FunctionImport{ 229 "env": { 230 "setState": &exec.FunctionImport{Execute: resolver.envSetState, GasCost: constGasFunc}, 231 "getState": &exec.FunctionImport{Execute: resolver.envGetState, GasCost: constGasFunc}, 232 "getStateSize": &exec.FunctionImport{Execute: resolver.envGetStateSize, GasCost: constGasFunc}, 233 "getTestLog": &exec.FunctionImport{Execute: resolver.envGetTestLog, GasCost: constGasFunc}, 234 "getTestLogSize": &exec.FunctionImport{Execute: resolver.envGetTestLogSize, GasCost: constGasFunc}, 235 "clearLog": &exec.FunctionImport{Execute: resolver.envClearLog, GasCost: constGasFunc}, 236 "setStateDB": &exec.FunctionImport{Execute: resolver.envSetStateDB, GasCost: constGasFunc}, 237 "platonCallString": &exec.FunctionImport{Execute: resolver.envPlatonCall, GasCost: constGasFunc}, 238 "platonCallInt64": &exec.FunctionImport{Execute: resolver.envPlatonCall, GasCost: constGasFunc}, 239 "platonDelegateCallString": &exec.FunctionImport{Execute: resolver.envPlatonCall, GasCost: constGasFunc}, 240 "platonDelegateCallInt64": &exec.FunctionImport{Execute: resolver.envPlatonCall, GasCost: constGasFunc}, 241 "platonCall": &exec.FunctionImport{Execute: resolver.envPlatonCall, GasCost: constGasFunc}, 242 "platonDelegateCall": &exec.FunctionImport{Execute: resolver.envPlatonCall, GasCost: constGasFunc}, 243 "emitEvent": &exec.FunctionImport{Execute: resolver.envEmitEvent, GasCost: constGasFunc}, 244 "bigintAdd": &exec.FunctionImport{Execute: resolver.envBigintAdd, GasCost: constGasFunc}, 245 "envMalloc": &exec.FunctionImport{Execute: resolver.envMalloc, GasCost: constGasFunc}, 246 "envFree": &exec.FunctionImport{Execute: resolver.envFree, GasCost: constGasFunc}, 247 }, 248 } 249 return resolver 250 } 251 252 func (r *UnitTestResolver) envMalloc(vm *exec.VirtualMachine) int64 { 253 254 mem := vm.Memory 255 size := int(uint32(vm.GetCurrentFrame().Locals[0])) 256 pos := mem.Malloc(size) 257 258 return int64(pos) 259 } 260 261 func (r *UnitTestResolver) envFree(vm *exec.VirtualMachine) int64 { 262 mem := vm.Memory 263 offset := int(uint32(vm.GetCurrentFrame().Locals[0])) 264 err := mem.Free(offset) 265 if err != nil { 266 return -1 267 } 268 return 0 269 } 270 271 func (r *UnitTestResolver) ResolveFunc(module, field string) *exec.FunctionImport { 272 if m, exist := r.funcs[module]; exist == true { 273 if f, exist := m[field]; exist == true { 274 return f 275 } 276 } 277 return r.resolver.ResolveFunc(module, field) 278 } 279 280 func (r *UnitTestResolver) ResolveGlobal(module, field string) int64 { 281 return r.resolver.ResolveGlobal(module, field) 282 } 283 284 func (r *UnitTestResolver) envSetState(vm *exec.VirtualMachine) int64 { 285 key := int(int32(vm.GetCurrentFrame().Locals[0])) 286 keyLen := int(int32(vm.GetCurrentFrame().Locals[1])) 287 value := int(int32(vm.GetCurrentFrame().Locals[2])) 288 valueLen := int(int32(vm.GetCurrentFrame().Locals[3])) 289 r.db.Put(vm.Memory.Memory[key:key+keyLen], vm.Memory.Memory[value:value+valueLen], nil) 290 return 0 291 } 292 293 func (r *UnitTestResolver) envGetState(vm *exec.VirtualMachine) int64 { 294 key := int(int32(vm.GetCurrentFrame().Locals[0])) 295 keyLen := int(int32(vm.GetCurrentFrame().Locals[1])) 296 value := int(int32(vm.GetCurrentFrame().Locals[2])) 297 valueLen := int(int32(vm.GetCurrentFrame().Locals[3])) 298 299 val, err := r.db.Get(vm.Memory.Memory[key:key+keyLen], nil) 300 if len(val) > valueLen || err != nil { 301 return 0 302 } 303 304 copy(vm.Memory.Memory[value:value+valueLen], val) 305 return 0 306 } 307 308 func (r *UnitTestResolver) envGetStateSize(vm *exec.VirtualMachine) int64 { 309 key := int(int32(vm.GetCurrentFrame().Locals[0])) 310 keyLen := int(int32(vm.GetCurrentFrame().Locals[1])) 311 312 val, err := r.db.Get(vm.Memory.Memory[key:key+keyLen], nil) 313 if err != nil { 314 return 0 315 } 316 return int64(len(val)) 317 } 318 319 func (r *UnitTestResolver) envGetTestLog(v *exec.VirtualMachine) int64 { 320 if logger, ok := v.Context.Log.(*vm.WasmLogger); ok { 321 logger.Flush() 322 } 323 data := int(int32(v.GetCurrentFrame().Locals[0])) 324 dataLen := int(int32(v.GetCurrentFrame().Locals[1])) 325 size := len(r.logStream.Bytes()) 326 if dataLen < size { 327 panic("out of buffer") 328 } 329 330 copy(v.Memory.Memory[data:data+dataLen], r.logStream.Bytes()) 331 332 return int64(size) 333 } 334 335 func (r *UnitTestResolver) envGetTestLogSize(v *exec.VirtualMachine) int64 { 336 if logger, ok := v.Context.Log.(*vm.WasmLogger); ok { 337 logger.Flush() 338 } 339 return int64(len(r.logStream.Bytes())) 340 } 341 342 func (r *UnitTestResolver) envClearLog(vm *exec.VirtualMachine) int64 { 343 r.logStream.Reset() 344 return 0 345 } 346 347 func (r *UnitTestResolver) envSetStateDB(vm *exec.VirtualMachine) int64 { 348 data := int(int32(vm.GetCurrentFrame().Locals[0])) 349 dataLen := int(int32(vm.GetCurrentFrame().Locals[1])) 350 t := testState{} 351 err := json.Unmarshal([]byte(vm.Memory.Memory[data:data+dataLen]), &t) 352 if err != nil { 353 return -1 354 } 355 356 vm.Context.StateDB = &stateDB{ 357 state: &t, 358 } 359 return 0 360 } 361 362 func (r *UnitTestResolver) envPlatonCall(vm *exec.VirtualMachine) int64 { 363 addr := int(int32(vm.GetCurrentFrame().Locals[0])) 364 params := int(int32(vm.GetCurrentFrame().Locals[1])) 365 paramsLen := int(int32(vm.GetCurrentFrame().Locals[2])) 366 vm.Context.Log.Debug(hex.EncodeToString(vm.Memory.Memory[addr : addr+20])) 367 vm.Context.Log.Debug(" ") 368 vm.Context.Log.Debug(hex.EncodeToString(vm.Memory.Memory[params : params+paramsLen])) 369 return 0 370 } 371 372 func (r *UnitTestResolver) envEmitEvent(vm *exec.VirtualMachine) int64 { 373 topic := int(int32(vm.GetCurrentFrame().Locals[0])) 374 topicLen := int(int32(vm.GetCurrentFrame().Locals[1])) 375 data := int(int32(vm.GetCurrentFrame().Locals[2])) 376 dataLen := int(int32(vm.GetCurrentFrame().Locals[3])) 377 vm.Context.Log.Debug(string(vm.Memory.Memory[topic : topic+topicLen])) 378 vm.Context.Log.Debug(" ") 379 vm.Context.Log.Debug(hex.EncodeToString(vm.Memory.Memory[data : data+dataLen])) 380 381 return 0 382 } 383 384 func (r *UnitTestResolver) envBigintAdd(vm *exec.VirtualMachine) int64 { 385 frame := vm.GetCurrentFrame() 386 src := int(int32(frame.Locals[0])) 387 srcLen := int(int32(frame.Locals[1])) 388 dst := int(int32(frame.Locals[2])) 389 dstLen := int(int32(frame.Locals[3])) 390 391 i := new(big.Int) 392 i.SetBytes(vm.Memory.Memory[src : src+srcLen]) 393 394 ii := new(big.Int) 395 ii.SetUint64(1) 396 i = i.Add(i, ii) 397 398 buf := make([]byte, 4) 399 binary.LittleEndian.PutUint32(buf, uint32(len(i.Bytes()))) 400 401 copy(vm.Memory.Memory[dst:], i.Bytes()) 402 copy(vm.Memory.Memory[dstLen:], buf) 403 return 0 404 } 405 406 type testState struct { 407 GasPrice int64 `json:"gasPrice,omitempty"` 408 BlockHash map[string]string `json:"blockHash,omitempty"` 409 Number int64 `json:"number,omitempty"` 410 GasLimit uint64 `json:"gasLimit,omitempty"` 411 Timestamp int64 `json:"timestamp,omitempty"` 412 CoinBase string `json:"coinbase,omitempty"` 413 Balance int64 `json:"balance,omitempty"` 414 Origin string `json:"origin,omitempty"` 415 Caller string `json:"caller,omitempty"` 416 Value int64 `json:"value,omitempty"` 417 Address string `json:"address,omitempty"` 418 CallerNonce int64 `json:"nonce,omitempty"` 419 Account map[string]int64 `json:"account,omitempty"` 420 } 421 422 type stateDB struct { 423 vm.StateDB 424 state *testState 425 } 426 427 func (s *stateDB) GasPrice() int64 { 428 return s.state.GasPrice 429 } 430 func (s *stateDB) BlockHash(num uint64) common.Hash { 431 hash, e := s.state.BlockHash[strconv.FormatUint(num, 10)] 432 if !e { 433 return common.Hash{} 434 } 435 return common.HexToHash(hash) 436 } 437 func (s *stateDB) BlockNumber() *big.Int { 438 return big.NewInt(s.state.Number) 439 } 440 func (s *stateDB) GasLimimt() uint64 { 441 return s.state.GasLimit 442 } 443 func (s *stateDB) Time() *big.Int { 444 return big.NewInt(s.state.Timestamp) 445 } 446 func (s *stateDB) Coinbase() common.Address { 447 return common.HexToAddress(s.state.CoinBase) 448 } 449 func (s *stateDB) GetBalance(addr common.Address) *big.Int { 450 //fmt.Println("addr:", addr.Hex()) 451 balance, ok := s.state.Account[strings.ToLower(addr.Hex())] 452 if !ok { 453 return big.NewInt(0) 454 } 455 return big.NewInt(balance) 456 } 457 458 func (s *stateDB) Origin() common.Address { 459 return common.HexToAddress(s.state.Origin) 460 } 461 func (s *stateDB) Caller() common.Address { 462 return common.HexToAddress(s.state.Caller) 463 } 464 func (s *stateDB) Address() common.Address { 465 return common.HexToAddress(s.state.Address) 466 } 467 func (s *stateDB) CallValue() *big.Int { 468 return big.NewInt(s.state.Value) 469 } 470 func (s *stateDB) AddLog(address common.Address, topics []common.Hash, data []byte, bn uint64) { 471 472 } 473 func (s *stateDB) SetState(key []byte, value []byte) { 474 475 } 476 func (s *stateDB) GetState(key []byte) []byte { 477 return nil 478 } 479 480 func (s *stateDB) GetCallerNonce() int64 { 481 return s.state.CallerNonce 482 } 483 func (s *stateDB) Transfer(addr common.Address, value *big.Int) (ret []byte, leftOverGas uint64, err error) { 484 fromBalance, ok := s.state.Account[s.state.Address] 485 if !ok { 486 return nil, 0, fmt.Errorf("not found account %s", s.state.Address) 487 } 488 fBigBalance := new(big.Int) 489 fBigBalance.SetInt64(fromBalance) 490 if fBigBalance.Cmp(value) == -1 { 491 return nil, 0, errors.New("amount not enough") 492 } 493 494 495 tBalance, ok := s.state.Account[strings.ToLower(addr.Hex())] 496 if !ok { 497 return nil, 0, fmt.Errorf("not found account %s", strings.ToLower(addr.Hex())) 498 } 499 tBigBalance := new(big.Int) 500 tBigBalance.SetInt64(tBalance) 501 502 tBigBalance= tBigBalance.Add(tBigBalance, value) 503 s.state.Account[strings.ToLower(addr.Hex())] = tBigBalance.Int64() 504 505 fBigBalance = fBigBalance.Sub(fBigBalance, value) 506 s.state.Balance = fBigBalance.Int64() 507 s.state.Account[s.state.Address] = fBigBalance.Int64() 508 509 return nil, 0, nil 510 } 511 func (s *stateDB) Call(addr, params []byte) ([]byte, error) { 512 return nil, nil 513 } 514 func (s *stateDB) DelegateCall(addr, params []byte) ([]byte, error) { 515 return nil, nil 516 } 517 518 519 //func (s *stateDB) CreateAccount(common.Address){} 520 // 521 //func (s *stateDB) SubBalance(common.Address, *big.Int){} 522 //func (s *stateDB) AddBalance(common.Address, *big.Int){} 523 ////func (s *stateDB) GetBalance(common.Address) *big.Int{return nil} 524 // 525 //func (s *stateDB) GetNonce(common.Address) uint64{return 0} 526 //func (s *stateDB) SetNonce(common.Address, uint64){} 527 // 528 //func (s *stateDB) GetCodeHash(common.Address) common.Hash{return common.Hash{}} 529 //func (s *stateDB) GetCode(common.Address) []byte{return nil} 530 //func (s *stateDB) SetCode(common.Address, []byte){} 531 //func (s *stateDB) GetCodeSize(common.Address) int{return 0} 532 533 //// todo: new func for abi of contract. 534 //func (stateDB) GetAbiHash(common.Address) common.Hash{return common.Hash{}} 535 //func (stateDB) GetAbi(common.Address) []byte{return nil} 536 //func (stateDB) SetAbi(common.Address, []byte){} 537 // 538 //func (stateDB) AddRefund(uint64){} 539 //func (stateDB) SubRefund(uint64){} 540 //func (stateDB) GetRefund() uint64{return 0} 541 // 542 //func (stateDB) GetCommittedState(common.Address, []byte) []byte{return nil} 543 //func (stateDB) GetState(common.Address, []byte) []byte{return []byte("world+++++++**")} 544 //func (stateDB) SetState(common.Address, []byte, []byte){} 545 //func (stateDB) Suicide(common.Address) bool{return true} 546 //func (stateDB) HasSuicided(common.Address) bool{return true} 547 // 548 //// Exist reports whether the given account exists in state. 549 //// Notably this should also return true for suicided accounts. 550 //func (stateDB) Exist(common.Address) bool {return true} 551 //// Empty returns whether the given account is empty. Empty 552 //// is defined according to EIP161 (balance = nonce = code = 0). 553 //func (stateDB) Empty(common.Address) bool {return true} 554 // 555 //func (stateDB) RevertToSnapshot(int){} 556 //func (stateDB) Snapshot() int {return 0} 557 // 558 //func (stateDB) AddPreimage(common.Hash, []byte){} 559 // 560 //func (stateDB) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool){} 561 //func (stateDB) Address() common.Address { 562 // return common.Address{} 563 //} 564 // 565 //func (stateDB) BlockHash(num uint64) common.Hash { 566 // return common.Hash{} 567 //} 568 // 569 //func (stateDB) BlockNumber() *big.Int { 570 // return big.NewInt(0) 571 //} 572 //func (stateDB) AddLog(*types.Log) { 573 // fmt.Println("add log") 574 //}