// stm: #unit package api import ( "encoding/json" "os" "os/exec" "path/filepath" "reflect" "runtime" "strings" "testing" "github.com/stretchr/testify/require" "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc" ) func goCmd() string { var exeSuffix string if runtime.GOOS == "windows" { exeSuffix = ".exe" } path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) if _, err := os.Stat(path); err == nil { return path } return "go" } func TestDoesntDependOnFFI(t *testing.T) { //stm: @OTHER_IMPLEMENTATION_FFI_DEPENDENCE_001 deps, err := exec.Command(goCmd(), "list", "-deps", "github.com/filecoin-project/lotus/api").Output() if err != nil { t.Fatal(err) } for _, pkg := range strings.Fields(string(deps)) { if pkg == "github.com/filecoin-project/filecoin-ffi" { t.Fatal("api depends on filecoin-ffi") } } } func TestDoesntDependOnBuild(t *testing.T) { //stm: @OTHER_IMPLEMENTATION_FFI_DEPENDENCE_002 deps, err := exec.Command(goCmd(), "list", "-deps", "github.com/filecoin-project/lotus/api").Output() if err != nil { t.Fatal(err) } for _, pkg := range strings.Fields(string(deps)) { if pkg == "github.com/filecoin-project/build" { t.Fatal("api depends on filecoin-ffi") } } } func TestReturnTypes(t *testing.T) { //stm: @OTHER_IMPLEMENTATION_001 errType := reflect.TypeOf(new(error)).Elem() bareIface := reflect.TypeOf(new(interface{})).Elem() jmarsh := reflect.TypeOf(new(json.Marshaler)).Elem() tst := func(api interface{}) func(t *testing.T) { return func(t *testing.T) { ra := reflect.TypeOf(api).Elem() for i := 0; i < ra.NumMethod(); i++ { m := ra.Method(i) switch m.Type.NumOut() { case 1: // if 1 return value, it must be an error require.Equal(t, errType, m.Type.Out(0), m.Name) case 2: // if 2 return values, first cant be an interface/function, second must be an error seen := map[reflect.Type]struct{}{} todo := []reflect.Type{m.Type.Out(0)} for len(todo) > 0 { typ := todo[len(todo)-1] todo = todo[:len(todo)-1] if _, ok := seen[typ]; ok { continue } seen[typ] = struct{}{} if typ.Kind() == reflect.Interface && typ != bareIface && !typ.Implements(jmarsh) { t.Error("methods can't return interfaces or struct types not implementing json.Marshaller", m.Name) } switch typ.Kind() { case reflect.Ptr: fallthrough case reflect.Array: fallthrough case reflect.Slice: fallthrough case reflect.Chan: todo = append(todo, typ.Elem()) case reflect.Map: todo = append(todo, typ.Elem()) todo = append(todo, typ.Key()) case reflect.Struct: for i := 0; i < typ.NumField(); i++ { todo = append(todo, typ.Field(i).Type) } } } require.NotEqual(t, reflect.Func.String(), m.Type.Out(0).Kind().String(), m.Name) require.Equal(t, errType, m.Type.Out(1), m.Name) default: t.Error("methods can only have 1 or 2 return values", m.Name) } } } } t.Run("common", tst(new(Common))) t.Run("full", tst(new(FullNode))) t.Run("miner", tst(new(StorageMiner))) t.Run("worker", tst(new(Worker))) } func TestPermTags(t *testing.T) { //stm: @OTHER_IMPLEMENTATION_PERM_TAGS_001 _ = PermissionedFullAPI(&FullNodeStruct{}) _ = PermissionedStorMinerAPI(&StorageMinerStruct{}) _ = PermissionedWorkerAPI(&WorkerStruct{}) } func TestRetryErrorIsInTrue(t *testing.T) { errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} require.True(t, ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry)) } func TestRetryErrorIsInFalse(t *testing.T) { errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} require.False(t, ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry)) } func TestRetryWrappedErrorIsInTrue(t *testing.T) { errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} require.True(t, ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry)) }