engine: resources: Add CheckApply event detection to resource tests

This adds the ability to wait with a timeout for CheckApply happenings
in a resource. This helps avoid unnecessary long sleeping and timing
guesses. This also adds a cleanup function to run at the end.
This commit is contained in:
James Shubin
2019-02-24 12:08:22 -05:00
parent 652b657809
commit ae7ebeedd1

View File

@@ -22,6 +22,7 @@ package resources
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@@ -92,6 +93,65 @@ func NewStartupStep(ms uint) Step {
} }
} }
type changedStep struct {
ms uint
expect bool // what checkOK value we're expecting
ch chan bool // set by test harness, filled with checkOK values
}
func (obj *changedStep) Action() error {
select {
case checkOK, ok := <-obj.ch: // from CheckApply() in test Process loop
if !ok {
return fmt.Errorf("channel closed unexpectedly")
}
if checkOK != obj.expect {
return fmt.Errorf("got unexpected checkOK value of: %t", checkOK)
}
case <-time.After(time.Duration(obj.ms) * time.Millisecond):
return fmt.Errorf("took too long to startup")
}
return nil
}
func (obj *changedStep) Expect() error { return nil }
// NewChangedStep waits up to this many ms for a CheckApply action to occur. Watch function to startup.
func NewChangedStep(ms uint, expect bool) Step {
return &changedStep{
ms: ms,
expect: expect,
}
}
type clearChangedStep struct {
ms uint
ch chan bool // set by test harness, filled with checkOK values
}
func (obj *clearChangedStep) Action() error {
// read all pending events...
for {
select {
case _, ok := <-obj.ch: // from CheckApply() in test Process loop
if !ok {
return fmt.Errorf("channel closed unexpectedly")
}
case <-time.After(time.Duration(obj.ms) * time.Millisecond):
return nil // done waiting
}
}
}
func (obj *clearChangedStep) Expect() error { return nil }
// NewClearChangedStep waits up to this many ms for additional CheckApply
// actions to occur, and flushes them all so that a future NewChangedStep won't
// see unwanted events.
func NewClearChangedStep(ms uint) Step {
return &clearChangedStep{
ms: ms,
}
}
func TestResources1(t *testing.T) { func TestResources1(t *testing.T) {
type test struct { // an individual test type test struct { // an individual test
name string name string
@@ -101,6 +161,7 @@ func TestResources1(t *testing.T) {
experrstr string // expected error prefix experrstr string // expected error prefix
timeline []Step // TODO: this could be a generator that keeps pushing out steps until it's done! timeline []Step // TODO: this could be a generator that keeps pushing out steps until it's done!
expect func() error // function to check for expected state expect func() error // function to check for expected state
cleanup func() error // function to run as cleanup
} }
// helpers // helpers
@@ -152,11 +213,14 @@ func TestResources1(t *testing.T) {
r.Content = &contents r.Content = &contents
timeline := []Step{ timeline := []Step{
NewStartupStep(3000), // startup NewStartupStep(1000 * 60), // startup
NewChangedStep(1000*60, false), // did we do something?
fileExpect(p, s), // check initial state fileExpect(p, s), // check initial state
NewClearChangedStep(1000 * 15), // did we do something?
fileWrite(p, "this is whatever\n"), // change state fileWrite(p, "this is whatever\n"), // change state
sleep(1000), // wait for converge NewChangedStep(1000*60, false), // did we do something?
fileExpect(p, s), // check again fileExpect(p, s), // check again
sleep(1), // we can sleep too!
} }
testCases = append(testCases, test{ testCases = append(testCases, test{
@@ -165,6 +229,7 @@ func TestResources1(t *testing.T) {
fail: false, fail: false,
timeline: timeline, timeline: timeline,
expect: func() error { return nil }, expect: func() error { return nil },
cleanup: func() error { return os.Remove(p) },
}) })
} }
@@ -180,7 +245,7 @@ func TestResources1(t *testing.T) {
} }
names = append(names, tc.name) names = append(names, tc.name)
t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) { t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) {
res, fail, experr, experrstr, timeline, expect := tc.res, tc.fail, tc.experr, tc.experrstr, tc.timeline, tc.expect res, fail, experr, experrstr, timeline, expect, cleanup := tc.res, tc.fail, tc.experr, tc.experrstr, tc.timeline, tc.expect, tc.cleanup
t.Logf("\n\ntest #%d: Res: %+v\n", index, res) t.Logf("\n\ntest #%d: Res: %+v\n", index, res)
defer t.Logf("test #%d: done!", index) defer t.Logf("test #%d: done!", index)
@@ -217,6 +282,7 @@ func TestResources1(t *testing.T) {
t.Logf("test #%d: err: %+v", index, err) t.Logf("test #%d: err: %+v", index, err)
} }
changedChan := make(chan bool, 1) // buffered!
readyChan := make(chan struct{}) readyChan := make(chan struct{})
eventChan := make(chan struct{}) eventChan := make(chan struct{})
doneChan := make(chan struct{}) doneChan := make(chan struct{})
@@ -254,6 +320,13 @@ func TestResources1(t *testing.T) {
// run init // run init
t.Logf("test #%d: running Init", index) t.Logf("test #%d: running Init", index)
err = res.Init(init) err = res.Init(init)
defer func() {
t.Logf("test #%d: running cleanup()", index)
if err := cleanup(); err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: could not cleanup: %+v", index, err)
}
}()
closeFn := func() { closeFn := func() {
// run close (we don't ever expect an error on close!) // run close (we don't ever expect an error on close!)
t.Logf("test #%d: running Close", index) t.Logf("test #%d: running Close", index)
@@ -319,10 +392,16 @@ func TestResources1(t *testing.T) {
defer wg.Done() defer wg.Done()
for ix, step := range timeline { for ix, step := range timeline {
// magic setting of important value... // magic setting of important values...
if s, ok := step.(*startupStep); ok { if s, ok := step.(*startupStep); ok {
s.ch = readyChan s.ch = readyChan
} }
if s, ok := step.(*changedStep); ok {
s.ch = changedChan
}
if s, ok := step.(*clearChangedStep); ok {
s.ch = changedChan
}
t.Logf("test #%d: step(%d)...", index, ix) t.Logf("test #%d: step(%d)...", index, ix)
if err := step.Action(); err != nil { if err := step.Action(); err != nil {
@@ -356,8 +435,11 @@ func TestResources1(t *testing.T) {
t.Errorf("test #%d: CheckApply failed: %s", index, err.Error()) t.Errorf("test #%d: CheckApply failed: %s", index, err.Error())
return return
} }
_ = checkOK // TODO: do we look at this? select {
// send a msg if we can, but never block
case changedChan <- checkOK:
default:
}
} }
t.Logf("test #%d: waiting for shutdown", index) t.Logf("test #%d: waiting for shutdown", index)