2020-08-15 22:57:27 +00:00
package conformance
import (
"bytes"
"compress/gzip"
"context"
2020-08-27 15:42:01 +00:00
"encoding/base64"
2020-08-15 22:57:27 +00:00
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/lib/blockstore"
2020-08-26 14:07:51 +00:00
"github.com/filecoin-project/statediff"
2020-08-19 16:33:30 +00:00
"github.com/filecoin-project/test-vectors/schema"
2020-08-15 22:57:27 +00:00
2020-08-26 17:35:41 +00:00
"github.com/fatih/color"
2020-08-15 22:57:27 +00:00
"github.com/ipld/go-car"
)
const (
2020-08-26 14:07:51 +00:00
// EnvSkipConformance, if 1, skips the conformance test suite.
EnvSkipConformance = "SKIP_CONFORMANCE"
// EnvCorpusRootDir is the name of the environment variable where the path
// to an alternative corpus location can be provided.
//
// The default is defaultCorpusRoot.
EnvCorpusRootDir = "CORPUS_DIR"
2020-08-15 22:57:27 +00:00
// defaultCorpusRoot is the directory where the test vector corpus is hosted.
// It is mounted on the Lotus repo as a git submodule.
2020-08-15 22:57:27 +00:00
//
2020-08-15 22:57:27 +00:00
// When running this test, the corpus root can be overridden through the
2020-08-16 21:22:12 +00:00
// -conformance.corpus CLI flag to run an alternate corpus.
2020-08-16 21:15:12 +00:00
defaultCorpusRoot = "../extern/test-vectors/corpus"
2020-08-15 22:57:27 +00:00
)
2020-08-26 14:07:51 +00:00
// ignore is a set of paths relative to root to skip.
var ignore = map [ string ] struct { } {
".git" : { } ,
"schema.json" : { } ,
2020-08-15 22:57:27 +00:00
}
// TestConformance is the entrypoint test that runs all test vectors found
2020-08-15 22:57:27 +00:00
// in the corpus root directory.
2020-08-15 22:57:27 +00:00
//
// It locates all json files via a recursive walk, skipping over the ignore set,
// as well as files beginning with _. It parses each file as a test vector, and
// runs it via the Driver.
func TestConformance ( t * testing . T ) {
2020-08-26 14:07:51 +00:00
if skip := strings . TrimSpace ( os . Getenv ( EnvSkipConformance ) ) ; skip == "1" {
2020-08-16 21:22:12 +00:00
t . SkipNow ( )
}
2020-08-26 14:07:51 +00:00
// corpusRoot is the effective corpus root path, taken from the `-conformance.corpus` CLI flag,
// falling back to defaultCorpusRoot if not provided.
corpusRoot := defaultCorpusRoot
if dir := strings . TrimSpace ( os . Getenv ( EnvCorpusRootDir ) ) ; dir != "" {
corpusRoot = dir
}
2020-08-15 22:57:27 +00:00
var vectors [ ] string
2020-08-15 22:57:27 +00:00
err := filepath . Walk ( corpusRoot + "/" , func ( path string , info os . FileInfo , err error ) error {
2020-08-15 22:57:27 +00:00
if err != nil {
t . Fatal ( err )
}
filename := filepath . Base ( path )
2020-08-15 22:57:27 +00:00
rel , err := filepath . Rel ( corpusRoot , path )
2020-08-15 22:57:27 +00:00
if err != nil {
t . Fatal ( err )
}
if _ , ok := ignore [ rel ] ; ok {
// skip over using the right error.
if info . IsDir ( ) {
return filepath . SkipDir
}
return nil
}
if info . IsDir ( ) {
// dive into directories.
return nil
}
if filepath . Ext ( path ) != ".json" {
// skip if not .json.
return nil
}
if ignored := strings . HasPrefix ( filename , "_" ) ; ignored {
// ignore files starting with _.
t . Logf ( "ignoring: %s" , rel )
return nil
}
vectors = append ( vectors , rel )
return nil
} )
if err != nil {
t . Fatal ( err )
}
if len ( vectors ) == 0 {
t . Fatalf ( "no test vectors found" )
}
// Run a test for each vector.
for _ , v := range vectors {
2020-08-16 21:15:12 +00:00
path := filepath . Join ( corpusRoot , v )
raw , err := ioutil . ReadFile ( path )
if err != nil {
t . Fatalf ( "failed to read test raw file: %s" , path )
}
2020-08-15 22:57:27 +00:00
2020-08-19 16:33:30 +00:00
var vector schema . TestVector
2020-08-16 21:15:12 +00:00
err = json . Unmarshal ( raw , & vector )
if err != nil {
t . Errorf ( "failed to parse test vector %s: %s; skipping" , path , err )
continue
}
2020-08-15 22:57:27 +00:00
2020-08-16 21:15:12 +00:00
t . Run ( v , func ( t * testing . T ) {
2020-08-26 10:45:34 +00:00
for _ , h := range vector . Hints {
if h == schema . HintIncorrect {
t . Logf ( "skipping vector marked as incorrect: %s" , vector . Meta . ID )
t . SkipNow ( )
}
}
2020-08-15 22:57:27 +00:00
// dispatch the execution depending on the vector class.
switch vector . Class {
case "message" :
executeMessageVector ( t , & vector )
default :
t . Fatalf ( "test vector class not supported: %s" , vector . Class )
}
} )
}
}
// executeMessageVector executes a message-class test vector.
2020-08-19 16:33:30 +00:00
func executeMessageVector ( t * testing . T , vector * schema . TestVector ) {
2020-08-15 22:57:27 +00:00
var (
ctx = context . Background ( )
epoch = vector . Pre . Epoch
root = vector . Pre . StateTree . RootCID
)
bs := blockstore . NewTemporary ( )
// Read the base64-encoded CAR from the vector, and inflate the gzip.
buf := bytes . NewReader ( vector . CAR )
r , err := gzip . NewReader ( buf )
if err != nil {
t . Fatalf ( "failed to inflate gzipped CAR: %s" , err )
}
defer r . Close ( ) // nolint
// Load the CAR embedded in the test vector into the Blockstore.
_ , err = car . LoadCar ( bs , r )
if err != nil {
t . Fatalf ( "failed to load state tree car from test vector: %s" , err )
}
// Create a new Driver.
2020-08-19 17:10:56 +00:00
driver := NewDriver ( ctx , vector )
2020-08-15 22:57:27 +00:00
// Apply every message.
2020-08-26 15:48:47 +00:00
for i , m := range vector . ApplyMessages {
2020-08-15 22:57:27 +00:00
msg , err := types . DecodeMessage ( m . Bytes )
if err != nil {
t . Fatalf ( "failed to deserialize message: %s" , err )
}
// add an epoch if one's set.
if m . Epoch != nil {
epoch = * m . Epoch
}
2020-08-15 22:57:27 +00:00
// Execute the message.
2020-08-15 22:57:27 +00:00
var ret * vm . ApplyRet
ret , root , err = driver . ExecuteMessage ( msg , root , bs , epoch )
if err != nil {
t . Fatalf ( "fatal failure when executing message: %s" , err )
}
2020-08-15 22:57:27 +00:00
// Assert that the receipt matches what the test vector expects.
2020-08-15 22:57:27 +00:00
receipt := vector . Post . Receipts [ i ]
if expected , actual := receipt . ExitCode , ret . ExitCode ; expected != actual {
t . Errorf ( "exit code of msg %d did not match; expected: %s, got: %s" , i , expected , actual )
}
if expected , actual := receipt . GasUsed , ret . GasUsed ; expected != actual {
t . Errorf ( "gas used of msg %d did not match; expected: %d, got: %d" , i , expected , actual )
}
2020-08-27 15:42:01 +00:00
if expected , actual := [ ] byte ( receipt . ReturnValue ) , ret . Return ; ! bytes . Equal ( expected , actual ) {
t . Errorf ( "return value of msg %d did not match; expected: %s, got: %s" , i , base64 . StdEncoding . EncodeToString ( expected ) , base64 . StdEncoding . EncodeToString ( actual ) )
}
2020-08-15 22:57:27 +00:00
}
2020-08-15 22:57:27 +00:00
// Once all messages are applied, assert that the final state root matches
// the expected postcondition root.
2020-08-15 22:57:27 +00:00
if root != vector . Post . StateTree . RootCID {
2020-08-26 17:35:41 +00:00
color . NoColor = false // enable colouring.
2020-08-26 14:07:51 +00:00
t . Errorf ( "wrong post root cid; expected %v, but got %v" , vector . Post . StateTree . RootCID , root )
2020-08-26 17:35:41 +00:00
var (
a = color . New ( color . FgMagenta , color . Bold ) . Sprint ( "(A) expected final state" )
b = color . New ( color . FgYellow , color . Bold ) . Sprint ( "(B) actual final state" )
c = color . New ( color . FgCyan , color . Bold ) . Sprint ( "(C) initial state" )
d1 = color . New ( color . FgGreen , color . Bold ) . Sprint ( "[Δ1]" )
d2 = color . New ( color . FgGreen , color . Bold ) . Sprint ( "[Δ2]" )
d3 = color . New ( color . FgGreen , color . Bold ) . Sprint ( "[Δ3]" )
)
bold := color . New ( color . Bold ) . SprintfFunc ( )
2020-08-26 14:07:51 +00:00
// run state diffs.
2020-08-26 17:35:41 +00:00
t . Log ( bold ( "=== dumping 3-way diffs between %s, %s, %s ===" , a , b , c ) )
2020-08-26 14:07:51 +00:00
2020-08-26 17:35:41 +00:00
t . Log ( bold ( "--- %s left: %s; right: %s ---" , d1 , a , b ) )
2020-08-26 14:07:51 +00:00
t . Log ( statediff . Diff ( context . Background ( ) , bs , vector . Post . StateTree . RootCID , root ) )
2020-08-26 17:35:41 +00:00
t . Log ( bold ( "--- %s left: %s; right: %s ---" , d2 , c , b ) )
2020-08-26 14:07:51 +00:00
t . Log ( statediff . Diff ( context . Background ( ) , bs , vector . Pre . StateTree . RootCID , root ) )
2020-08-26 17:35:41 +00:00
t . Log ( bold ( "--- %s left: %s; right: %s ---" , d3 , c , a ) )
2020-08-26 14:07:51 +00:00
t . Log ( statediff . Diff ( context . Background ( ) , bs , vector . Pre . StateTree . RootCID , vector . Post . StateTree . RootCID ) )
2020-08-15 22:57:27 +00:00
}
}