cli/bptest/list.go (161 lines of code) (raw):
package bptest
import (
"errors"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/discovery"
testing "github.com/mitchellh/go-testing-interface"
)
const (
discoverTestFilename = "discover_test.go"
)
type bpTest struct {
name string
config string
location string
bptestCfg discovery.BlueprintTestConfig
}
// getTests returns slice of all blueprint tests
func getTests(intTestDir string) ([]bpTest, error) {
intTestDir, err := getIntTestDir(intTestDir)
if err != nil {
return nil, err
}
Log.Info(fmt.Sprintf("using test-dir: %s", intTestDir))
tests := []bpTest{}
discoveredTests, err := getDiscoveredTests(intTestDir)
if err != nil {
return nil, err
}
tests = append(tests, discoveredTests...)
explicitTests, err := getExplicitTests(intTestDir)
if err != nil {
return nil, err
}
tests = append(tests, explicitTests...)
return tests, nil
}
// getDiscoveredTests returns slice of discovered blueprint tests
func getDiscoveredTests(intTestDir string) ([]bpTest, error) {
discoverTestFile := path.Join(intTestDir, discoverTestFilename)
// skip discovering tests if no discoverTestFile
_, err := os.Stat(discoverTestFile)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
Log.Warn(fmt.Sprintf("Skipping discovered test. %s not found.", discoverTestFilename))
return nil, nil
}
// if discoverTestFile is present, find auto discovered tests
tests := []bpTest{}
if discoverTestFile != "" {
discoverTestName, err := getDiscoverTestName(discoverTestFile)
if err != nil {
return nil, err
}
discoveredSubTests := discovery.FindTestConfigs(&testing.RuntimeT{}, intTestDir)
for testName, testCfg := range discoveredSubTests {
bptestCfg, err := discovery.GetTestConfig(path.Join(testCfg, discovery.DefaultTestConfigFilename))
if err != nil {
Log.Warn(fmt.Sprintf("error discovering BlueprintTest config: %v", err))
}
tests = append(tests, bpTest{name: fmt.Sprintf("%s/%s", discoverTestName, testName), config: testCfg, location: discoverTestFile, bptestCfg: bptestCfg})
}
}
sort.SliceStable(tests, func(i, j int) bool { return tests[i].name < tests[j].name })
return tests, nil
}
func getExplicitTests(intTestDir string) ([]bpTest, error) {
// find all explicit test files ending with *_test.go excluding discover_test.go within intTestDir
testFiles, err := findFiles(intTestDir,
func(d fs.DirEntry) bool {
return strings.HasSuffix(d.Name(), "_test.go") && d.Name() != discoverTestFilename
},
)
if err != nil {
Log.Warn(fmt.Sprintf("walking file path: %s : details: %v", intTestDir, err))
}
eTests := []bpTest{}
for _, testFile := range testFiles {
// testDir name maps to a matching example/fixture
testDir := path.Dir(testFile)
testCfg, err := discovery.GetConfigDirFromTestDir(testDir)
if err != nil {
Log.Warn(fmt.Sprintf("unable to discover configs for %s: %v", testDir, err))
}
// discover BlueprintTest config if any
bptestCfg, err := discovery.GetTestConfig(path.Join(testCfg, discovery.DefaultTestConfigFilename))
if err != nil {
Log.Warn(fmt.Sprintf("error discovering BlueprintTest config: %v", err))
}
testFns, err := getTestFuncsFromFile(testFile)
if err != nil {
return nil, err
}
for _, fnName := range testFns {
eTests = append(eTests, bpTest{name: fnName, location: testFile, config: testCfg, bptestCfg: bptestCfg})
}
}
sort.SliceStable(eTests, func(i, j int) bool { return eTests[i].name < eTests[j].name })
return eTests, nil
}
// getDiscoverTestName returns test name used for auto discovered tests
func getDiscoverTestName(dFileName string) (string, error) {
fn, err := getTestFuncsFromFile(dFileName)
if err != nil {
return "", err
}
// enforce only one main test func decl for discovered tests
if len(fn) != 1 {
return "", fmt.Errorf("only one function should be defined in %s. Found %+q", dFileName, fn)
}
return fn[0], nil
}
// discoverIntTestDir attempts to discover the integration test directory
// by searching for discover_test.go in the current working directory.
// If not found, it returns current working directory.
func discoverIntTestDir(cwd string) (string, error) {
// search for discover_test.go
discoverTestFiles, err := findFiles(cwd,
func(d fs.DirEntry) bool {
return d.Name() == discoverTestFilename
},
)
if err != nil {
Log.Warn(fmt.Sprintf("walking file path: %s : details: %v", cwd, err))
}
if len(discoverTestFiles) > 1 {
return "", fmt.Errorf("found multiple %s files: %+q. Exactly one file was expected", discoverTestFilename, discoverTestFiles)
}
if len(discoverTestFiles) == 1 {
relIntTestDir, err := filepath.Rel(cwd, path.Dir(discoverTestFiles[0]))
if err != nil {
return "", err
}
return relIntTestDir, nil
}
// no discover_test.go file discovered
return ".", nil
}
// getIntTestDir discovers the integration test directory
// from current working directory if an empty intTestDir is provided
func getIntTestDir(intTestDir string) (string, error) {
if intTestDir != "" {
return intTestDir, nil
}
// discover from current working directory
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return discoverIntTestDir(cwd)
}
// findFiles returns a slice of file paths matching matchFn
func findFiles(dir string, matchFn func(d fs.DirEntry) bool) ([]string, error) {
files := []string{}
err := filepath.WalkDir(dir, func(fpath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// ignore hidden dirs
if d.IsDir() && strings.HasPrefix(d.Name(), ".") {
return filepath.SkipDir
}
if !d.IsDir() && matchFn(d) {
files = append(files, fpath)
return nil
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}