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 }