github.com/cheng762/platon-go@v1.8.17-0.20190529111256-7deff2d7be26/cmd/swarm/run_test.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"crypto/ecdsa"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"runtime"
    29  	"sync"
    30  	"syscall"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/docker/docker/pkg/reexec"
    35  	"github.com/PlatONnetwork/PlatON-Go/accounts"
    36  	"github.com/PlatONnetwork/PlatON-Go/accounts/keystore"
    37  	"github.com/PlatONnetwork/PlatON-Go/internal/cmdtest"
    38  	"github.com/PlatONnetwork/PlatON-Go/node"
    39  	"github.com/PlatONnetwork/PlatON-Go/p2p"
    40  	"github.com/PlatONnetwork/PlatON-Go/rpc"
    41  	"github.com/PlatONnetwork/PlatON-Go/swarm"
    42  )
    43  
    44  func init() {
    45  	// Run the app if we've been exec'd as "swarm-test" in runSwarm.
    46  	reexec.Register("swarm-test", func() {
    47  		if err := app.Run(os.Args); err != nil {
    48  			fmt.Fprintln(os.Stderr, err)
    49  			os.Exit(1)
    50  		}
    51  		os.Exit(0)
    52  	})
    53  }
    54  
    55  func TestMain(m *testing.M) {
    56  	// check if we have been reexec'd
    57  	if reexec.Init() {
    58  		return
    59  	}
    60  	os.Exit(m.Run())
    61  }
    62  
    63  func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
    64  	tt := cmdtest.NewTestCmd(t, nil)
    65  
    66  	// Boot "swarm". This actually runs the test binary but the TestMain
    67  	// function will prevent any tests from running.
    68  	tt.Run("swarm-test", args...)
    69  
    70  	return tt
    71  }
    72  
    73  type testCluster struct {
    74  	Nodes  []*testNode
    75  	TmpDir string
    76  }
    77  
    78  // newTestCluster starts a test swarm cluster of the given size.
    79  //
    80  // A temporary directory is created and each node gets a data directory inside
    81  // it.
    82  //
    83  // Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
    84  // ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
    85  // as flags).
    86  //
    87  // When starting more than one node, they are connected together using the
    88  // admin SetPeer RPC method.
    89  
    90  func newTestCluster(t *testing.T, size int) *testCluster {
    91  	cluster := &testCluster{}
    92  	defer func() {
    93  		if t.Failed() {
    94  			cluster.Shutdown()
    95  		}
    96  	}()
    97  
    98  	tmpdir, err := ioutil.TempDir("", "swarm-test")
    99  	if err != nil {
   100  		t.Fatal(err)
   101  	}
   102  	cluster.TmpDir = tmpdir
   103  
   104  	// start the nodes
   105  	cluster.StartNewNodes(t, size)
   106  
   107  	if size == 1 {
   108  		return cluster
   109  	}
   110  
   111  	// connect the nodes together
   112  	for _, node := range cluster.Nodes {
   113  		if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
   114  			t.Fatal(err)
   115  		}
   116  	}
   117  
   118  	// wait until all nodes have the correct number of peers
   119  outer:
   120  	for _, node := range cluster.Nodes {
   121  		var peers []*p2p.PeerInfo
   122  		for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
   123  			if err := node.Client.Call(&peers, "admin_peers"); err != nil {
   124  				t.Fatal(err)
   125  			}
   126  			if len(peers) == len(cluster.Nodes)-1 {
   127  				continue outer
   128  			}
   129  		}
   130  		t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
   131  	}
   132  
   133  	return cluster
   134  }
   135  
   136  func (c *testCluster) Shutdown() {
   137  	for _, node := range c.Nodes {
   138  		node.Shutdown()
   139  	}
   140  	os.RemoveAll(c.TmpDir)
   141  }
   142  
   143  func (c *testCluster) Stop() {
   144  	for _, node := range c.Nodes {
   145  		node.Shutdown()
   146  	}
   147  }
   148  
   149  func (c *testCluster) StartNewNodes(t *testing.T, size int) {
   150  	c.Nodes = make([]*testNode, 0, size)
   151  	for i := 0; i < size; i++ {
   152  		dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
   153  		if err := os.Mkdir(dir, 0700); err != nil {
   154  			t.Fatal(err)
   155  		}
   156  
   157  		node := newTestNode(t, dir)
   158  		node.Name = fmt.Sprintf("swarm%02d", i)
   159  
   160  		c.Nodes = append(c.Nodes, node)
   161  	}
   162  }
   163  
   164  func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) {
   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  		node := existingTestNode(t, dir, bzzaccount)
   169  		node.Name = fmt.Sprintf("swarm%02d", i)
   170  
   171  		c.Nodes = append(c.Nodes, node)
   172  	}
   173  }
   174  
   175  func (c *testCluster) Cleanup() {
   176  	os.RemoveAll(c.TmpDir)
   177  }
   178  
   179  type testNode struct {
   180  	Name       string
   181  	Addr       string
   182  	URL        string
   183  	Enode      string
   184  	Dir        string
   185  	IpcPath    string
   186  	PrivateKey *ecdsa.PrivateKey
   187  	Client     *rpc.Client
   188  	Cmd        *cmdtest.TestCmd
   189  }
   190  
   191  const testPassphrase = "swarm-test-passphrase"
   192  
   193  func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
   194  	// create key
   195  	conf = &node.Config{
   196  		DataDir: dir,
   197  		IPCPath: "bzzd.ipc",
   198  		NoUSB:   true,
   199  	}
   200  	n, err := node.New(conf)
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  
   209  	// use a unique IPCPath when running tests on Windows
   210  	if runtime.GOOS == "windows" {
   211  		conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
   212  	}
   213  
   214  	return conf, account
   215  }
   216  
   217  func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
   218  	conf, _ := getTestAccount(t, dir)
   219  	node := &testNode{Dir: dir}
   220  
   221  	// use a unique IPCPath when running tests on Windows
   222  	if runtime.GOOS == "windows" {
   223  		conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount)
   224  	}
   225  
   226  	// assign ports
   227  	ports, err := getAvailableTCPPorts(2)
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	p2pPort := ports[0]
   232  	httpPort := ports[1]
   233  
   234  	// start the node
   235  	node.Cmd = runSwarm(t,
   236  		"--port", p2pPort,
   237  		"--nodiscover",
   238  		"--datadir", dir,
   239  		"--ipcpath", conf.IPCPath,
   240  		"--ens-api", "",
   241  		"--bzzaccount", bzzaccount,
   242  		"--bzznetworkid", "321",
   243  		"--bzzport", httpPort,
   244  		"--verbosity", "6",
   245  	)
   246  	node.Cmd.InputLine(testPassphrase)
   247  	defer func() {
   248  		if t.Failed() {
   249  			node.Shutdown()
   250  		}
   251  	}()
   252  
   253  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   254  	defer cancel()
   255  
   256  	// ensure that all ports have active listeners
   257  	// so that the next node will not get the same
   258  	// when calling getAvailableTCPPorts
   259  	err = waitTCPPorts(ctx, ports...)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	// wait for the node to start
   265  	for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
   266  		node.Client, err = rpc.Dial(conf.IPCEndpoint())
   267  		if err == nil {
   268  			break
   269  		}
   270  	}
   271  	if node.Client == nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	// load info
   276  	var info swarm.Info
   277  	if err := node.Client.Call(&info, "bzz_info"); err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
   281  	node.URL = "http://" + node.Addr
   282  
   283  	var nodeInfo p2p.NodeInfo
   284  	if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
   288  
   289  	return node
   290  }
   291  
   292  func newTestNode(t *testing.T, dir string) *testNode {
   293  
   294  	conf, account := getTestAccount(t, dir)
   295  	ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
   296  
   297  	pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
   298  
   299  	node := &testNode{Dir: dir, PrivateKey: pk}
   300  
   301  	// assign ports
   302  	ports, err := getAvailableTCPPorts(2)
   303  	if err != nil {
   304  		t.Fatal(err)
   305  	}
   306  	p2pPort := ports[0]
   307  	httpPort := ports[1]
   308  
   309  	// start the node
   310  	node.Cmd = runSwarm(t,
   311  		"--port", p2pPort,
   312  		"--nodiscover",
   313  		"--datadir", dir,
   314  		"--ipcpath", conf.IPCPath,
   315  		"--ens-api", "",
   316  		"--bzzaccount", account.Address.String(),
   317  		"--bzznetworkid", "321",
   318  		"--bzzport", httpPort,
   319  		"--verbosity", "6",
   320  	)
   321  	node.Cmd.InputLine(testPassphrase)
   322  	defer func() {
   323  		if t.Failed() {
   324  			node.Shutdown()
   325  		}
   326  	}()
   327  
   328  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   329  	defer cancel()
   330  
   331  	// ensure that all ports have active listeners
   332  	// so that the next node will not get the same
   333  	// when calling getAvailableTCPPorts
   334  	err = waitTCPPorts(ctx, ports...)
   335  	if err != nil {
   336  		t.Fatal(err)
   337  	}
   338  
   339  	// wait for the node to start
   340  	for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
   341  		node.Client, err = rpc.Dial(conf.IPCEndpoint())
   342  		if err == nil {
   343  			break
   344  		}
   345  	}
   346  	if node.Client == nil {
   347  		t.Fatal(err)
   348  	}
   349  
   350  	// load info
   351  	var info swarm.Info
   352  	if err := node.Client.Call(&info, "bzz_info"); err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
   356  	node.URL = "http://" + node.Addr
   357  
   358  	var nodeInfo p2p.NodeInfo
   359  	if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
   360  		t.Fatal(err)
   361  	}
   362  	node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
   363  	node.IpcPath = conf.IPCPath
   364  
   365  	return node
   366  }
   367  
   368  func (n *testNode) Shutdown() {
   369  	if n.Cmd != nil {
   370  		n.Cmd.Kill()
   371  	}
   372  }
   373  
   374  // getAvailableTCPPorts returns a set of ports that
   375  // nothing is listening on at the time.
   376  //
   377  // Function assignTCPPort cannot be called in sequence
   378  // and guardantee that the same port will be returned in
   379  // different calls as the listener is closed within the function,
   380  // not after all listeners are started and selected unique
   381  // available ports.
   382  func getAvailableTCPPorts(count int) (ports []string, err error) {
   383  	for i := 0; i < count; i++ {
   384  		l, err := net.Listen("tcp", "127.0.0.1:0")
   385  		if err != nil {
   386  			return nil, err
   387  		}
   388  		// defer close in the loop to be sure the same port will not
   389  		// be selected in the next iteration
   390  		defer l.Close()
   391  
   392  		_, port, err := net.SplitHostPort(l.Addr().String())
   393  		if err != nil {
   394  			return nil, err
   395  		}
   396  		ports = append(ports, port)
   397  	}
   398  	return ports, nil
   399  }
   400  
   401  // waitTCPPorts blocks until tcp connections can be
   402  // established on all provided ports. It runs all
   403  // ports dialers in parallel, and returns the first
   404  // encountered error.
   405  // See waitTCPPort also.
   406  func waitTCPPorts(ctx context.Context, ports ...string) error {
   407  	var err error
   408  	// mu locks err variable that is assigned in
   409  	// other goroutines
   410  	var mu sync.Mutex
   411  
   412  	// cancel is canceling all goroutines
   413  	// when the firs error is returned
   414  	// to prevent unnecessary waiting
   415  	ctx, cancel := context.WithCancel(ctx)
   416  	defer cancel()
   417  
   418  	var wg sync.WaitGroup
   419  	for _, port := range ports {
   420  		wg.Add(1)
   421  		go func(port string) {
   422  			defer wg.Done()
   423  
   424  			e := waitTCPPort(ctx, port)
   425  
   426  			mu.Lock()
   427  			defer mu.Unlock()
   428  			if e != nil && err == nil {
   429  				err = e
   430  				cancel()
   431  			}
   432  		}(port)
   433  	}
   434  	wg.Wait()
   435  
   436  	return err
   437  }
   438  
   439  // waitTCPPort blocks until tcp connection can be established
   440  // ona provided port. It has a 3 minute timeout as maximum,
   441  // to prevent long waiting, but it can be shortened with
   442  // a provided context instance. Dialer has a 10 second timeout
   443  // in every iteration, and connection refused error will be
   444  // retried in 100 milliseconds periods.
   445  func waitTCPPort(ctx context.Context, port string) error {
   446  	ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
   447  	defer cancel()
   448  
   449  	for {
   450  		c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port)
   451  		if err != nil {
   452  			if operr, ok := err.(*net.OpError); ok {
   453  				if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED {
   454  					time.Sleep(100 * time.Millisecond)
   455  					continue
   456  				}
   457  			}
   458  			return err
   459  		}
   460  		return c.Close()
   461  	}
   462  }