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 }