cli/bptest/run.go (104 lines of code) (raw):
package bptest
import (
"bufio"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"sync"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/utils"
"github.com/spf13/viper"
)
const (
allTests = "all"
testStageEnvVarKey = "RUN_STAGE"
gotestBin = "gotest"
goBin = "go"
// The tfplan.json files that are being used as input for the terraform validation tests
// through the gcloud beta terraform vet are higher than the buffer default value (64*1024),
// after some tests we had evidences that the value were around from 3MB to 5MB, so
// we choosed a value that is at least 2x higher than the original one to avoid errors.
// maxScanTokenSize is the maximum size used to buffer a token
// startBufSize is the initial of the buffer token
maxScanTokenSize = 10 * 1024 * 1024
startBufSize = 4096
// This must be kept in sync with what github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft parses.
setupEnvVarPrefix = "CFT_SETUP_"
)
var allTestArgs = []string{"-p", "1", "-count", "1", "-timeout", "0"}
// validateAndGetRelativeTestPkg validates a given test or test regex is part of the blueprint test set and returns location of test relative to intTestDir
func validateAndGetRelativeTestPkg(intTestDir string, name string) (string, error) {
// user wants to run all tests
if name == allTests {
return "./...", nil
}
tests, err := getTests(intTestDir)
if err != nil {
return "", err
}
testNames := []string{}
for _, test := range tests {
if test.bptestCfg.Spec.Skip {
Log.Info(fmt.Sprintf("skipping %s due to BlueprintTest config %s", test.name, test.bptestCfg.Name))
continue
}
matched, _ := regexp.Match(name, []byte(test.name))
if test.name == name {
//exact match, return test relative test pkg
relPkg, err := filepath.Rel(intTestDir, path.Dir(test.location))
if err != nil {
return "", err
}
return fmt.Sprintf("./%s", relPkg), nil
} else if matched {
// loose match, more than one test could be specified
return "./...", nil
}
testNames = append(testNames, test.name)
}
return "", fmt.Errorf("unable to find %s- one of %+q expected", name, append(testNames, allTests))
}
// streamExec runs a given cmd while streaming logs
func streamExec(cmd *exec.Cmd) error {
op, err := cmd.StdoutPipe()
if err != nil {
return err
}
cmd.Stderr = cmd.Stdout
Log.Debug(fmt.Sprintf("running %s with args %v in %s", cmd.Path, cmd.Args, cmd.Dir))
// waitgroup to block while processing exec op
var wg sync.WaitGroup
wg.Add(1)
defer wg.Wait()
go func() {
defer wg.Done()
scanner := bufio.NewScanner(op)
scanner.Buffer(make([]byte, startBufSize), maxScanTokenSize)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
Log.Error(fmt.Sprintf("error reading output: %s", err))
}
}()
// run command
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running command: %w", err)
}
return nil
}
// getTestCmd returns a prepared cmd for running the specified tests(s)
func getTestCmd(intTestDir string, testStage string, testName string, relTestPkg string, setupVars map[string]string) (*exec.Cmd, error) {
// pass all current env vars to test command
env := os.Environ()
// set test stage env var if specified
if testStage != "" {
env = append(env, fmt.Sprintf("%s=%s", testStageEnvVarKey, testStage))
}
// Load the env with any setup-vars specified
for k, v := range setupVars {
env = append(env, fmt.Sprintf("%s%s=%s", setupEnvVarPrefix, k, v))
}
// determine binary and args used for test execution
testArgs := append([]string{relTestPkg}, allTestArgs...)
if testName != allTests {
testArgs = append([]string{relTestPkg, "-run", testName}, allTestArgs...)
}
cmdBin := goBin
if utils.BinaryInPath(gotestBin) != nil {
testArgs = append([]string{"test"}, testArgs...)
} else {
cmdBin = gotestBin
// CI=true enables color op for non tty exec output
env = append(env, "CI=true")
}
// verbose test output if global verbose flag is passed
if viper.GetBool("verbose") {
testArgs = append(testArgs, "-v")
}
// prepare cmd
cmd := exec.Command(cmdBin, testArgs...)
cmd.Env = env
cmd.Dir = intTestDir
return cmd, nil
}