github.com/koderover/helm@v2.17.0+incompatible/pkg/releasetesting/test_suite.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package releasetesting
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"golang.org/x/sync/semaphore"
    23  	"strings"
    24  
    25  	"github.com/ghodss/yaml"
    26  	"github.com/golang/protobuf/ptypes/timestamp"
    27  	"k8s.io/api/core/v1"
    28  
    29  	"k8s.io/helm/pkg/hooks"
    30  	"k8s.io/helm/pkg/proto/hapi/release"
    31  	util "k8s.io/helm/pkg/releaseutil"
    32  	"k8s.io/helm/pkg/timeconv"
    33  )
    34  
    35  // TestSuite what tests are run, results, and metadata
    36  type TestSuite struct {
    37  	StartedAt     *timestamp.Timestamp
    38  	CompletedAt   *timestamp.Timestamp
    39  	TestManifests []string
    40  	Results       []*release.TestRun
    41  }
    42  
    43  type test struct {
    44  	manifest        string
    45  	expectedSuccess bool
    46  	result          *release.TestRun
    47  }
    48  
    49  // NewTestSuite takes a release object and returns a TestSuite object with test definitions
    50  //  extracted from the release
    51  func NewTestSuite(rel *release.Release) (*TestSuite, error) {
    52  	testManifests, err := extractTestManifestsFromHooks(rel.Hooks)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	results := []*release.TestRun{}
    58  
    59  	return &TestSuite{
    60  		TestManifests: testManifests,
    61  		Results:       results,
    62  	}, nil
    63  }
    64  
    65  // Run executes tests in a test suite and stores a result within a given environment
    66  func (ts *TestSuite) Run(env *Environment) error {
    67  	ts.StartedAt = timeconv.Now()
    68  
    69  	if len(ts.TestManifests) == 0 {
    70  		// TODO: make this better, adding test run status on test suite is weird
    71  		env.streamMessage("No Tests Found", release.TestRun_UNKNOWN)
    72  	}
    73  
    74  	var tests []*test
    75  
    76  	for _, testManifest := range ts.TestManifests {
    77  		test, err := newTest(testManifest)
    78  		if err != nil {
    79  			return err
    80  		}
    81  
    82  		tests = append(tests, test)
    83  	}
    84  
    85  	if env.Parallel {
    86  		c := make(chan error, len(tests))
    87  		// Use a semaphore to restrict the number of tests running in parallel.
    88  		sem := semaphore.NewWeighted(int64(env.Parallelism))
    89  		ctx := context.Background()
    90  		for _, t := range tests {
    91  			sem.Acquire(ctx, 1)
    92  			go func(t *test, sem *semaphore.Weighted) {
    93  				defer sem.Release(1)
    94  				c <- t.run(env)
    95  			}(t, sem)
    96  		}
    97  
    98  		for range tests {
    99  			if err := <-c; err != nil {
   100  				return err
   101  			}
   102  		}
   103  
   104  	} else {
   105  		for _, t := range tests {
   106  			if err := t.run(env); err != nil {
   107  				return err
   108  			}
   109  		}
   110  	}
   111  
   112  	for _, t := range tests {
   113  		ts.Results = append(ts.Results, t.result)
   114  	}
   115  
   116  	ts.CompletedAt = timeconv.Now()
   117  	return nil
   118  }
   119  
   120  func (t *test) run(env *Environment) error {
   121  	t.result.StartedAt = timeconv.Now()
   122  	if err := env.streamRunning(t.result.Name); err != nil {
   123  		return err
   124  	}
   125  	t.result.Status = release.TestRun_RUNNING
   126  
   127  	resourceCreated := true
   128  	if err := env.createTestPod(t); err != nil {
   129  		resourceCreated = false
   130  		if streamErr := env.streamError(t.result.Info); streamErr != nil {
   131  			return err
   132  		}
   133  	}
   134  
   135  	resourceCleanExit := true
   136  	status := v1.PodUnknown
   137  	if resourceCreated {
   138  		var err error
   139  		status, err = env.getTestPodStatus(t)
   140  		if err != nil {
   141  			resourceCleanExit = false
   142  			if streamErr := env.streamError(t.result.Info); streamErr != nil {
   143  				return streamErr
   144  			}
   145  		}
   146  	}
   147  
   148  	if resourceCreated && resourceCleanExit {
   149  		if err := t.assignTestResult(status); err != nil {
   150  			return err
   151  		}
   152  
   153  		if err := env.streamResult(t.result); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	t.result.CompletedAt = timeconv.Now()
   159  	return nil
   160  }
   161  
   162  func (t *test) assignTestResult(podStatus v1.PodPhase) error {
   163  	switch podStatus {
   164  	case v1.PodSucceeded:
   165  		if t.expectedSuccess {
   166  			t.result.Status = release.TestRun_SUCCESS
   167  		} else {
   168  			t.result.Status = release.TestRun_FAILURE
   169  		}
   170  	case v1.PodFailed:
   171  		if !t.expectedSuccess {
   172  			t.result.Status = release.TestRun_SUCCESS
   173  		} else {
   174  			t.result.Status = release.TestRun_FAILURE
   175  		}
   176  	default:
   177  		t.result.Status = release.TestRun_UNKNOWN
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  func expectedSuccess(hookTypes []string) (bool, error) {
   184  	for _, hookType := range hookTypes {
   185  		hookType = strings.ToLower(strings.TrimSpace(hookType))
   186  		if hookType == hooks.ReleaseTestSuccess {
   187  			return true, nil
   188  		} else if hookType == hooks.ReleaseTestFailure {
   189  			return false, nil
   190  		}
   191  	}
   192  	return false, fmt.Errorf("No %s or %s hook found", hooks.ReleaseTestSuccess, hooks.ReleaseTestFailure)
   193  }
   194  
   195  func extractTestManifestsFromHooks(h []*release.Hook) ([]string, error) {
   196  	testHooks := hooks.FilterTestHooks(h)
   197  
   198  	tests := []string{}
   199  	for _, h := range testHooks {
   200  		individualTests := util.SplitManifests(h.Manifest)
   201  		for _, t := range individualTests {
   202  			tests = append(tests, t)
   203  		}
   204  	}
   205  	return tests, nil
   206  }
   207  
   208  func newTest(testManifest string) (*test, error) {
   209  	var sh util.SimpleHead
   210  	err := yaml.Unmarshal([]byte(testManifest), &sh)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	if sh.Kind != "Pod" {
   216  		return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name)
   217  	}
   218  
   219  	hookTypes := sh.Metadata.Annotations[hooks.HookAnno]
   220  	expected, err := expectedSuccess(strings.Split(hookTypes, ","))
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	name := strings.TrimSuffix(sh.Metadata.Name, ",")
   226  	return &test{
   227  		manifest:        testManifest,
   228  		expectedSuccess: expected,
   229  		result: &release.TestRun{
   230  			Name: name,
   231  		},
   232  	}, nil
   233  }