launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/relation/hookqueue_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package relation_test
     5  
     6  import (
     7  	gc "launchpad.net/gocheck"
     8  	stdtesting "testing"
     9  	"time"
    10  
    11  	"launchpad.net/juju-core/charm/hooks"
    12  	"launchpad.net/juju-core/state/api/params"
    13  	coretesting "launchpad.net/juju-core/testing"
    14  	"launchpad.net/juju-core/worker/uniter/hook"
    15  	"launchpad.net/juju-core/worker/uniter/relation"
    16  )
    17  
    18  func Test(t *stdtesting.T) { coretesting.MgoTestPackage(t) }
    19  
    20  type HookQueueSuite struct{}
    21  
    22  var _ = gc.Suite(&HookQueueSuite{})
    23  
    24  type msi map[string]int64
    25  
    26  type hookQueueTest struct {
    27  	summary string
    28  	initial *relation.State
    29  	steps   []checker
    30  }
    31  
    32  func fullTest(summary string, steps ...checker) hookQueueTest {
    33  	return hookQueueTest{summary, &relation.State{21345, nil, ""}, steps}
    34  }
    35  
    36  func reconcileTest(summary string, members msi, joined string, steps ...checker) hookQueueTest {
    37  	return hookQueueTest{summary, &relation.State{21345, members, joined}, steps}
    38  }
    39  
    40  var aliveHookQueueTests = []hookQueueTest{
    41  	fullTest(
    42  		"Empty initial change causes no hooks.",
    43  		send{nil, nil},
    44  	), fullTest(
    45  		"Joined and changed are both run when unit is first detected.",
    46  		send{msi{"u/0": 0}, nil},
    47  		expect{hooks.RelationJoined, "u/0", 0},
    48  		expect{hooks.RelationChanged, "u/0", 0},
    49  	), fullTest(
    50  		"Automatic changed is run with latest settings.",
    51  		send{msi{"u/0": 0}, nil},
    52  		expect{hooks.RelationJoined, "u/0", 0},
    53  		send{msi{"u/0": 7}, nil},
    54  		expect{hooks.RelationChanged, "u/0", 7},
    55  	), fullTest(
    56  		"Joined is also run with latest settings.",
    57  		send{msi{"u/0": 0}, nil},
    58  		send{msi{"u/0": 7}, nil},
    59  		expect{hooks.RelationJoined, "u/0", 7},
    60  		expect{hooks.RelationChanged, "u/0", 7},
    61  	), fullTest(
    62  		"Nothing happens if a unit departs before its joined is run.",
    63  		send{msi{"u/0": 0}, nil},
    64  		send{msi{"u/0": 7}, nil},
    65  		send{nil, []string{"u/0"}},
    66  	), fullTest(
    67  		"A changed is run after a joined, even if a departed is known.",
    68  		send{msi{"u/0": 0}, nil},
    69  		expect{hooks.RelationJoined, "u/0", 0},
    70  		send{nil, []string{"u/0"}},
    71  		expect{hooks.RelationChanged, "u/0", 0},
    72  		expect{hooks.RelationDeparted, "u/0", 0},
    73  	), fullTest(
    74  		"A departed replaces a changed.",
    75  		send{msi{"u/0": 0}, nil},
    76  		advance{2},
    77  		send{msi{"u/0": 7}, nil},
    78  		send{nil, []string{"u/0"}},
    79  		expect{hooks.RelationDeparted, "u/0", 7},
    80  	), fullTest(
    81  		"Changed events are ignored if the version has not changed.",
    82  		send{msi{"u/0": 0}, nil},
    83  		advance{2},
    84  		send{msi{"u/0": 0}, nil},
    85  	), fullTest(
    86  		"Redundant changed events are elided.",
    87  		send{msi{"u/0": 0}, nil},
    88  		advance{2},
    89  		send{msi{"u/0": 3}, nil},
    90  		send{msi{"u/0": 7}, nil},
    91  		send{msi{"u/0": 79}, nil},
    92  		expect{hooks.RelationChanged, "u/0", 79},
    93  	), fullTest(
    94  		"Latest hooks are run in the original unit order.",
    95  		send{msi{"u/0": 0, "u/1": 1}, nil},
    96  		advance{4},
    97  		send{msi{"u/0": 3}, nil},
    98  		send{msi{"u/1": 7}, nil},
    99  		send{nil, []string{"u/0"}},
   100  		expect{hooks.RelationDeparted, "u/0", 3},
   101  		expect{hooks.RelationChanged, "u/1", 7},
   102  	), fullTest(
   103  		"Test everything we can think of at the same time.",
   104  		send{msi{"u/0": 0, "u/1": 0, "u/2": 0, "u/3": 0, "u/4": 0}, nil},
   105  		advance{6},
   106  		// u/0, u/1, u/2 are now up to date; u/3, u/4 are untouched.
   107  		send{msi{"u/0": 1}, nil},
   108  		send{msi{"u/1": 1, "u/2": 1, "u/3": 1, "u/5": 0}, []string{"u/0", "u/4"}},
   109  		send{msi{"u/3": 2}, nil},
   110  		// - Finish off the rest of the initial state, ignoring u/4, but using
   111  		// the latest known settings.
   112  		expect{hooks.RelationJoined, "u/3", 2},
   113  		expect{hooks.RelationChanged, "u/3", 2},
   114  		// - u/0 was queued for change by the first RUC, but this change is
   115  		// no longer relevant; it's departed in the second RUC, so we run
   116  		// that hook instead.
   117  		expect{hooks.RelationDeparted, "u/0", 1},
   118  		// - Handle the remaining changes in the second RUC, still ignoring u/4.
   119  		// We do run new changed hooks for u/1 and u/2, because the latest settings
   120  		// are newer than those used in their original changed events.
   121  		expect{hooks.RelationChanged, "u/1", 1},
   122  		expect{hooks.RelationChanged, "u/2", 1},
   123  		expect{hooks.RelationJoined, "u/5", 0},
   124  		expect{hooks.RelationChanged, "u/5", 0},
   125  		// - Ignore the third RUC, because the original joined/changed on u/3
   126  		// was executed after we got the latest settings version.
   127  	), reconcileTest(
   128  		"Check that matching settings versions cause no changes.",
   129  		msi{"u/0": 0}, "",
   130  		send{msi{"u/0": 0}, nil},
   131  	), reconcileTest(
   132  		"Check that new settings versions cause appropriate changes.",
   133  		msi{"u/0": 0}, "",
   134  		send{msi{"u/0": 1}, nil},
   135  		expect{hooks.RelationChanged, "u/0", 1},
   136  	), reconcileTest(
   137  		"Check that a just-joined unit gets its changed hook run first.",
   138  		msi{"u/0": 0}, "u/0",
   139  		send{msi{"u/0": 0}, nil},
   140  		expect{hooks.RelationChanged, "u/0", 0},
   141  	), reconcileTest(
   142  		"Check that missing units are queued for depart as early as possible.",
   143  		msi{"u/0": 0}, "",
   144  		send{msi{"u/1": 0}, nil},
   145  		expect{hooks.RelationDeparted, "u/0", 0},
   146  		expect{hooks.RelationJoined, "u/1", 0},
   147  		expect{hooks.RelationChanged, "u/1", 0},
   148  	), reconcileTest(
   149  		"Double-check that a pending changed happens before an injected departed.",
   150  		msi{"u/0": 0}, "u/0",
   151  		send{nil, nil},
   152  		expect{hooks.RelationChanged, "u/0", 0},
   153  		expect{hooks.RelationDeparted, "u/0", 0},
   154  	), reconcileTest(
   155  		"Check that missing units don't slip in front of required changed hooks.",
   156  		msi{"u/0": 0}, "u/0",
   157  		send{msi{"u/1": 0}, nil},
   158  		expect{hooks.RelationChanged, "u/0", 0},
   159  		expect{hooks.RelationDeparted, "u/0", 0},
   160  		expect{hooks.RelationJoined, "u/1", 0},
   161  		expect{hooks.RelationChanged, "u/1", 0},
   162  	),
   163  }
   164  
   165  func (s *HookQueueSuite) TestAliveHookQueue(c *gc.C) {
   166  	for i, t := range aliveHookQueueTests {
   167  		c.Logf("test %d: %s", i, t.summary)
   168  		out := make(chan hook.Info)
   169  		in := make(chan params.RelationUnitsChange)
   170  		ruw := &RUW{in, false}
   171  		q := relation.NewAliveHookQueue(t.initial, out, ruw)
   172  		for i, step := range t.steps {
   173  			c.Logf("  step %d", i)
   174  			step.check(c, in, out)
   175  		}
   176  		expect{}.check(c, in, out)
   177  		q.Stop()
   178  		c.Assert(ruw.stopped, gc.Equals, true)
   179  	}
   180  }
   181  
   182  var dyingHookQueueTests = []hookQueueTest{
   183  	fullTest(
   184  		"Empty state just gets a broken hook.",
   185  		expect{hook: hooks.RelationBroken},
   186  	), reconcileTest(
   187  		"Each current member is departed before broken is sent.",
   188  		msi{"u/1": 7, "u/4": 33}, "",
   189  		expect{hooks.RelationDeparted, "u/1", 7},
   190  		expect{hooks.RelationDeparted, "u/4", 33},
   191  		expect{hook: hooks.RelationBroken},
   192  	), reconcileTest(
   193  		"If there's a pending changed, that must still be respected.",
   194  		msi{"u/0": 3}, "u/0",
   195  		expect{hooks.RelationChanged, "u/0", 3},
   196  		expect{hooks.RelationDeparted, "u/0", 3},
   197  		expect{hook: hooks.RelationBroken},
   198  	),
   199  }
   200  
   201  func (s *HookQueueSuite) TestDyingHookQueue(c *gc.C) {
   202  	for i, t := range dyingHookQueueTests {
   203  		c.Logf("test %d: %s", i, t.summary)
   204  		out := make(chan hook.Info)
   205  		q := relation.NewDyingHookQueue(t.initial, out)
   206  		for i, step := range t.steps {
   207  			c.Logf("  step %d", i)
   208  			step.check(c, nil, out)
   209  		}
   210  		expect{}.check(c, nil, out)
   211  		q.Stop()
   212  	}
   213  }
   214  
   215  // RUW exists entirely to send RelationUnitsChanged events to a tested
   216  // HookQueue in a synchronous and predictable fashion.
   217  type RUW struct {
   218  	in      chan params.RelationUnitsChange
   219  	stopped bool
   220  }
   221  
   222  func (w *RUW) Changes() <-chan params.RelationUnitsChange {
   223  	return w.in
   224  }
   225  
   226  func (w *RUW) Stop() error {
   227  	close(w.in)
   228  	w.stopped = true
   229  	return nil
   230  }
   231  
   232  func (w *RUW) Err() error {
   233  	return nil
   234  }
   235  
   236  type checker interface {
   237  	check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info)
   238  }
   239  
   240  type send struct {
   241  	changed  msi
   242  	departed []string
   243  }
   244  
   245  func (d send) check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) {
   246  	ruc := params.RelationUnitsChange{Changed: map[string]params.UnitSettings{}}
   247  	for name, version := range d.changed {
   248  		ruc.Changed[name] = params.UnitSettings{Version: version}
   249  	}
   250  	for _, name := range d.departed {
   251  		ruc.Departed = append(ruc.Departed, name)
   252  	}
   253  	in <- ruc
   254  }
   255  
   256  type advance struct {
   257  	count int
   258  }
   259  
   260  func (d advance) check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) {
   261  	for i := 0; i < d.count; i++ {
   262  		select {
   263  		case <-out:
   264  		case <-time.After(coretesting.LongWait):
   265  			c.Fatalf("timed out waiting for event %d", i)
   266  		}
   267  	}
   268  }
   269  
   270  type expect struct {
   271  	hook    hooks.Kind
   272  	unit    string
   273  	version int64
   274  }
   275  
   276  func (d expect) check(c *gc.C, in chan params.RelationUnitsChange, out chan hook.Info) {
   277  	if d.hook == "" {
   278  		select {
   279  		case unexpected := <-out:
   280  			c.Fatalf("got %#v", unexpected)
   281  		case <-time.After(coretesting.ShortWait):
   282  		}
   283  		return
   284  	}
   285  	expect := hook.Info{
   286  		Kind:          d.hook,
   287  		RelationId:    21345,
   288  		RemoteUnit:    d.unit,
   289  		ChangeVersion: d.version,
   290  	}
   291  	select {
   292  	case actual := <-out:
   293  		c.Assert(actual, gc.DeepEquals, expect)
   294  	case <-time.After(coretesting.LongWait):
   295  		c.Fatalf("timed out waiting for %#v", expect)
   296  	}
   297  }