github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/cmd/swarm/run_test.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:33</date> 10 //</624450071349432320> 11 12 13 package main 14 15 import ( 16 "context" 17 "crypto/ecdsa" 18 "flag" 19 "fmt" 20 "io/ioutil" 21 "net" 22 "os" 23 "path" 24 "path/filepath" 25 "runtime" 26 "sync" 27 "syscall" 28 "testing" 29 "time" 30 31 "github.com/docker/docker/pkg/reexec" 32 "github.com/ethereum/go-ethereum/accounts" 33 "github.com/ethereum/go-ethereum/accounts/keystore" 34 "github.com/ethereum/go-ethereum/internal/cmdtest" 35 "github.com/ethereum/go-ethereum/node" 36 "github.com/ethereum/go-ethereum/p2p" 37 "github.com/ethereum/go-ethereum/rpc" 38 "github.com/ethereum/go-ethereum/swarm" 39 "github.com/ethereum/go-ethereum/swarm/api" 40 swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http" 41 ) 42 43 var loglevel = flag.Int("loglevel", 3, "verbosity of logs") 44 45 func init() { 46 //如果我们在run swarm中被执行为“swarm测试”,就运行这个应用程序。 47 reexec.Register("swarm-test", func() { 48 if err := app.Run(os.Args); err != nil { 49 fmt.Fprintln(os.Stderr, err) 50 os.Exit(1) 51 } 52 os.Exit(0) 53 }) 54 } 55 56 const clusterSize = 3 57 58 var clusteronce sync.Once 59 var cluster *testCluster 60 61 func initCluster(t *testing.T) { 62 clusteronce.Do(func() { 63 cluster = newTestCluster(t, clusterSize) 64 }) 65 } 66 67 func serverFunc(api *api.API) swarmhttp.TestServer { 68 return swarmhttp.NewServer(api, "") 69 } 70 func TestMain(m *testing.M) { 71 //检查我们是否被重新执行了 72 if reexec.Init() { 73 return 74 } 75 os.Exit(m.Run()) 76 } 77 78 func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd { 79 tt := cmdtest.NewTestCmd(t, nil) 80 81 //引导“蜂群”。这实际上运行了测试二进制文件,但是testmain 82 //函数将阻止任何测试运行。 83 tt.Run("swarm-test", args...) 84 85 return tt 86 } 87 88 type testCluster struct { 89 Nodes []*testNode 90 TmpDir string 91 } 92 93 //newtestcluster启动一个给定大小的测试群集群。 94 // 95 //创建一个临时目录,每个节点在其中获取一个数据目录 96 //它。 97 // 98 //每个节点在127.0.0.1上侦听HTTP和P2P的随机端口 99 //端口(通过首先监听127.0.0.1:0,然后通过端口分配 100 //作为旗帜)。 101 // 102 //当启动多个节点时,使用 103 //admin setpeer rpc方法。 104 105 func newTestCluster(t *testing.T, size int) *testCluster { 106 cluster := &testCluster{} 107 defer func() { 108 if t.Failed() { 109 cluster.Shutdown() 110 } 111 }() 112 113 tmpdir, err := ioutil.TempDir("", "swarm-test") 114 if err != nil { 115 t.Fatal(err) 116 } 117 cluster.TmpDir = tmpdir 118 119 //启动节点 120 cluster.StartNewNodes(t, size) 121 122 if size == 1 { 123 return cluster 124 } 125 126 //将节点连接在一起 127 for _, node := range cluster.Nodes { 128 if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil { 129 t.Fatal(err) 130 } 131 } 132 133 //等待所有节点具有正确的对等数 134 outer: 135 for _, node := range cluster.Nodes { 136 var peers []*p2p.PeerInfo 137 for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) { 138 if err := node.Client.Call(&peers, "admin_peers"); err != nil { 139 t.Fatal(err) 140 } 141 if len(peers) == len(cluster.Nodes)-1 { 142 continue outer 143 } 144 } 145 t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1) 146 } 147 148 return cluster 149 } 150 151 func (c *testCluster) Shutdown() { 152 for _, node := range c.Nodes { 153 node.Shutdown() 154 } 155 os.RemoveAll(c.TmpDir) 156 } 157 158 func (c *testCluster) Stop() { 159 for _, node := range c.Nodes { 160 node.Shutdown() 161 } 162 } 163 164 func (c *testCluster) StartNewNodes(t *testing.T, size int) { 165 c.Nodes = make([]*testNode, 0, size) 166 for i := 0; i < size; i++ { 167 dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i)) 168 if err := os.Mkdir(dir, 0700); err != nil { 169 t.Fatal(err) 170 } 171 172 node := newTestNode(t, dir) 173 node.Name = fmt.Sprintf("swarm%02d", i) 174 175 c.Nodes = append(c.Nodes, node) 176 } 177 } 178 179 func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) { 180 c.Nodes = make([]*testNode, 0, size) 181 for i := 0; i < size; i++ { 182 dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i)) 183 node := existingTestNode(t, dir, bzzaccount) 184 node.Name = fmt.Sprintf("swarm%02d", i) 185 186 c.Nodes = append(c.Nodes, node) 187 } 188 } 189 190 func (c *testCluster) Cleanup() { 191 os.RemoveAll(c.TmpDir) 192 } 193 194 type testNode struct { 195 Name string 196 Addr string 197 URL string 198 Enode string 199 Dir string 200 IpcPath string 201 PrivateKey *ecdsa.PrivateKey 202 Client *rpc.Client 203 Cmd *cmdtest.TestCmd 204 } 205 206 const testPassphrase = "swarm-test-passphrase" 207 208 func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) { 209 //创建密钥 210 conf = &node.Config{ 211 DataDir: dir, 212 IPCPath: "bzzd.ipc", 213 NoUSB: true, 214 } 215 n, err := node.New(conf) 216 if err != nil { 217 t.Fatal(err) 218 } 219 account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase) 220 if err != nil { 221 t.Fatal(err) 222 } 223 224 //在Windows上运行测试时使用唯一的ipcpath 225 if runtime.GOOS == "windows" { 226 conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String()) 227 } 228 229 return conf, account 230 } 231 232 func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode { 233 conf, _ := getTestAccount(t, dir) 234 node := &testNode{Dir: dir} 235 236 //在Windows上运行测试时使用唯一的ipcpath 237 if runtime.GOOS == "windows" { 238 conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount) 239 } 240 241 //指定端口 242 ports, err := getAvailableTCPPorts(2) 243 if err != nil { 244 t.Fatal(err) 245 } 246 p2pPort := ports[0] 247 httpPort := ports[1] 248 249 //启动节点 250 node.Cmd = runSwarm(t, 251 "--port", p2pPort, 252 "--nat", "extip:127.0.0.1", 253 "--nodiscover", 254 "--datadir", dir, 255 "--ipcpath", conf.IPCPath, 256 "--ens-api", "", 257 "--bzzaccount", bzzaccount, 258 "--bzznetworkid", "321", 259 "--bzzport", httpPort, 260 "--verbosity", fmt.Sprint(*loglevel), 261 ) 262 node.Cmd.InputLine(testPassphrase) 263 defer func() { 264 if t.Failed() { 265 node.Shutdown() 266 } 267 }() 268 269 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 270 defer cancel() 271 272 //确保所有端口都有活动的侦听器 273 //这样下一个节点就不会得到相同的 274 //调用GetAvailableTCPPorts时 275 err = waitTCPPorts(ctx, ports...) 276 if err != nil { 277 t.Fatal(err) 278 } 279 280 //等待节点启动 281 for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { 282 node.Client, err = rpc.Dial(conf.IPCEndpoint()) 283 if err == nil { 284 break 285 } 286 } 287 if node.Client == nil { 288 t.Fatal(err) 289 } 290 291 //加载信息 292 var info swarm.Info 293 if err := node.Client.Call(&info, "bzz_info"); err != nil { 294 t.Fatal(err) 295 } 296 node.Addr = net.JoinHostPort("127.0.0.1", info.Port) 297 node.URL = "http://“+No.ADDR” 298 299 var nodeInfo p2p.NodeInfo 300 if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil { 301 t.Fatal(err) 302 } 303 node.Enode = nodeInfo.Enode 304 node.IpcPath = conf.IPCPath 305 return node 306 } 307 308 func newTestNode(t *testing.T, dir string) *testNode { 309 310 conf, account := getTestAccount(t, dir) 311 ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1) 312 313 pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase}) 314 315 node := &testNode{Dir: dir, PrivateKey: pk} 316 317 //指定端口 318 ports, err := getAvailableTCPPorts(2) 319 if err != nil { 320 t.Fatal(err) 321 } 322 p2pPort := ports[0] 323 httpPort := ports[1] 324 325 //启动节点 326 node.Cmd = runSwarm(t, 327 "--port", p2pPort, 328 "--nat", "extip:127.0.0.1", 329 "--nodiscover", 330 "--datadir", dir, 331 "--ipcpath", conf.IPCPath, 332 "--ens-api", "", 333 "--bzzaccount", account.Address.String(), 334 "--bzznetworkid", "321", 335 "--bzzport", httpPort, 336 "--verbosity", fmt.Sprint(*loglevel), 337 ) 338 node.Cmd.InputLine(testPassphrase) 339 defer func() { 340 if t.Failed() { 341 node.Shutdown() 342 } 343 }() 344 345 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 346 defer cancel() 347 348 //确保所有端口都有活动的侦听器 349 //这样下一个节点就不会得到相同的 350 //调用GetAvailableTCPPorts时 351 err = waitTCPPorts(ctx, ports...) 352 if err != nil { 353 t.Fatal(err) 354 } 355 356 //等待节点启动 357 for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { 358 node.Client, err = rpc.Dial(conf.IPCEndpoint()) 359 if err == nil { 360 break 361 } 362 } 363 if node.Client == nil { 364 t.Fatal(err) 365 } 366 367 //加载信息 368 var info swarm.Info 369 if err := node.Client.Call(&info, "bzz_info"); err != nil { 370 t.Fatal(err) 371 } 372 node.Addr = net.JoinHostPort("127.0.0.1", info.Port) 373 node.URL = "http://“+No.ADDR” 374 375 var nodeInfo p2p.NodeInfo 376 if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil { 377 t.Fatal(err) 378 } 379 node.Enode = nodeInfo.Enode 380 node.IpcPath = conf.IPCPath 381 return node 382 } 383 384 func (n *testNode) Shutdown() { 385 if n.Cmd != nil { 386 n.Cmd.Kill() 387 } 388 } 389 390 //GetAvailableTCPPorts返回一组端口, 391 //当时什么都没听。 392 // 393 //不能按顺序调用函数assigntcpport 394 //并保证同一港口将被运回 395 //不同的调用,因为侦听器在函数内关闭, 396 //不是在所有侦听器启动并选择唯一之后 397 //可用端口。 398 func getAvailableTCPPorts(count int) (ports []string, err error) { 399 for i := 0; i < count; i++ { 400 l, err := net.Listen("tcp", "127.0.0.1:0") 401 if err != nil { 402 return nil, err 403 } 404 //在循环中延迟关闭以确保同一端口不会 405 //在下一个迭代中被选择 406 defer l.Close() 407 408 _, port, err := net.SplitHostPort(l.Addr().String()) 409 if err != nil { 410 return nil, err 411 } 412 ports = append(ports, port) 413 } 414 return ports, nil 415 } 416 417 //waittcpports将阻止,直到可以 418 //在所有提供的端口上建立。它运行所有 419 //并行端口拨号程序,并返回第一个 420 //遇到错误。 421 //另请参见waitcpport。 422 func waitTCPPorts(ctx context.Context, ports ...string) error { 423 var err error 424 //在中分配的mu locks err变量 425 //其他Goroutines 426 var mu sync.Mutex 427 428 //取消是取消所有goroutine 429 // 430 //防止不必要的等待 431 ctx, cancel := context.WithCancel(ctx) 432 defer cancel() 433 434 var wg sync.WaitGroup 435 for _, port := range ports { 436 wg.Add(1) 437 go func(port string) { 438 defer wg.Done() 439 440 e := waitTCPPort(ctx, port) 441 442 mu.Lock() 443 defer mu.Unlock() 444 if e != nil && err == nil { 445 err = e 446 cancel() 447 } 448 }(port) 449 } 450 wg.Wait() 451 452 return err 453 } 454 455 //waittcpport阻止,直到可以建立TCP连接 456 //ONA提供的端口。它最多有3分钟的超时时间, 457 // 458 //提供的上下文实例。拨号程序超时10秒 459 //在每次迭代中,连接被拒绝的错误将 460 //在100毫秒内重试。 461 func waitTCPPort(ctx context.Context, port string) error { 462 ctx, cancel := context.WithTimeout(ctx, 3*time.Minute) 463 defer cancel() 464 465 for { 466 c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port) 467 if err != nil { 468 if operr, ok := err.(*net.OpError); ok { 469 if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED { 470 time.Sleep(100 * time.Millisecond) 471 continue 472 } 473 } 474 return err 475 } 476 return c.Close() 477 } 478 } 479