Merge pull request #3693 from hashicorp/b-flaky-parallelism-tests
command: fix flaky parallelism tests
This commit is contained in:
commit
76c488dc24
|
@ -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) {
|
||||||
|
|
|
@ -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" {}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue