diff options
author | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-11-27 17:41:22 +0800 |
---|---|---|
committer | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-11-27 17:41:22 +0800 |
commit | 7dde2b902cf81e90b484b1a48f6d45e0abd10e0f (patch) | |
tree | 9b92cb3e42269697e0a2b553ba31c36aef73cc25 /node/node_test.go | |
parent | ffe58bf5abe5100b29ac1091c882f586cd3a2ef9 (diff) | |
parent | 3e1000fda3424d880bc43ebbb16d8a33447d4182 (diff) | |
download | go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.tar go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.tar.gz go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.tar.bz2 go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.tar.lz go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.tar.xz go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.tar.zst go-tangerine-7dde2b902cf81e90b484b1a48f6d45e0abd10e0f.zip |
Merge pull request #1970 from karalabe/customizable-protocol-stacks
Customizable protocol stacks
Diffstat (limited to 'node/node_test.go')
-rw-r--r-- | node/node_test.go | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/node/node_test.go b/node/node_test.go new file mode 100644 index 000000000..ef096fc91 --- /dev/null +++ b/node/node_test.go @@ -0,0 +1,496 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package node + +import ( + "errors" + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + testNodeKey, _ = crypto.GenerateKey() + + testNodeConfig = &Config{ + PrivateKey: testNodeKey, + Name: "test node", + } +) + +// Tests that an empty protocol stack can be started, restarted and stopped. +func TestNodeLifeCycle(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Ensure that a stopped node can be stopped again + for i := 0; i < 3; i++ { + if err := stack.Stop(); err != ErrNodeStopped { + t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped) + } + } + // Ensure that a node can be successfully started, but only once + if err := stack.Start(); err != nil { + t.Fatalf("failed to start node: %v", err) + } + if err := stack.Start(); err != ErrNodeRunning { + t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) + } + // Ensure that a node can be restarted arbitrarily many times + for i := 0; i < 3; i++ { + if err := stack.Restart(); err != nil { + t.Fatalf("iter %d: failed to restart node: %v", i, err) + } + } + // Ensure that a node can be stopped, but only once + if err := stack.Stop(); err != nil { + t.Fatalf("failed to stop node: %v", err) + } + if err := stack.Stop(); err != ErrNodeStopped { + t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped) + } +} + +// Tests that if the data dir is already in use, an appropriate error is returned. +func TestNodeUsedDataDir(t *testing.T) { + // Create a temporary folder to use as the data directory + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temporary data directory: %v", err) + } + defer os.RemoveAll(dir) + + // Create a new node based on the data directory + original, err := New(&Config{DataDir: dir}) + if err != nil { + t.Fatalf("failed to create original protocol stack: %v", err) + } + if err := original.Start(); err != nil { + t.Fatalf("failed to start original protocol stack: %v", err) + } + defer original.Stop() + + // Create a second node based on the same data directory and ensure failure + duplicate, err := New(&Config{DataDir: dir}) + if err != nil { + t.Fatalf("failed to create duplicate protocol stack: %v", err) + } + if err := duplicate.Start(); err != ErrDatadirUsed { + t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed) + } +} + +// Tests whether services can be registered and duplicates caught. +func TestServiceRegistry(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of unique services and ensure they start successfully + services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC} + for i, constructor := range services { + if err := stack.Register(constructor); err != nil { + t.Fatalf("service #%d: registration failed: %v", i, err) + } + } + if err := stack.Start(); err != nil { + t.Fatalf("failed to start original service stack: %v", err) + } + if err := stack.Stop(); err != nil { + t.Fatalf("failed to stop original service stack: %v", err) + } + // Duplicate one of the services and retry starting the node + if err := stack.Register(NewNoopServiceB); err != nil { + t.Fatalf("duplicate registration failed: %v", err) + } + if err := stack.Start(); err == nil { + t.Fatalf("duplicate service started") + } else { + if _, ok := err.(*DuplicateServiceError); !ok { + t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{}) + } + } +} + +// Tests that registered services get started and stopped correctly. +func TestServiceLifeCycle(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of life-cycle instrumented services + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } + started := make(map[string]bool) + stopped := make(map[string]bool) + + for id, maker := range services { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func(*p2p.Server) { started[id] = true }, + stopHook: func() { stopped[id] = true }, + }, nil + } + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Start the node and check that all services are running + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + for id, _ := range services { + if !started[id] { + t.Fatalf("service %s: freshly started service not running", id) + } + if stopped[id] { + t.Fatalf("service %s: freshly started service already stopped", id) + } + } + // Stop the node and check that all services have been stopped + if err := stack.Stop(); err != nil { + t.Fatalf("failed to stop protocol stack: %v", err) + } + for id, _ := range services { + if !stopped[id] { + t.Fatalf("service %s: freshly terminated service still running", id) + } + } +} + +// Tests that services are restarted cleanly as new instances. +func TestServiceRestarts(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Define a service that does not support restarts + var ( + running bool + started int + ) + constructor := func(*ServiceContext) (Service, error) { + running = false + + return &InstrumentedService{ + startHook: func(*p2p.Server) { + if running { + panic("already running") + } + running = true + started++ + }, + }, nil + } + // Register the service and start the protocol stack + if err := stack.Register(constructor); err != nil { + t.Fatalf("failed to register the service: %v", err) + } + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + defer stack.Stop() + + if running != true || started != 1 { + t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started) + } + // Restart the stack a few times and check successful service restarts + for i := 0; i < 3; i++ { + if err := stack.Restart(); err != nil { + t.Fatalf("iter %d: failed to restart stack: %v", i, err) + } + } + if running != true || started != 4 { + t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started) + } +} + +// Tests that if a service fails to initialize itself, none of the other services +// will be allowed to even start. +func TestServiceConstructionAbortion(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Define a batch of good services + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } + started := make(map[string]bool) + for id, maker := range services { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func(*p2p.Server) { started[id] = true }, + }, nil + } + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Register a service that fails to construct itself + failure := errors.New("fail") + failer := func(*ServiceContext) (Service, error) { + return nil, failure + } + if err := stack.Register(failer); err != nil { + t.Fatalf("failer registration failed: %v", err) + } + // Start the protocol stack and ensure none of the services get started + for i := 0; i < 100; i++ { + if err := stack.Start(); err != failure { + t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) + } + for id, _ := range services { + if started[id] { + t.Fatalf("service %s: started should not have", id) + } + delete(started, id) + } + } +} + +// Tests that if a service fails to start, all others started before it will be +// shut down. +func TestServiceStartupAbortion(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of good services + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } + started := make(map[string]bool) + stopped := make(map[string]bool) + + for id, maker := range services { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func(*p2p.Server) { started[id] = true }, + stopHook: func() { stopped[id] = true }, + }, nil + } + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Register a service that fails to start + failure := errors.New("fail") + failer := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + start: failure, + }, nil + } + if err := stack.Register(failer); err != nil { + t.Fatalf("failer registration failed: %v", err) + } + // Start the protocol stack and ensure all started services stop + for i := 0; i < 100; i++ { + if err := stack.Start(); err != failure { + t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) + } + for id, _ := range services { + if started[id] && !stopped[id] { + t.Fatalf("service %s: started but not stopped", id) + } + delete(started, id) + delete(stopped, id) + } + } +} + +// Tests that even if a registered service fails to shut down cleanly, it does +// not influece the rest of the shutdown invocations. +func TestServiceTerminationGuarantee(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of good services + services := map[string]InstrumentingWrapper{ + "A": InstrumentedServiceMakerA, + "B": InstrumentedServiceMakerB, + "C": InstrumentedServiceMakerC, + } + started := make(map[string]bool) + stopped := make(map[string]bool) + + for id, maker := range services { + id := id // Closure for the constructor + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + startHook: func(*p2p.Server) { started[id] = true }, + stopHook: func() { stopped[id] = true }, + }, nil + } + if err := stack.Register(maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Register a service that fails to shot down cleanly + failure := errors.New("fail") + failer := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + stop: failure, + }, nil + } + if err := stack.Register(failer); err != nil { + t.Fatalf("failer registration failed: %v", err) + } + // Start the protocol stack, and ensure that a failing shut down terminates all + for i := 0; i < 100; i++ { + // Start the stack and make sure all is online + if err := stack.Start(); err != nil { + t.Fatalf("iter %d: failed to start protocol stack: %v", i, err) + } + for id, _ := range services { + if !started[id] { + t.Fatalf("iter %d, service %s: service not running", i, id) + } + if stopped[id] { + t.Fatalf("iter %d, service %s: service already stopped", i, id) + } + } + // Stop the stack, verify failure and check all terminations + err := stack.Stop() + if err, ok := err.(*StopError); !ok { + t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err) + } else { + failer := reflect.TypeOf(&InstrumentedService{}) + if err.Services[failer] != failure { + t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure) + } + if len(err.Services) != 1 { + t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1) + } + } + for id, _ := range services { + if !stopped[id] { + t.Fatalf("iter %d, service %s: service not terminated", i, id) + } + delete(started, id) + delete(stopped, id) + } + } +} + +// TestServiceRetrieval tests that individual services can be retrieved. +func TestServiceRetrieval(t *testing.T) { + // Create a simple stack and register two service types + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + if err := stack.Register(NewNoopService); err != nil { + t.Fatalf("noop service registration failed: %v", err) + } + if err := stack.Register(NewInstrumentedService); err != nil { + t.Fatalf("instrumented service registration failed: %v", err) + } + // Make sure none of the services can be retrieved until started + var noopServ *NoopService + if err := stack.Service(&noopServ); err != ErrNodeStopped { + t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) + } + var instServ *InstrumentedService + if err := stack.Service(&instServ); err != ErrNodeStopped { + t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) + } + // Start the stack and ensure everything is retrievable now + if err := stack.Start(); err != nil { + t.Fatalf("failed to start stack: %v", err) + } + defer stack.Stop() + + if err := stack.Service(&noopServ); err != nil { + t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) + } + if err := stack.Service(&instServ); err != nil { + t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) + } +} + +// Tests that all protocols defined by individual services get launched. +func TestProtocolGather(t *testing.T) { + stack, err := New(testNodeConfig) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Register a batch of services with some configured number of protocols + services := map[string]struct { + Count int + Maker InstrumentingWrapper + }{ + "Zero Protocols": {0, InstrumentedServiceMakerA}, + "Single Protocol": {1, InstrumentedServiceMakerB}, + "Many Protocols": {25, InstrumentedServiceMakerC}, + } + for id, config := range services { + protocols := make([]p2p.Protocol, config.Count) + for i := 0; i < len(protocols); i++ { + protocols[i].Name = id + protocols[i].Version = uint(i) + } + constructor := func(*ServiceContext) (Service, error) { + return &InstrumentedService{ + protocols: protocols, + }, nil + } + if err := stack.Register(config.Maker(constructor)); err != nil { + t.Fatalf("service %s: registration failed: %v", id, err) + } + } + // Start the services and ensure all protocols start successfully + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) + } + defer stack.Stop() + + protocols := stack.Server().Protocols + if len(protocols) != 26 { + t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) + } + for id, config := range services { + for ver := 0; ver < config.Count; ver++ { + launched := false + for i := 0; i < len(protocols); i++ { + if protocols[i].Name == id && protocols[i].Version == uint(ver) { + launched = true + break + } + } + if !launched { + t.Errorf("configured protocol not launched: %s v%d", id, ver) + } + } + } +} |