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