github.com/chadouming/blockbook@v0.0.0-20230522201718-fae8f5aff8a3/tests/integration.go (about)

     1  //go:build integration
     2  
     3  package tests
     4  
     5  import (
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"sort"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/martinboehm/btcutil/chaincfg"
    20  	"github.com/chadouming/blockbook/bchain"
    21  	"github.com/chadouming/blockbook/bchain/coins"
    22  	build "github.com/chadouming/blockbook/build/tools"
    23  	"github.com/chadouming/blockbook/tests/rpc"
    24  	"github.com/chadouming/blockbook/tests/sync"
    25  )
    26  
    27  type TestFunc func(t *testing.T, coin string, chain bchain.BlockChain, mempool bchain.Mempool, testConfig json.RawMessage)
    28  
    29  var integrationTests = map[string]TestFunc{
    30  	"rpc":  rpc.IntegrationTest,
    31  	"sync": sync.IntegrationTest,
    32  }
    33  
    34  var notConnectedError = errors.New("Not connected to backend server")
    35  
    36  func runIntegrationTests(t *testing.T) {
    37  	tests, err := loadTests("tests.json")
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  
    42  	keys := make([]string, 0, len(tests))
    43  	for k := range tests {
    44  		keys = append(keys, k)
    45  	}
    46  	sort.Strings(keys)
    47  
    48  	for _, coin := range keys {
    49  		cfg := tests[coin]
    50  		name := getMatchableName(coin)
    51  		t.Run(name, func(t *testing.T) { runTests(t, coin, cfg) })
    52  
    53  	}
    54  }
    55  
    56  func loadTests(path string) (map[string]map[string]json.RawMessage, error) {
    57  	b, err := ioutil.ReadFile(path)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	v := make(map[string]map[string]json.RawMessage)
    62  	err = json.Unmarshal(b, &v)
    63  	return v, err
    64  }
    65  
    66  func getMatchableName(coin string) string {
    67  	if idx := strings.Index(coin, "_testnet"); idx != -1 {
    68  		return coin[:idx] + "=test"
    69  	} else {
    70  		return coin + "=main"
    71  	}
    72  }
    73  
    74  func runTests(t *testing.T, coin string, cfg map[string]json.RawMessage) {
    75  	if cfg == nil || len(cfg) == 0 {
    76  		t.Skip("No tests to run")
    77  	}
    78  	defer chaincfg.ResetParams()
    79  
    80  	bc, m, err := makeBlockChain(coin)
    81  	if err != nil {
    82  		if err == notConnectedError {
    83  			t.Fatal(err)
    84  		}
    85  		t.Fatalf("Cannot init blockchain: %s", err)
    86  	}
    87  
    88  	for test, c := range cfg {
    89  		if fn, found := integrationTests[test]; found {
    90  			t.Run(test, func(t *testing.T) { fn(t, coin, bc, m, c) })
    91  		} else {
    92  			t.Errorf("Test not found: %s", test)
    93  		}
    94  	}
    95  }
    96  
    97  func makeBlockChain(coin string) (bchain.BlockChain, bchain.Mempool, error) {
    98  	c, err := build.LoadConfig("../configs", coin)
    99  	if err != nil {
   100  		return nil, nil, err
   101  	}
   102  
   103  	outputDir, err := ioutil.TempDir("", "integration_test")
   104  	if err != nil {
   105  		return nil, nil, err
   106  	}
   107  	defer os.RemoveAll(outputDir)
   108  
   109  	err = build.GeneratePackageDefinitions(c, "../build/templates", outputDir)
   110  	if err != nil {
   111  		return nil, nil, err
   112  	}
   113  
   114  	b, err := ioutil.ReadFile(filepath.Join(outputDir, "blockbook", "blockchaincfg.json"))
   115  	if err != nil {
   116  		return nil, nil, err
   117  	}
   118  
   119  	var cfg json.RawMessage
   120  	err = json.Unmarshal(b, &cfg)
   121  	if err != nil {
   122  		return nil, nil, err
   123  	}
   124  
   125  	coinName, err := getName(cfg)
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  
   130  	return initBlockChain(coinName, cfg)
   131  }
   132  
   133  func getName(raw json.RawMessage) (string, error) {
   134  	var cfg map[string]interface{}
   135  	err := json.Unmarshal(raw, &cfg)
   136  	if err != nil {
   137  		return "", err
   138  	}
   139  	if n, found := cfg["coin_name"]; found {
   140  		switch n := n.(type) {
   141  		case string:
   142  			return n, nil
   143  		default:
   144  			return "", fmt.Errorf("Unexpected type of field `name`: %s", reflect.TypeOf(n))
   145  		}
   146  	} else {
   147  		return "", errors.New("Missing field `name`")
   148  	}
   149  }
   150  
   151  func initBlockChain(coinName string, cfg json.RawMessage) (bchain.BlockChain, bchain.Mempool, error) {
   152  	factory, found := coins.BlockChainFactories[coinName]
   153  	if !found {
   154  		return nil, nil, fmt.Errorf("Factory function not found")
   155  	}
   156  
   157  	chain, err := factory(cfg, func(_ bchain.NotificationType) {})
   158  	if err != nil {
   159  		if isNetError(err) {
   160  			return nil, nil, notConnectedError
   161  		}
   162  		return nil, nil, fmt.Errorf("Factory function failed: %s", err)
   163  	}
   164  
   165  	for i := 0; ; i++ {
   166  		err = chain.Initialize()
   167  		if err == nil {
   168  			break
   169  		}
   170  		if isNetError(err) {
   171  			return nil, nil, notConnectedError
   172  		}
   173  		// wait max 5 minutes for backend to startup
   174  		if i > 5*60 {
   175  			return nil, nil, fmt.Errorf("BlockChain initialization failed: %s", err)
   176  		}
   177  		time.Sleep(time.Millisecond * 1000)
   178  	}
   179  
   180  	mempool, err := chain.CreateMempool(chain)
   181  	if err != nil {
   182  		return nil, nil, fmt.Errorf("Mempool creation failed: %s", err)
   183  	}
   184  
   185  	err = chain.InitializeMempool(nil, nil, nil)
   186  	if err != nil {
   187  		return nil, nil, fmt.Errorf("Mempool initialization failed: %s", err)
   188  	}
   189  
   190  	return chain, mempool, nil
   191  }
   192  
   193  func isNetError(err error) bool {
   194  	if _, ok := err.(net.Error); ok {
   195  		return true
   196  	}
   197  	return false
   198  }