github.com/decred/dcrlnd@v0.7.6/cluster/etcd_elector_test.go (about)

     1  //go:build kvdb_etcd
     2  // +build kvdb_etcd
     3  
     4  package cluster
     5  
     6  import (
     7  	"context"
     8  	"io/ioutil"
     9  	"os"
    10  	"runtime/pprof"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/decred/dcrlnd/kvdb/etcd"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  // GuardTimeout implements a test level timeout guard.
    20  func GuardTimeout(t *testing.T, timeout time.Duration) func() {
    21  	done := make(chan struct{})
    22  	go func() {
    23  		select {
    24  		case <-time.After(timeout):
    25  			err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
    26  			require.NoError(t, err)
    27  			panic("test timeout")
    28  
    29  		case <-done:
    30  		}
    31  	}()
    32  
    33  	return func() {
    34  		close(done)
    35  	}
    36  }
    37  
    38  // TestEtcdElector tests that two candidates competing for leadership works as
    39  // expected and that elected leader can resign and allow others to take on.
    40  func TestEtcdElector(t *testing.T) {
    41  	guard := GuardTimeout(t, 5*time.Second)
    42  	defer guard()
    43  
    44  	tmpDir, err := ioutil.TempDir("", "etcd")
    45  	if err != nil {
    46  		t.Fatalf("unable to create temp dir: %v", err)
    47  	}
    48  
    49  	etcdCfg, cleanup, err := etcd.NewEmbeddedEtcdInstance(tmpDir, 0, 0, "")
    50  	require.NoError(t, err)
    51  	defer cleanup()
    52  
    53  	ctx, cancel := context.WithCancel(context.Background())
    54  	defer cancel()
    55  
    56  	const (
    57  		election = "/election/"
    58  		id1      = "e1"
    59  		id2      = "e2"
    60  		ttl      = 5
    61  	)
    62  
    63  	e1, err := newEtcdLeaderElector(
    64  		ctx, id1, election, ttl, etcdCfg,
    65  	)
    66  	require.NoError(t, err)
    67  
    68  	e2, err := newEtcdLeaderElector(
    69  		ctx, id2, election, ttl, etcdCfg,
    70  	)
    71  	require.NoError(t, err)
    72  
    73  	var wg sync.WaitGroup
    74  	ch := make(chan *etcdLeaderElector)
    75  
    76  	wg.Add(2)
    77  	ctxb := context.Background()
    78  
    79  	go func() {
    80  		defer wg.Done()
    81  		require.NoError(t, e1.Campaign(ctxb))
    82  		ch <- e1
    83  	}()
    84  
    85  	go func() {
    86  		defer wg.Done()
    87  		require.NoError(t, e2.Campaign(ctxb))
    88  		ch <- e2
    89  	}()
    90  
    91  	tmp := <-ch
    92  	first, err := tmp.Leader(ctxb)
    93  	require.NoError(t, err)
    94  	require.NoError(t, tmp.Resign())
    95  
    96  	tmp = <-ch
    97  	second, err := tmp.Leader(ctxb)
    98  	require.NoError(t, err)
    99  	require.NoError(t, tmp.Resign())
   100  
   101  	require.Contains(t, []string{id1, id2}, first)
   102  	require.Contains(t, []string{id1, id2}, second)
   103  	require.NotEqual(t, first, second)
   104  
   105  	wg.Wait()
   106  }