mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-19 22:41:40 -04:00
* Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at https://hashi.co/license-faq, and details of the license at www.hashicorp.com/bsl. * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
179 lines
4.4 KiB
Go
179 lines
4.4 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hcl/v2/hcldec"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/hashicorp/packer/builder/file"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/provisioner/sleep"
|
|
)
|
|
|
|
// NewParallelTestBuilder will return a New ParallelTestBuilder that will
|
|
// unlock after `runs` builds
|
|
func NewParallelTestBuilder(runs int) *ParallelTestBuilder {
|
|
pb := &ParallelTestBuilder{}
|
|
pb.wg.Add(runs)
|
|
return pb
|
|
}
|
|
|
|
// The ParallelTestBuilder's first run will lock
|
|
type ParallelTestBuilder struct {
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func (b *ParallelTestBuilder) ConfigSpec() hcldec.ObjectSpec { return nil }
|
|
|
|
func (b *ParallelTestBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
func (b *ParallelTestBuilder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
ui.Say("building")
|
|
b.wg.Done()
|
|
return nil, nil
|
|
}
|
|
|
|
// LockedBuilder wont run until unlock is called
|
|
type LockedBuilder struct{ unlock chan interface{} }
|
|
|
|
func (b *LockedBuilder) ConfigSpec() hcldec.ObjectSpec { return nil }
|
|
|
|
func (b *LockedBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
func (b *LockedBuilder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
|
|
ui.Say("locking build")
|
|
select {
|
|
case <-b.unlock:
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// testMetaFile creates a Meta object that includes a file builder
|
|
func testMetaParallel(t *testing.T, builder *ParallelTestBuilder, locked *LockedBuilder) Meta {
|
|
var out, err bytes.Buffer
|
|
return Meta{
|
|
CoreConfig: &packer.CoreConfig{
|
|
Components: packer.ComponentFinder{
|
|
PluginConfig: &packer.PluginConfig{
|
|
Builders: packer.MapOfBuilder{
|
|
"parallel-test": func() (packersdk.Builder, error) { return builder, nil },
|
|
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
|
|
"lock": func() (packersdk.Builder, error) { return locked, nil },
|
|
},
|
|
Provisioners: packer.MapOfProvisioner{
|
|
"sleep": func() (packersdk.Provisioner, error) { return &sleep.Provisioner{}, nil },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Ui: &packersdk.BasicUi{
|
|
Writer: &out,
|
|
ErrorWriter: &err,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestBuildParallel_1(t *testing.T) {
|
|
// testfile has 6 builds, with first one locks 'forever', other builds
|
|
// should go through.
|
|
b := NewParallelTestBuilder(5)
|
|
locked := &LockedBuilder{unlock: make(chan interface{})}
|
|
|
|
c := &BuildCommand{
|
|
Meta: testMetaParallel(t, b, locked),
|
|
}
|
|
|
|
args := []string{
|
|
fmt.Sprintf("-parallel-builds=10"),
|
|
filepath.Join(testFixture("parallel"), "1lock-5wg.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 5 times
|
|
close(locked.unlock) // unlock locking one
|
|
wg.Wait() // wait for termination
|
|
}
|
|
|
|
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"),
|
|
filepath.Join(testFixture("parallel"), "2lock-4wg.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
|
|
}
|
|
|
|
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
|
|
}
|