aboutsummaryrefslogblamecommitdiffstats
path: root/cmd/swarm/run_test.go
blob: 2d32a51c8d99bac344573e37b98056dcbbea8b51 (plain) (tree)






























































































































































































































































                                                                                                                             
// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.

package main

import (
    "fmt"
    "io/ioutil"
    "net"
    "os"
    "path/filepath"
    "runtime"
    "testing"
    "time"

    "github.com/docker/docker/pkg/reexec"
    "github.com/ethereum/go-ethereum/accounts/keystore"
    "github.com/ethereum/go-ethereum/internal/cmdtest"
    "github.com/ethereum/go-ethereum/node"
    "github.com/ethereum/go-ethereum/p2p"
    "github.com/ethereum/go-ethereum/rpc"
    "github.com/ethereum/go-ethereum/swarm"
)

func init() {
    // Run the app if we've been exec'd as "swarm-test" in runSwarm.
    reexec.Register("swarm-test", func() {
        if err := app.Run(os.Args); err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        os.Exit(0)
    })
}

func TestMain(m *testing.M) {
    // check if we have been reexec'd
    if reexec.Init() {
        return
    }
    os.Exit(m.Run())
}

func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
    tt := cmdtest.NewTestCmd(t, nil)

    // Boot "swarm". This actually runs the test binary but the TestMain
    // function will prevent any tests from running.
    tt.Run("swarm-test", args...)

    return tt
}

type testCluster struct {
    Nodes  []*testNode
    TmpDir string
}

// newTestCluster starts a test swarm cluster of the given size.
//
// A temporary directory is created and each node gets a data directory inside
// it.
//
// Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
// ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
// as flags).
//
// When starting more than one node, they are connected together using the
// admin SetPeer RPC method.
func newTestCluster(t *testing.T, size int) *testCluster {
    cluster := &testCluster{}
    defer func() {
        if t.Failed() {
            cluster.Shutdown()
        }
    }()

    tmpdir, err := ioutil.TempDir("", "swarm-test")
    if err != nil {
        t.Fatal(err)
    }
    cluster.TmpDir = tmpdir

    // start the nodes
    cluster.Nodes = make([]*testNode, 0, size)
    for i := 0; i < size; i++ {
        dir := filepath.Join(cluster.TmpDir, fmt.Sprintf("swarm%02d", i))
        if err := os.Mkdir(dir, 0700); err != nil {
            t.Fatal(err)
        }

        node := newTestNode(t, dir)
        node.Name = fmt.Sprintf("swarm%02d", i)

        cluster.Nodes = append(cluster.Nodes, node)
    }

    if size == 1 {
        return cluster
    }

    // connect the nodes together
    for _, node := range cluster.Nodes {
        if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
            t.Fatal(err)
        }
    }

    // wait until all nodes have the correct number of peers
outer:
    for _, node := range cluster.Nodes {
        var peers []*p2p.PeerInfo
        for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
            if err := node.Client.Call(&peers, "admin_peers"); err != nil {
                t.Fatal(err)
            }
            if len(peers) == len(cluster.Nodes)-1 {
                continue outer
            }
        }
        t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
    }

    return cluster
}

func (c *testCluster) Shutdown() {
    for _, node := range c.Nodes {
        node.Shutdown()
    }
    os.RemoveAll(c.TmpDir)
}

type testNode struct {
    Name   string
    Addr   string
    URL    string
    Enode  string
    Dir    string
    Client *rpc.Client
    Cmd    *cmdtest.TestCmd
}

const testPassphrase = "swarm-test-passphrase"

func newTestNode(t *testing.T, dir string) *testNode {
    // create key
    conf := &node.Config{
        DataDir: dir,
        IPCPath: "bzzd.ipc",
    }
    n, err := node.New(conf)
    if err != nil {
        t.Fatal(err)
    }
    account, err := n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
    if err != nil {
        t.Fatal(err)
    }

    node := &testNode{Dir: dir}

    // use a unique IPCPath when running tests on Windows
    if runtime.GOOS == "windows" {
        conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
    }

    // assign ports
    httpPort, err := assignTCPPort()
    if err != nil {
        t.Fatal(err)
    }
    p2pPort, err := assignTCPPort()
    if err != nil {
        t.Fatal(err)
    }

    // start the node
    node.Cmd = runSwarm(t,
        "--port", p2pPort,
        "--nodiscover",
        "--datadir", dir,
        "--ipcpath", conf.IPCPath,
        "--ethapi", "",
        "--bzzaccount", account.Address.String(),
        "--bzznetworkid", "321",
        "--bzzport", httpPort,
        "--verbosity", "6",
    )
    node.Cmd.InputLine(testPassphrase)
    defer func() {
        if t.Failed() {
            node.Shutdown()
        }
    }()

    // wait for the node to start
    for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
        node.Client, err = rpc.Dial(conf.IPCEndpoint())
        if err == nil {
            break
        }
    }
    if node.Client == nil {
        t.Fatal(err)
    }

    // load info
    var info swarm.Info
    if err := node.Client.Call(&info, "bzz_info"); err != nil {
        t.Fatal(err)
    }
    node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
    node.URL = "http://" + node.Addr

    var nodeInfo p2p.NodeInfo
    if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
        t.Fatal(err)
    }
    node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)

    return node
}

func (n *testNode) Shutdown() {
    if n.Cmd != nil {
        n.Cmd.Kill()
    }
}

func assignTCPPort() (string, error) {
    l, err := net.Listen("tcp", "127.0.0.1:0")
    if err != nil {
        return "", err
    }
    l.Close()
    _, port, err := net.SplitHostPort(l.Addr().String())
    if err != nil {
        return "", err
    }
    return port, nil
}