runner/main.go (140 lines of code) (raw):
// Copyright 2018 Google LLC
//
// 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 main
import (
"flag"
"fmt"
"net/http"
"os"
"strings"
"github.com/GoogleCloudPlatform/marketplace-testrunner/asserts"
"github.com/GoogleCloudPlatform/marketplace-testrunner/conditions"
"github.com/GoogleCloudPlatform/marketplace-testrunner/flags"
"github.com/GoogleCloudPlatform/marketplace-testrunner/gcp"
"github.com/GoogleCloudPlatform/marketplace-testrunner/specs"
"github.com/GoogleCloudPlatform/marketplace-testrunner/tests"
"github.com/golang/glog"
)
const outcomeFailed = "FAILED"
const outcomePassed = "PASSED"
const outcomeSkipped = "SKIPPED"
type testResult struct {
Name string
Message string
Passed bool
}
type testStatus struct {
FailureCount int
}
func (r *testResult) Fail(testType, msg string) {
r.Passed = false
r.Message = asserts.MessageWithContext(msg, fmt.Sprintf("%s: %s", outcomeFailed, testType))
}
func (r *testResult) Pass() {
r.Passed = true
r.Message = outcomePassed
}
func (r *testResult) Skip(msg string) {
r.Passed = true
r.Message = asserts.MessageWithContext(msg, fmt.Sprintf("%s: Condition not satisfied", outcomeSkipped))
}
func (t testStatus) FailuresSoFarCount() int {
return t.FailureCount
}
func getEnvs() map[string]string {
envs := make(map[string]string)
for _, element := range os.Environ() {
splits := strings.SplitN(element, "=", 2)
envs[splits[0]] = splits[1]
}
return envs
}
func main() {
testSpecs := flags.FlagStringList("test_spec", "path to a yaml or json file containing the test spec. Can be specified multiple times")
vars := flags.FlagStringMap("var", "variable substitutions. Value should be key=value. Can be specified multiple times")
flag.Parse()
if len(*testSpecs) <= 0 {
glog.Fatal("--test_spec must be specified")
}
status := testStatus{}
for _, testSpec := range *testSpecs {
glog.Infof(">>> Running %v", testSpec)
suite := specs.LoadSuite(testSpec, specs.TemplateSpec{Var: *vars, Env: getEnvs()})
if len(suite.Actions) <= 0 {
glog.Info(" > Nothing to run!")
continue
}
doRunActions(suite, &status)
}
if status.FailureCount > 0 {
glog.Errorf(">>> SUMMARY: %d failed", status.FailureCount)
} else {
glog.Infof(">>> SUMMARY: All passed")
}
os.Exit(status.FailureCount)
}
// doRunActions executes the actions in the suite and returns a function
// to do a summary report. This report function returns the number of
// test failures, i.e. its returning 0 means all tests are passing.
func doRunActions(suite *specs.Suite, status *testStatus) {
results := make([]testResult, len(suite.Actions), len(suite.Actions))
for index, action := range suite.Actions {
doOneAction(index, &action, status, results)
}
for _, r := range results {
if !r.Passed {
status.FailureCount++
}
}
if status.FailureCount == 0 {
glog.Infof(" >> Summary: %s", outcomePassed)
} else {
glog.Errorf(" >> Summary: %d %s, %d %s", status.FailureCount, outcomeFailed, len(results)-status.FailureCount, outcomePassed)
}
for _, r := range results {
if r.Passed {
glog.Infof(" > %s: %s", r.Name, outcomePassed)
} else {
glog.Errorf(" > %s: %s", r.Name, outcomeFailed)
}
}
}
func doOneAction(index int, action *specs.Action, status *testStatus, results []testResult) {
result := testResult{}
if len(action.Name) <= 0 {
glog.Fatalf("All actions must have names")
}
result.Name = fmt.Sprintf("%3d: %s", index, action.Name)
glog.Infof(" > %s", result.Name)
recordResult := func(r *testResult) {
results[index] = *r
if r.Passed {
glog.Infof(" %s", r.Message)
} else {
glog.Errorf(" %s", r.Message)
}
}
defer recordResult(&result)
if action.Condition != nil {
ok, msg := conditions.Evaluate(action.Condition, status)
if !ok {
result.Skip(msg)
return
}
}
if action.HttpTest != nil {
msg := tests.RunHttpTest(action.HttpTest, &http.Client{})
if len(msg) > 0 {
result.Fail("HTTP test failed", msg)
return
}
} else if action.Gcp != nil {
msg := gcp.RunAction(action.Gcp)
if len(msg) > 0 {
result.Fail("GCP action failed", msg)
return
}
} else if action.BashTest != nil {
msg := tests.RunBashTest(action.BashTest, &tests.RealExecutor{})
if len(msg) > 0 {
result.Fail("Bash test failed", msg)
return
}
}
result.Pass()
}