github.com/status-im/status-go@v1.1.0/t/utils/utils.go (about)

     1  package utils
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/status-im/status-go/common"
    21  
    22  	_ "github.com/stretchr/testify/suite" // required to register testify flags
    23  
    24  	"github.com/status-im/status-go/logutils"
    25  	"github.com/status-im/status-go/params"
    26  	"github.com/status-im/status-go/static"
    27  	"github.com/status-im/status-go/t"
    28  )
    29  
    30  var (
    31  	networkSelected = flag.String("network", "statuschain", "-network=NETWORKID or -network=NETWORKNAME to select network used for tests")
    32  	logLevel        = flag.String("log", "INFO", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
    33  )
    34  
    35  var (
    36  	// ErrNoRemoteURL is returned when network id has no associated url.
    37  	ErrNoRemoteURL = errors.New("network id requires a remote URL")
    38  
    39  	// ErrTimeout is returned when test times out
    40  	ErrTimeout = errors.New("timeout")
    41  
    42  	// TestConfig defines the default config usable at package-level.
    43  	TestConfig *testConfig
    44  
    45  	// RootDir is the main application directory
    46  	RootDir string
    47  
    48  	// TestDataDir is data directory used for tests
    49  	TestDataDir string
    50  
    51  	// TestNetworkNames network ID to name mapping
    52  	TestNetworkNames = map[int]string{
    53  		params.MainNetworkID:        "Mainnet",
    54  		params.StatusChainNetworkID: "StatusChain",
    55  		params.GoerliNetworkID:      "Goerli",
    56  	}
    57  
    58  	syncTimeout = 50 * time.Minute
    59  )
    60  
    61  func Init() {
    62  	pwd, err := os.Getwd()
    63  	if err != nil {
    64  		panic(err)
    65  	}
    66  
    67  	flag.Parse()
    68  
    69  	// set up logger
    70  	loggerEnabled := *logLevel != ""
    71  	if err := logutils.OverrideRootLog(loggerEnabled, *logLevel, logutils.FileOptions{}, true); err != nil {
    72  		panic(err)
    73  	}
    74  
    75  	// setup root directory
    76  	const pathSeparator = string(os.PathSeparator)
    77  	RootDir = filepath.Dir(pwd)
    78  	pathDirs := strings.Split(RootDir, pathSeparator)
    79  	for i := len(pathDirs) - 1; i >= 0; i-- {
    80  		if pathDirs[i] == "status-go" {
    81  			RootDir = filepath.Join(pathDirs[:i+1]...)
    82  			RootDir = filepath.Join(pathSeparator, RootDir)
    83  			break
    84  		}
    85  	}
    86  
    87  	// setup auxiliary directories
    88  	TestDataDir = filepath.Join(RootDir, ".ethereumtest")
    89  
    90  	TestConfig, err = loadTestConfig()
    91  	if err != nil {
    92  		panic(err)
    93  	}
    94  }
    95  
    96  // LoadFromFile is useful for loading test data, from testdata/filename into a variable
    97  // nolint: errcheck
    98  func LoadFromFile(filename string) string {
    99  	f, err := os.Open(filename)
   100  	if err != nil {
   101  		return ""
   102  	}
   103  
   104  	buf := bytes.NewBuffer(nil)
   105  	io.Copy(buf, f) // nolint: gas
   106  	f.Close()       // nolint: gas
   107  
   108  	return buf.String()
   109  }
   110  
   111  // EnsureSync waits until blockchain synchronization is complete and returns.
   112  type EnsureSync func(context.Context) error
   113  
   114  // EnsureNodeSync waits until node synchronzation is done to continue
   115  // with tests afterwards. Panics in case of an error or a timeout.
   116  func EnsureNodeSync(ensureSync EnsureSync) {
   117  	ctx, cancel := context.WithTimeout(context.Background(), syncTimeout)
   118  	defer cancel()
   119  
   120  	if err := ensureSync(ctx); err != nil {
   121  		panic(err)
   122  	}
   123  }
   124  
   125  // GetRemoteURLFromNetworkID returns associated network url for giving network id.
   126  func GetRemoteURLFromNetworkID(id int) (url string, err error) {
   127  	switch id {
   128  	case params.MainNetworkID:
   129  		url = params.MainnetEthereumNetworkURL
   130  	case params.GoerliNetworkID:
   131  		url = params.GoerliEthereumNetworkURL
   132  	default:
   133  		err = ErrNoRemoteURL
   134  	}
   135  
   136  	return
   137  }
   138  
   139  // GetHeadHashFromNetworkID returns the hash associated with a given network id.
   140  func GetHeadHashFromNetworkID(id int) string {
   141  	switch id {
   142  	case params.MainNetworkID:
   143  		return "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
   144  	case params.StatusChainNetworkID:
   145  		return "0xe9d8920a99dc66a9557a87d51f9d14a34ec50aae04298e0f142187427d3c832e"
   146  	case params.GoerliNetworkID:
   147  		return "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"
   148  	}
   149  	// Every other ID must break the test.
   150  	panic(fmt.Sprintf("invalid network id: %d", id))
   151  }
   152  
   153  // GetRemoteURL returns the url associated with a given network id.
   154  func GetRemoteURL() (string, error) {
   155  	return GetRemoteURLFromNetworkID(GetNetworkID())
   156  }
   157  
   158  // GetHeadHash returns the hash associated with a given network id.
   159  func GetHeadHash() string {
   160  	return GetHeadHashFromNetworkID(GetNetworkID())
   161  }
   162  
   163  // GetNetworkID returns appropriate network id for test based on
   164  // default or provided -network flag.
   165  func GetNetworkID() int {
   166  	switch strings.ToLower(*networkSelected) {
   167  	case fmt.Sprintf("%d", params.MainNetworkID), "mainnet":
   168  		return params.MainNetworkID
   169  	case fmt.Sprintf("%d", params.StatusChainNetworkID), "statuschain":
   170  		return params.StatusChainNetworkID
   171  	case fmt.Sprintf("%d", params.GoerliNetworkID), "goerli":
   172  		return params.GoerliNetworkID
   173  	}
   174  	// Every other selected network must break the test.
   175  	panic(fmt.Sprintf("invalid selected network: %q", *networkSelected))
   176  }
   177  
   178  // CheckTestSkipForNetworks checks if network for test is one of the
   179  // prohibited ones and skips the test in this case.
   180  func CheckTestSkipForNetworks(t *testing.T, networks ...int) {
   181  	id := GetNetworkID()
   182  	for _, network := range networks {
   183  		if network == id {
   184  			t.Skipf("skipping test for network %d", network)
   185  		}
   186  	}
   187  }
   188  
   189  // GetAccount1PKFile returns the filename for Account1 keystore based
   190  // on the current network. This allows running the e2e tests on the
   191  // private network w/o access to the ACCOUNT_PASSWORD env variable
   192  func GetAccount1PKFile() string {
   193  	if GetNetworkID() == params.StatusChainNetworkID {
   194  		return "test-account1-status-chain.pk"
   195  	}
   196  	return "test-account1.pk"
   197  }
   198  
   199  // GetAccount2PKFile returns the filename for Account2 keystore based
   200  // on the current network. This allows running the e2e tests on the
   201  // private network w/o access to the ACCOUNT_PASSWORD env variable
   202  func GetAccount2PKFile() string {
   203  	if GetNetworkID() == params.StatusChainNetworkID {
   204  		return "test-account2-status-chain.pk"
   205  	}
   206  	return "test-account2.pk"
   207  }
   208  
   209  // WaitClosed used to wait on a channel in tests
   210  func WaitClosed(c <-chan struct{}, d time.Duration) error {
   211  	timer := time.NewTimer(d)
   212  	defer timer.Stop()
   213  	select {
   214  	case <-c:
   215  		return nil
   216  	case <-timer.C:
   217  		return ErrTimeout
   218  	}
   219  }
   220  
   221  // MakeTestNodeConfig defines a function to return a params.NodeConfig
   222  // where specific network addresses are assigned based on provided network id.
   223  func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
   224  	testDir := filepath.Join(TestDataDir, TestNetworkNames[networkID])
   225  
   226  	if common.OperatingSystemIs(common.WindowsPlatform) {
   227  		testDir = filepath.ToSlash(testDir)
   228  	}
   229  
   230  	// run tests with "INFO" log level only
   231  	// when `go test` invoked with `-v` flag
   232  	errorLevel := "ERROR"
   233  	if testing.Verbose() {
   234  		errorLevel = "INFO"
   235  	}
   236  
   237  	configJSON := `{
   238  		"Name": "test",
   239  		"NetworkId": ` + strconv.Itoa(networkID) + `,
   240  		"RootDataDir": "` + testDir + `",
   241  		"DataDir": "` + testDir + `",
   242  		"KeyStoreDir": "` + path.Join(testDir, "keystore") + `",
   243  		"KeycardPairingDataFile": "` + path.Join(testDir, "keycard/pairings.json") + `",
   244  		"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
   245  		"WSPort": ` + strconv.Itoa(TestConfig.Node.WSPort) + `,
   246  		"LogLevel": "` + errorLevel + `",
   247  		"NoDiscovery": true,
   248  		"LightEthConfig": {
   249  			"Enabled": true
   250  		}
   251  	}`
   252  
   253  	nodeConfig, err := params.NewConfigFromJSON(configJSON)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	return nodeConfig, nil
   259  }
   260  
   261  // MakeTestNodeConfigWithDataDir defines a function to return a params.NodeConfig
   262  // where specific network addresses are assigned based on provided network id, and assigns
   263  // a given name and data dir.
   264  func MakeTestNodeConfigWithDataDir(name, dataDir string, networkID uint64) (*params.NodeConfig, error) {
   265  	cfg, err := params.NewNodeConfig(dataDir, networkID)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	if name == "" {
   270  		cfg.Name = "test"
   271  	} else {
   272  		cfg.Name = name
   273  	}
   274  	cfg.NoDiscovery = true
   275  	cfg.LightEthConfig.Enabled = false
   276  	if dataDir != "" {
   277  		cfg.KeyStoreDir = path.Join(dataDir, "keystore")
   278  	}
   279  
   280  	// Only attempt to validate if a dataDir is specified, we only support in-memory DB for tests
   281  	if dataDir != "" {
   282  		if err := cfg.Validate(); err != nil {
   283  			return nil, err
   284  		}
   285  	}
   286  
   287  	return cfg, nil
   288  }
   289  
   290  type account struct {
   291  	WalletAddress string
   292  	ChatAddress   string
   293  	Password      string
   294  }
   295  
   296  // testConfig contains shared (among different test packages) parameters
   297  type testConfig struct {
   298  	Node struct {
   299  		SyncSeconds time.Duration
   300  		HTTPPort    int
   301  		WSPort      int
   302  	}
   303  	Account1 account
   304  	Account2 account
   305  	Account3 account
   306  }
   307  
   308  const passphraseEnvName = "ACCOUNT_PASSWORD"
   309  
   310  // loadTestConfig loads test configuration values from disk
   311  func loadTestConfig() (*testConfig, error) {
   312  	var config testConfig
   313  
   314  	err := parseTestConfigFromFile("config/test-data.json", &config)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	if GetNetworkID() == params.StatusChainNetworkID {
   320  		err := parseTestConfigFromFile("config/status-chain-accounts.json", &config)
   321  		if err != nil {
   322  			return nil, err
   323  		}
   324  	} else {
   325  		err := parseTestConfigFromFile("public-chain-accounts.json", &config)
   326  		if err != nil {
   327  			return nil, err
   328  		}
   329  
   330  		pass, ok := os.LookupEnv(passphraseEnvName)
   331  		if !ok {
   332  			err := fmt.Errorf("Missing %s environment variable", passphraseEnvName)
   333  			return nil, err
   334  		}
   335  
   336  		config.Account1.Password = pass
   337  		config.Account2.Password = pass
   338  	}
   339  
   340  	return &config, nil
   341  }
   342  
   343  // ImportTestAccount imports keystore from static resources, see "static/keys" folder
   344  func ImportTestAccount(keystoreDir, accountFile string) error {
   345  	// make sure that keystore folder exists
   346  	if _, err := os.Stat(keystoreDir); os.IsNotExist(err) {
   347  		os.MkdirAll(keystoreDir, os.ModePerm) // nolint: errcheck, gas
   348  	}
   349  
   350  	var (
   351  		data []byte
   352  		err  error
   353  	)
   354  
   355  	// Allow to read keys from a custom dir.
   356  	// Fallback to embedded data.
   357  	if dir := os.Getenv("TEST_KEYS_DIR"); dir != "" {
   358  		data, err = ioutil.ReadFile(filepath.Join(dir, accountFile))
   359  	} else {
   360  		data, err = static.Asset(filepath.Join("keys", accountFile))
   361  	}
   362  
   363  	if err != nil {
   364  		return err
   365  	}
   366  
   367  	return createFile(data, filepath.Join(keystoreDir, accountFile))
   368  }
   369  
   370  func parseTestConfigFromFile(file string, config *testConfig) error {
   371  	var (
   372  		data []byte
   373  		err  error
   374  	)
   375  
   376  	// Allow to read config from a custom dir.
   377  	// Fallback to embedded data.
   378  	if dir := os.Getenv("TEST_CONFIG_DIR"); dir != "" {
   379  		data, err = ioutil.ReadFile(filepath.Join(dir, file))
   380  	} else {
   381  		data, err = t.Asset(file)
   382  	}
   383  
   384  	if err != nil {
   385  		return err
   386  	}
   387  
   388  	return json.Unmarshal(data, &config)
   389  }
   390  
   391  func createFile(data []byte, dst string) error {
   392  	out, err := os.Create(dst)
   393  	if err != nil {
   394  		return err
   395  	}
   396  	defer out.Close()
   397  
   398  	_, err = io.Copy(out, bytes.NewBuffer(data))
   399  	return err
   400  }
   401  
   402  // Eventually will raise error if condition won't be met during the given timeout.
   403  func Eventually(f func() error, timeout, period time.Duration) (err error) {
   404  	timer := time.NewTimer(timeout)
   405  	defer timer.Stop()
   406  	ticker := time.NewTicker(period)
   407  	defer ticker.Stop()
   408  	for {
   409  		select {
   410  		case <-timer.C:
   411  			return
   412  		case <-ticker.C:
   413  			err = f()
   414  			if err == nil {
   415  				return nil
   416  			}
   417  		}
   418  	}
   419  }