go/e2e_test_utils/launcher.go (143 lines of code) (raw):
// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2etestutils
import (
"bytes"
"context"
"encoding/xml"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/flags"
"github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils/junitxml"
"github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils/test_config"
)
var (
testSuiteFilter = flag.String("test_suite_filter", "", "test suite filter")
testCaseFilter = flag.String("test_case_filter", "", "test case filter")
outDir = flag.String("out_dir", "/tmp", "junit xml directory")
testProjectID = flag.String("test_project_id", "", "test project id")
testZone = flag.String("test_zone", "", "test zone")
variables = flag.String("variables", "", "comma separated list of variables, in the form 'key=value'")
)
// LaunchTests launches tests by the test framework
func LaunchTests(testFunctions []func(context.Context, *sync.WaitGroup, chan *junitxml.TestSuite, *log.Logger, *regexp.Regexp, *regexp.Regexp, *testconfig.Project),
loggerPrefix string) {
if success := RunTestsAndOutput(testFunctions, loggerPrefix); !success {
os.Exit(1)
}
}
// RunTestsAndOutput runs tests by the test framework and output results to given file
func RunTestsAndOutput(testFunctions []func(context.Context, *sync.WaitGroup, chan *junitxml.TestSuite, *log.Logger, *regexp.Regexp, *regexp.Regexp, *testconfig.Project),
loggerPrefix string) bool {
testFunctionsWithArgs := []func(context.Context, *sync.WaitGroup, chan *junitxml.TestSuite, *log.Logger, *regexp.Regexp, *regexp.Regexp, *testconfig.Project, map[string]string){}
for _, tf := range testFunctions {
testFunctionsWithArgs = append(testFunctionsWithArgs,
func(ctx context.Context, wg *sync.WaitGroup, tests chan *junitxml.TestSuite, logger *log.Logger, testSuiteRegex *regexp.Regexp, testCaseRegex *regexp.Regexp, pr *testconfig.Project, _ map[string]string) {
tf(ctx, wg, tests, logger, testSuiteRegex, testCaseRegex, pr)
})
}
return RunTestsWithArgsAndOutput(testFunctionsWithArgs, loggerPrefix)
}
// RunTestsWithArgsAndOutput runs tests with arguments by the test framework and output results to given file
func RunTestsWithArgsAndOutput(testFunctions []func(context.Context, *sync.WaitGroup, chan *junitxml.TestSuite, *log.Logger, *regexp.Regexp, *regexp.Regexp, *testconfig.Project, map[string]string),
loggerPrefix string) bool {
flag.Parse()
ctx := context.Background()
pr := getProject(ctx)
testSuiteRegex, testCaseRegex := getTestRegex()
logger := log.New(os.Stdout, loggerPrefix+" ", log.LstdFlags)
logger.Println("Starting...")
var s flags.KeyValueString = nil
s.Set(*variables)
argMap := map[string]string(s)
testResultChan := runTests(ctx, testFunctions, logger, testSuiteRegex, testCaseRegex, pr, argMap)
testSuites := outputTestResultToFile(testResultChan, logger)
return outputTestResultToLogger(testSuites, logger)
}
func getProject(ctx context.Context) *testconfig.Project {
if len(strings.TrimSpace(*testProjectID)) == 0 {
fmt.Println("-test_project_id is invalid")
os.Exit(1)
}
if len(strings.TrimSpace(*testZone)) == 0 {
fmt.Println("-test_zone is invalid")
os.Exit(1)
}
pr := testconfig.GetProject(*testProjectID, *testZone)
return pr
}
func getTestRegex() (*regexp.Regexp, *regexp.Regexp) {
var testSuiteRegex *regexp.Regexp
if *testSuiteFilter != "" {
var err error
testSuiteRegex, err = regexp.Compile(*testSuiteFilter)
if err != nil {
fmt.Println("-testSuiteFilter flag not valid:", err)
os.Exit(1)
}
}
var testCaseRegex *regexp.Regexp
if *testCaseFilter != "" {
var err error
testCaseRegex, err = regexp.Compile(*testCaseFilter)
if err != nil {
fmt.Println("-testCaseFilter flag not valid:", err)
os.Exit(1)
}
}
return testSuiteRegex, testCaseRegex
}
func runTests(ctx context.Context, testFunctions []func(context.Context, *sync.WaitGroup, chan *junitxml.TestSuite, *log.Logger, *regexp.Regexp, *regexp.Regexp, *testconfig.Project, map[string]string),
logger *log.Logger, testSuiteRegex *regexp.Regexp, testCaseRegex *regexp.Regexp, pr *testconfig.Project, argMap map[string]string) chan *junitxml.TestSuite {
tests := make(chan *junitxml.TestSuite)
var wg sync.WaitGroup
for _, tf := range testFunctions {
wg.Add(1)
go tf(ctx, &wg, tests, logger, testSuiteRegex, testCaseRegex, pr, argMap)
}
go func() {
wg.Wait()
close(tests)
}()
return tests
}
func outputTestResultToFile(tests chan *junitxml.TestSuite, logger *log.Logger) []*junitxml.TestSuite {
var testSuites []*junitxml.TestSuite
for ret := range tests {
testSuites = append(testSuites, ret)
testSuiteOutPath := filepath.Join(*outDir, fmt.Sprintf("junit_%s.xml", ret.Name))
if err := os.MkdirAll(filepath.Dir(testSuiteOutPath), 0770); err != nil {
log.Fatal(err)
}
logger.Printf("Creating junit xml file: %s", testSuiteOutPath)
d, err := xml.MarshalIndent(ret, " ", " ")
if err != nil {
log.Fatal(err)
}
if err := ioutil.WriteFile(testSuiteOutPath, d, 0644); err != nil {
log.Fatal(err)
}
}
return testSuites
}
func outputTestResultToLogger(testSuites []*junitxml.TestSuite, logger *log.Logger) bool {
var buf bytes.Buffer
for _, ts := range testSuites {
if ts.Failures > 0 {
buf.WriteString(fmt.Sprintf("TestSuite %q has errors:\n", ts.Name))
for _, tc := range ts.TestCase {
if tc.Failure != nil {
buf.WriteString(fmt.Sprintf(" - %q: %s\n", tc.Name, tc.Failure.FailMessage))
}
}
}
}
if buf.Len() > 0 {
logger.Printf("%sExiting with exit code 1\n", buf.String())
return false
}
logger.Println("All test cases completed successfully.")
return true
}