2019-04-19 03:10:48 -04:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
2020-02-14 11:42:29 -05:00
|
|
|
"github.com/hashicorp/hcl/v2/hcldec"
|
|
|
|
|
2019-05-02 08:41:44 -04:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2019-05-03 03:02:56 -04:00
|
|
|
"github.com/hashicorp/packer/builder/file"
|
2019-04-19 03:10:48 -04:00
|
|
|
"github.com/hashicorp/packer/packer"
|
2019-05-03 03:02:56 -04:00
|
|
|
"github.com/hashicorp/packer/provisioner/sleep"
|
2019-04-19 03:10:48 -04:00
|
|
|
)
|
|
|
|
|
2019-05-02 09:04:02 -04:00
|
|
|
// NewParallelTestBuilder will return a New ParallelTestBuilder that will
|
|
|
|
// unlock after `runs` builds
|
2019-05-02 08:41:44 -04:00
|
|
|
func NewParallelTestBuilder(runs int) *ParallelTestBuilder {
|
2019-05-02 09:04:02 -04:00
|
|
|
pb := &ParallelTestBuilder{}
|
2019-05-02 08:41:44 -04:00
|
|
|
pb.wg.Add(runs)
|
|
|
|
return pb
|
|
|
|
}
|
|
|
|
|
|
|
|
// The ParallelTestBuilder's first run will lock
|
2019-04-19 03:10:48 -04:00
|
|
|
type ParallelTestBuilder struct {
|
2019-05-02 08:41:44 -04:00
|
|
|
wg sync.WaitGroup
|
2019-04-19 03:10:48 -04:00
|
|
|
}
|
|
|
|
|
2019-12-17 05:57:09 -05:00
|
|
|
func (b *ParallelTestBuilder) ConfigSpec() hcldec.ObjectSpec { return nil }
|
|
|
|
|
2019-12-17 00:23:05 -05:00
|
|
|
func (b *ParallelTestBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
2019-04-19 03:10:48 -04:00
|
|
|
|
|
|
|
func (b *ParallelTestBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
2019-05-02 08:41:44 -04:00
|
|
|
ui.Say("building")
|
2019-04-19 03:10:48 -04:00
|
|
|
b.wg.Done()
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2019-05-02 09:04:02 -04:00
|
|
|
// LockedBuilder wont run until unlock is called
|
|
|
|
type LockedBuilder struct{ unlock chan interface{} }
|
|
|
|
|
2019-12-17 05:57:09 -05:00
|
|
|
func (b *LockedBuilder) ConfigSpec() hcldec.ObjectSpec { return nil }
|
|
|
|
|
2020-03-17 07:05:37 -04:00
|
|
|
func (b *LockedBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
2019-05-02 09:04:02 -04:00
|
|
|
|
|
|
|
func (b *LockedBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
|
|
|
ui.Say("locking build")
|
|
|
|
select {
|
|
|
|
case <-b.unlock:
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2019-04-19 03:10:48 -04:00
|
|
|
// testMetaFile creates a Meta object that includes a file builder
|
2019-05-02 09:04:02 -04:00
|
|
|
func testMetaParallel(t *testing.T, builder *ParallelTestBuilder, locked *LockedBuilder) Meta {
|
2019-04-19 03:10:48 -04:00
|
|
|
var out, err bytes.Buffer
|
|
|
|
return Meta{
|
|
|
|
CoreConfig: &packer.CoreConfig{
|
|
|
|
Components: packer.ComponentFinder{
|
2019-12-17 05:25:56 -05:00
|
|
|
BuilderStore: packer.MapOfBuilder{
|
|
|
|
"parallel-test": func() (packer.Builder, error) { return builder, nil },
|
|
|
|
"file": func() (packer.Builder, error) { return &file.Builder{}, nil },
|
|
|
|
"lock": func() (packer.Builder, error) { return locked, nil },
|
2019-04-19 03:10:48 -04:00
|
|
|
},
|
2019-12-17 05:25:56 -05:00
|
|
|
ProvisionerStore: packer.MapOfProvisioner{
|
|
|
|
"sleep": func() (packer.Provisioner, error) { return &sleep.Provisioner{}, nil },
|
2019-05-03 03:02:56 -04:00
|
|
|
},
|
2019-04-19 03:10:48 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Ui: &packer.BasicUi{
|
|
|
|
Writer: &out,
|
|
|
|
ErrorWriter: &err,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-02 09:04:02 -04:00
|
|
|
func TestBuildParallel_1(t *testing.T) {
|
2019-05-02 10:23:35 -04:00
|
|
|
// testfile has 6 builds, with first one locks 'forever', other builds
|
|
|
|
// should go through.
|
2019-05-02 08:41:44 -04:00
|
|
|
b := NewParallelTestBuilder(5)
|
2019-05-02 09:04:02 -04:00
|
|
|
locked := &LockedBuilder{unlock: make(chan interface{})}
|
2019-04-19 03:10:48 -04:00
|
|
|
|
|
|
|
c := &BuildCommand{
|
2019-05-02 09:04:02 -04:00
|
|
|
Meta: testMetaParallel(t, b, locked),
|
2019-04-19 03:10:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
2019-05-02 09:04:02 -04:00
|
|
|
fmt.Sprintf("-parallel=true"),
|
2019-05-06 09:07:57 -04:00
|
|
|
filepath.Join(testFixture("parallel"), "1lock-5wg.json"),
|
2019-04-19 03:10:48 -04:00
|
|
|
}
|
|
|
|
|
2019-05-02 08:41:44 -04:00
|
|
|
wg := errgroup.Group{}
|
|
|
|
|
|
|
|
wg.Go(func() error {
|
2019-04-19 03:10:48 -04:00
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
fatalCommand(t, c.Meta)
|
|
|
|
}
|
2019-05-02 08:41:44 -04:00
|
|
|
return nil
|
|
|
|
})
|
2019-04-19 03:10:48 -04:00
|
|
|
|
2019-05-02 09:04:02 -04:00
|
|
|
b.wg.Wait() // ran 5 times
|
|
|
|
close(locked.unlock) // unlock locking one
|
|
|
|
wg.Wait() // wait for termination
|
2019-04-19 03:10:48 -04:00
|
|
|
}
|
2019-05-02 10:23:35 -04:00
|
|
|
|
|
|
|
func TestBuildParallel_2(t *testing.T) {
|
|
|
|
// testfile has 6 builds, 2 of them lock 'forever', other builds
|
|
|
|
// should go through.
|
|
|
|
b := NewParallelTestBuilder(4)
|
|
|
|
locked := &LockedBuilder{unlock: make(chan interface{})}
|
|
|
|
|
|
|
|
c := &BuildCommand{
|
|
|
|
Meta: testMetaParallel(t, b, locked),
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
fmt.Sprintf("-parallel-builds=3"),
|
2019-05-06 09:47:53 -04:00
|
|
|
filepath.Join(testFixture("parallel"), "2lock-4wg.json"),
|
2019-05-02 10:23:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
wg := errgroup.Group{}
|
|
|
|
|
|
|
|
wg.Go(func() error {
|
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
fatalCommand(t, c.Meta)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
b.wg.Wait() // ran 4 times
|
|
|
|
close(locked.unlock) // unlock locking one
|
|
|
|
wg.Wait() // wait for termination
|
|
|
|
}
|
2019-05-03 03:02:56 -04:00
|
|
|
|
|
|
|
func TestBuildParallel_Timeout(t *testing.T) {
|
|
|
|
// testfile has 6 builds, 1 of them locks 'forever', one locks and times
|
|
|
|
// out other builds should go through.
|
|
|
|
b := NewParallelTestBuilder(4)
|
|
|
|
locked := &LockedBuilder{unlock: make(chan interface{})}
|
|
|
|
|
|
|
|
c := &BuildCommand{
|
|
|
|
Meta: testMetaParallel(t, b, locked),
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
fmt.Sprintf("-parallel-builds=3"),
|
|
|
|
filepath.Join(testFixture("parallel"), "2lock-timeout.json"),
|
|
|
|
}
|
|
|
|
|
|
|
|
wg := errgroup.Group{}
|
|
|
|
|
|
|
|
wg.Go(func() error {
|
|
|
|
if code := c.Run(args); code == 0 {
|
|
|
|
fatalCommand(t, c.Meta)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
b.wg.Wait() // ran 4 times
|
|
|
|
close(locked.unlock) // unlock locking one
|
|
|
|
wg.Wait() // wait for termination
|
|
|
|
}
|