github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/singular/singular_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package singular_test
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/testing"
    15  	"github.com/juju/juju/worker"
    16  	"github.com/juju/juju/worker/singular"
    17  )
    18  
    19  type singularSuite struct {
    20  	testing.BaseSuite
    21  }
    22  
    23  var _ = gc.Suite(&singularSuite{})
    24  
    25  func (*singularSuite) TestWithMasterError(c *gc.C) {
    26  	expectErr := fmt.Errorf("an error")
    27  	conn := &fakeConn{
    28  		isMasterErr: expectErr,
    29  	}
    30  	r, err := singular.New(newRunner(), conn)
    31  	c.Check(err, gc.ErrorMatches, "cannot get master status: an error")
    32  	c.Check(r, gc.IsNil)
    33  }
    34  
    35  func (s *singularSuite) TestWithIsMasterTrue(c *gc.C) {
    36  	// When IsMaster returns true, workers get started on the underlying
    37  	// runner as usual.
    38  	s.PatchValue(&singular.PingInterval, 1*time.Millisecond)
    39  	underlyingRunner := newRunner()
    40  	conn := &fakeConn{
    41  		isMaster: true,
    42  	}
    43  	r, err := singular.New(underlyingRunner, conn)
    44  	c.Assert(err, jc.ErrorIsNil)
    45  
    46  	started := make(chan struct{}, 1)
    47  	err = r.StartWorker("worker", func() (worker.Worker, error) {
    48  		return worker.NewSimpleWorker(func(stop <-chan struct{}) error {
    49  			started <- struct{}{}
    50  			<-stop
    51  			return nil
    52  		}), nil
    53  	})
    54  	select {
    55  	case <-started:
    56  	case <-time.After(testing.LongWait):
    57  		c.Fatalf("timed out waiting for worker to start")
    58  	}
    59  
    60  	err = worker.Stop(r)
    61  	c.Assert(err, jc.ErrorIsNil)
    62  }
    63  
    64  var errFatal = fmt.Errorf("fatal error")
    65  
    66  func (s *singularSuite) TestWithIsMasterFalse(c *gc.C) {
    67  	// When IsMaster returns false, dummy workers are started that
    68  	// do nothing except wait for the pinger to return an error.
    69  
    70  	s.PatchValue(&singular.PingInterval, testing.ShortWait/10)
    71  
    72  	underlyingRunner := newRunner()
    73  	conn := &fakeConn{
    74  		isMaster: false,
    75  		pinged:   make(chan struct{}, 5),
    76  	}
    77  	r, err := singular.New(underlyingRunner, conn)
    78  	c.Assert(err, jc.ErrorIsNil)
    79  
    80  	err = r.StartWorker("worker", func() (worker.Worker, error) {
    81  		c.Errorf("worker unexpectedly started")
    82  		return nil, fmt.Errorf("no worker")
    83  	})
    84  	c.Assert(err, jc.ErrorIsNil)
    85  
    86  	timeout := time.NewTimer(testing.LongWait)
    87  	for i := 0; i < cap(conn.pinged); i++ {
    88  		select {
    89  		case <-conn.pinged:
    90  		case <-timeout.C:
    91  			c.Fatalf("timed out waiting for ping")
    92  		}
    93  	}
    94  	// Cause the ping to return an error; the underlying runner
    95  	// should then exit with the error it returned, because it's
    96  	// fatal.
    97  	conn.setPingErr(errFatal)
    98  	runWithTimeout(c, "wait for underlying runner", func() {
    99  		err = underlyingRunner.Wait()
   100  	})
   101  	c.Assert(err, gc.Equals, errFatal)
   102  
   103  	// Make sure there are no more pings after the ping interval by
   104  	// draining the channel and then making sure that at most
   105  	// one ping arrives within the next ShortWait.
   106  loop1:
   107  	for {
   108  		select {
   109  		case <-conn.pinged:
   110  		default:
   111  			break loop1
   112  		}
   113  	}
   114  	timeout.Reset(testing.ShortWait)
   115  	n := 0
   116  loop2:
   117  	for {
   118  		select {
   119  		case <-conn.pinged:
   120  			c.Assert(n, jc.LessThan, 2)
   121  			n++
   122  		case <-timeout.C:
   123  			break loop2
   124  		}
   125  	}
   126  }
   127  
   128  func (s *singularSuite) TestPingCalledOnceOnlyForSeveralWorkers(c *gc.C) {
   129  	// Patch the ping interval to a large value, start several workers
   130  	// and check that Ping is only called once.
   131  	s.PatchValue(&singular.PingInterval, testing.LongWait)
   132  
   133  	underlyingRunner := newRunner()
   134  	conn := &fakeConn{
   135  		isMaster: false,
   136  		pinged:   make(chan struct{}, 2),
   137  	}
   138  
   139  	r, err := singular.New(underlyingRunner, conn)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  
   142  	for i := 0; i < 5; i++ {
   143  		name := fmt.Sprint("worker", i)
   144  		err := r.StartWorker(name, func() (worker.Worker, error) {
   145  			c.Errorf("worker unexpectedly started")
   146  			return nil, fmt.Errorf("no worker")
   147  		})
   148  		c.Assert(err, jc.ErrorIsNil)
   149  	}
   150  	time.Sleep(testing.ShortWait)
   151  	n := 0
   152  loop:
   153  	for {
   154  		select {
   155  		case <-conn.pinged:
   156  			n++
   157  		default:
   158  			break loop
   159  		}
   160  	}
   161  	c.Assert(n, gc.Equals, 1)
   162  }
   163  
   164  func newRunner() worker.Runner {
   165  	return worker.NewRunner(
   166  		func(err error) bool {
   167  			return err == errFatal
   168  		},
   169  		func(err0, err1 error) bool { return true },
   170  		worker.RestartDelay,
   171  	)
   172  }
   173  
   174  type fakeConn struct {
   175  	isMaster    bool
   176  	isMasterErr error
   177  
   178  	pinged chan struct{}
   179  
   180  	mu      sync.Mutex
   181  	pingErr error
   182  }
   183  
   184  func (c *fakeConn) IsMaster() (bool, error) {
   185  	return c.isMaster, c.isMasterErr
   186  }
   187  
   188  func (c *fakeConn) Ping() error {
   189  	c.mu.Lock()
   190  	defer c.mu.Unlock()
   191  	select {
   192  	case c.pinged <- struct{}{}:
   193  	default:
   194  	}
   195  	return c.pingErr
   196  }
   197  
   198  func (c *fakeConn) setPingErr(err error) {
   199  	c.mu.Lock()
   200  	defer c.mu.Unlock()
   201  	c.pingErr = err
   202  }
   203  
   204  func runWithTimeout(c *gc.C, description string, f func()) {
   205  	done := make(chan struct{})
   206  	go func() {
   207  		f()
   208  		close(done)
   209  	}()
   210  	select {
   211  	case <-done:
   212  		return
   213  	case <-time.After(testing.LongWait):
   214  		c.Fatalf("time out, %s", description)
   215  	}
   216  }