Merge pull request #3693 from hashicorp/b-flaky-parallelism-tests

command: fix flaky parallelism tests
This commit is contained in:
Paul Hinze 2015-10-29 15:28:55 -05:00
commit 76c488dc24
4 changed files with 58 additions and 74 deletions

View File

@ -12,6 +12,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
@ -58,80 +59,60 @@ func TestApply(t *testing.T) {
} }
} }
func TestApply_parallelism1(t *testing.T) { func TestApply_parallelism(t *testing.T) {
statePath := testTempFile(t) provider := testProvider()
// This blocks all the appy functions. We close it when we exit so
// they end quickly after this test finishes.
block := make(chan struct{})
defer close(block)
var runCount uint64
provider.ApplyFn = func(
i *terraform.InstanceInfo,
s *terraform.InstanceState,
d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
// Increment so we're counting parallelism
atomic.AddUint64(&runCount, 1)
// Block until we're done
<-block
return nil, nil
}
ui := new(cli.MockUi) ui := new(cli.MockUi)
p := testProvider()
pr := new(terraform.MockResourceProvisioner)
pr.ApplyFn = func(*terraform.InstanceState, *terraform.ResourceConfig) error {
time.Sleep(time.Second)
return nil
}
args := []string{
"-state", statePath,
"-parallelism=1",
testFixturePath("parallelism"),
}
c := &ApplyCommand{ c := &ApplyCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfigWithShell(p, pr), ContextOpts: testCtxConfig(provider),
Ui: ui, Ui: ui,
}, },
} }
start := time.Now() par := uint64(5)
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
elapsed := time.Since(start).Seconds()
// This test should take exactly two seconds, plus some minor amount of execution time.
if elapsed < 2 || elapsed > 2.2 {
t.Fatalf("bad: %f\n\n%s", elapsed, ui.ErrorWriter.String())
}
}
func TestApply_parallelism2(t *testing.T) {
statePath := testTempFile(t)
ui := new(cli.MockUi)
p := testProvider()
pr := new(terraform.MockResourceProvisioner)
pr.ApplyFn = func(*terraform.InstanceState, *terraform.ResourceConfig) error {
time.Sleep(time.Second)
return nil
}
args := []string{ args := []string{
"-state", statePath, fmt.Sprintf("-parallelism=%d", par),
"-parallelism=2",
testFixturePath("parallelism"), testFixturePath("parallelism"),
} }
c := &ApplyCommand{ // Run in a goroutine. We still try to catch any errors and
Meta: Meta{ // get them on the error channel.
ContextOpts: testCtxConfigWithShell(p, pr), errCh := make(chan string, 1)
Ui: ui, go func() {
}, if code := c.Run(args); code != 0 {
errCh <- ui.OutputWriter.String()
}
}()
select {
case <-time.After(1000 * time.Millisecond):
case err := <-errCh:
t.Fatalf("err: %s", err)
} }
start := time.Now() // The total in flight should equal the parallelism
if code := c.Run(args); code != 0 { if rc := atomic.LoadUint64(&runCount); rc != par {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("Expected parallelism: %d, got: %d", par, rc)
} }
elapsed := time.Since(start).Seconds()
// This test should take exactly one second, plus some minor amount of execution time.
if elapsed < 1 || elapsed > 1.2 {
t.Fatalf("bad: %f\n\n%s", elapsed, ui.ErrorWriter.String())
}
} }
func TestApply_configInvalid(t *testing.T) { func TestApply_configInvalid(t *testing.T) {

View File

@ -1,13 +1,10 @@
resource "test_instance" "foo1" { resource "test_instance" "foo1" {}
ami = "bar" resource "test_instance" "foo2" {}
resource "test_instance" "foo3" {}
// shell has been configured to sleep for one second resource "test_instance" "foo4" {}
provisioner "shell" {} resource "test_instance" "foo5" {}
} resource "test_instance" "foo6" {}
resource "test_instance" "foo7" {}
resource "test_instance" "foo2" { resource "test_instance" "foo8" {}
ami = "bar" resource "test_instance" "foo9" {}
resource "test_instance" "foo10" {}
// shell has been configured to sleep for one second
provisioner "shell" {}
}

View File

@ -2,6 +2,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log"
"sync" "sync"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
@ -95,6 +96,8 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
} }
func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode {
log.Printf("[INFO] Entering eval tree: %s", dag.VertexName(v))
// Acquire a lock on the semaphore // Acquire a lock on the semaphore
w.Context.parallelSem.Acquire() w.Context.parallelSem.Acquire()
@ -105,6 +108,8 @@ func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode {
func (w *ContextGraphWalker) ExitEvalTree( func (w *ContextGraphWalker) ExitEvalTree(
v dag.Vertex, output interface{}, err error) error { v dag.Vertex, output interface{}, err error) error {
log.Printf("[INFO] Exiting eval tree: %s", dag.VertexName(v))
// Release the semaphore // Release the semaphore
w.Context.parallelSem.Release() w.Context.parallelSem.Release()

View File

@ -118,13 +118,14 @@ func (p *MockResourceProvider) Apply(
info *InstanceInfo, info *InstanceInfo,
state *InstanceState, state *InstanceState,
diff *InstanceDiff) (*InstanceState, error) { diff *InstanceDiff) (*InstanceState, error) {
// We only lock while writing data. Reading is fine
p.Lock() p.Lock()
defer p.Unlock()
p.ApplyCalled = true p.ApplyCalled = true
p.ApplyInfo = info p.ApplyInfo = info
p.ApplyState = state p.ApplyState = state
p.ApplyDiff = diff p.ApplyDiff = diff
p.Unlock()
if p.ApplyFn != nil { if p.ApplyFn != nil {
return p.ApplyFn(info, state, diff) return p.ApplyFn(info, state, diff)
} }