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