terraform: EvalContext.InitProvider (shadow) tests

This commit is contained in:
Mitchell Hashimoto 2016-09-30 21:56:07 -07:00
parent fb96b0c422
commit 792a9f1de4
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 82 additions and 8 deletions

View File

@ -43,17 +43,23 @@ type ShadowEvalContext interface {
// This should be called before the ctx is ever used in order to ensure // This should be called before the ctx is ever used in order to ensure
// a consistent shadow state. // a consistent shadow state.
func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) { func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) {
real := &shadowEvalContextReal{EvalContext: ctx} var shared shadowEvalContextShared
real := &shadowEvalContextReal{
EvalContext: ctx,
Shared: &shared,
}
// Copy the diff. We do this using some weird scoping so that the // Copy the diff. We do this using some weird scoping so that the
// "diff" (real) value never leaks out and can be used. // "diff" (real) value never leaks out and can be used.
var diffCopy *Diff var diffCopy *Diff
{ {
diff, lock := ctx.Diff() diff, lock := ctx.Diff()
lock.RLock() if lock != nil {
diffCopy = diff lock.RLock()
// TODO: diffCopy = diff.DeepCopy() diffCopy = diff
lock.RUnlock() // TODO: diffCopy = diff.DeepCopy()
lock.RUnlock()
}
} }
// Copy the state. We do this using some weird scoping so that the // Copy the state. We do this using some weird scoping so that the
@ -61,9 +67,11 @@ func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) {
var stateCopy *State var stateCopy *State
{ {
state, lock := ctx.State() state, lock := ctx.State()
lock.RLock() if lock != nil {
stateCopy = state.DeepCopy() lock.RLock()
lock.RUnlock() stateCopy = state.DeepCopy()
lock.RUnlock()
}
} }
// Build the shadow copy. For safety, we don't even give the shadow // Build the shadow copy. For safety, we don't even give the shadow
@ -71,6 +79,8 @@ func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) {
// very difficult (impossible without some real obvious mistakes) for // very difficult (impossible without some real obvious mistakes) for
// the shadow context to do "real" work. // the shadow context to do "real" work.
shadow := &shadowEvalContextShadow{ shadow := &shadowEvalContextShadow{
Shared: &shared,
PathValue: ctx.Path(), PathValue: ctx.Path(),
StateValue: stateCopy, StateValue: stateCopy,
StateLock: new(sync.RWMutex), StateLock: new(sync.RWMutex),

View File

@ -1,10 +1,74 @@
package terraform package terraform
import ( import (
"fmt"
"reflect"
"testing" "testing"
"time"
) )
func TestShadowEvalContext_impl(t *testing.T) { func TestShadowEvalContext_impl(t *testing.T) {
var _ EvalContext = new(shadowEvalContextReal) var _ EvalContext = new(shadowEvalContextReal)
var _ EvalContext = new(shadowEvalContextShadow) var _ EvalContext = new(shadowEvalContextShadow)
} }
func TestShadowEvalContextInitProvider(t *testing.T) {
mock := new(MockEvalContext)
real, shadow := NewShadowEvalContext(mock)
// Args, results
name := "foo"
mockResult := new(MockResourceProvider)
// Configure the mock
mock.InitProviderProvider = mockResult
// Verify that it blocks until the real func is called
var result ResourceProvider
var err error
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
result, err = shadow.InitProvider(name)
}()
select {
case <-doneCh:
t.Fatal("should block until finished")
case <-time.After(10 * time.Millisecond):
}
// Call the real func
realResult, realErr := real.InitProvider(name)
if realErr != nil {
t.Fatalf("bad: %#v", realErr)
}
realResult.Configure(nil)
if !mockResult.ConfigureCalled {
t.Fatalf("bad: %#v", realResult)
}
mockResult.ConfigureCalled = false
// The shadow should finish now
<-doneCh
// Verify the shadow returned the same values
if err != nil {
t.Fatalf("bad: %#v", err)
}
// Verify that the returned value is a shadow. Calling one function
// shouldn't affect the other.
result.Configure(nil)
if mockResult.ConfigureCalled {
t.Fatal("close should not be called")
}
// And doing some work should result in that value
mockErr := fmt.Errorf("yo")
mockResult.ConfigureReturnError = mockErr
realResult.Configure(nil)
if err := result.Configure(nil); !reflect.DeepEqual(err, mockErr) {
t.Fatalf("bad: %#v", err)
}
}