github.com/koderover/helm@v2.17.0+incompatible/pkg/releasetesting/test_suite_test.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 "io" 21 "io/ioutil" 22 "testing" 23 "time" 24 25 "github.com/golang/protobuf/ptypes/timestamp" 26 "golang.org/x/net/context" 27 grpc "google.golang.org/grpc" 28 "google.golang.org/grpc/metadata" 29 "k8s.io/api/core/v1" 30 31 "k8s.io/helm/pkg/helm" 32 "k8s.io/helm/pkg/proto/hapi/chart" 33 "k8s.io/helm/pkg/proto/hapi/release" 34 "k8s.io/helm/pkg/proto/hapi/services" 35 "k8s.io/helm/pkg/storage" 36 "k8s.io/helm/pkg/storage/driver" 37 tillerEnv "k8s.io/helm/pkg/tiller/environment" 38 ) 39 40 const ( 41 manifestWithTestSuccessHook = ` 42 apiVersion: v1 43 kind: Pod 44 metadata: 45 name: finding-nemo, 46 annotations: 47 "helm.sh/hook": test-success 48 spec: 49 containers: 50 - name: nemo-test 51 image: fake-image 52 cmd: fake-command 53 ` 54 55 manifestWithTestFailureHook = ` 56 apiVersion: v1 57 kind: Pod 58 metadata: 59 name: gold-rush, 60 annotations: 61 "helm.sh/hook": test-failure 62 spec: 63 containers: 64 - name: gold-finding-test 65 image: fake-gold-finding-image 66 cmd: fake-gold-finding-command 67 ` 68 manifestWithInstallHooks = `apiVersion: v1 69 kind: ConfigMap 70 metadata: 71 name: test-cm 72 annotations: 73 "helm.sh/hook": post-install,pre-delete 74 data: 75 name: value 76 ` 77 ) 78 79 func TestNewTestSuite(t *testing.T) { 80 rel := releaseStub() 81 82 _, err := NewTestSuite(rel) 83 if err != nil { 84 t.Errorf("%s", err) 85 } 86 } 87 88 func TestRun(t *testing.T) { 89 90 testManifests := []string{manifestWithTestSuccessHook, manifestWithTestFailureHook} 91 ts := testSuiteFixture(testManifests) 92 if err := ts.Run(testEnvFixture()); err != nil { 93 t.Errorf("%s", err) 94 } 95 96 if ts.StartedAt == nil { 97 t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) 98 } 99 100 if ts.CompletedAt == nil { 101 t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) 102 } 103 104 if len(ts.Results) != 2 { 105 t.Errorf("Expected 2 test result. Got %v", len(ts.Results)) 106 } 107 108 result := ts.Results[0] 109 if result.StartedAt == nil { 110 t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt) 111 } 112 113 if result.CompletedAt == nil { 114 t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt) 115 } 116 117 if result.Name != "finding-nemo" { 118 t.Errorf("Expected test name to be finding-nemo. Got: %v", result.Name) 119 } 120 121 if result.Status != release.TestRun_SUCCESS { 122 t.Errorf("Expected test result to be successful, got: %v", result.Status) 123 } 124 125 result2 := ts.Results[1] 126 if result2.StartedAt == nil { 127 t.Errorf("Expected test StartedAt to not be nil. Got: %v", result2.StartedAt) 128 } 129 130 if result2.CompletedAt == nil { 131 t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result2.CompletedAt) 132 } 133 134 if result2.Name != "gold-rush" { 135 t.Errorf("Expected test name to be gold-rush, Got: %v", result2.Name) 136 } 137 138 if result2.Status != release.TestRun_FAILURE { 139 t.Errorf("Expected test result to be failure, got: %v", result2.Status) 140 } 141 142 } 143 144 func TestRunEmptyTestSuite(t *testing.T) { 145 ts := testSuiteFixture([]string{}) 146 mockTestEnv := testEnvFixture() 147 if err := ts.Run(mockTestEnv); err != nil { 148 t.Errorf("%s", err) 149 } 150 151 if ts.StartedAt == nil { 152 t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) 153 } 154 155 if ts.CompletedAt == nil { 156 t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) 157 } 158 159 if len(ts.Results) != 0 { 160 t.Errorf("Expected 0 test result. Got %v", len(ts.Results)) 161 } 162 163 stream := mockTestEnv.Stream.(*mockStream) 164 if len(stream.messages) == 0 { 165 t.Errorf("Expected at least one message, Got: %v", len(stream.messages)) 166 } else { 167 msg := stream.messages[0].Msg 168 if msg != "No Tests Found" { 169 t.Errorf("Expected message 'No Tests Found', Got: %v", msg) 170 } 171 } 172 173 } 174 175 func TestRunSuccessWithTestFailureHook(t *testing.T) { 176 ts := testSuiteFixture([]string{manifestWithTestFailureHook}) 177 env := testEnvFixture() 178 env.KubeClient = newPodFailedKubeClient() 179 if err := ts.Run(env); err != nil { 180 t.Errorf("%s", err) 181 } 182 183 if ts.StartedAt == nil { 184 t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) 185 } 186 187 if ts.CompletedAt == nil { 188 t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) 189 } 190 191 if len(ts.Results) != 1 { 192 t.Errorf("Expected 1 test result. Got %v", len(ts.Results)) 193 } 194 195 result := ts.Results[0] 196 if result.StartedAt == nil { 197 t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt) 198 } 199 200 if result.CompletedAt == nil { 201 t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt) 202 } 203 204 if result.Name != "gold-rush" { 205 t.Errorf("Expected test name to be gold-rush, Got: %v", result.Name) 206 } 207 208 if result.Status != release.TestRun_SUCCESS { 209 t.Errorf("Expected test result to be successful, got: %v", result.Status) 210 } 211 } 212 213 func TestExtractTestManifestsFromHooks(t *testing.T) { 214 rel := releaseStub() 215 testManifests, err := extractTestManifestsFromHooks(rel.Hooks) 216 if err != nil { 217 t.Errorf("Expected no error, Got: %s", err) 218 } 219 220 if len(testManifests) != 1 { 221 t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests)) 222 } 223 } 224 225 func TestParallelTestRun(t *testing.T) { 226 ts := testSuiteFixture([]string{manifestWithTestSuccessHook, manifestWithTestSuccessHook}) 227 env := testEnvFixture() 228 env.Parallel = true 229 env.KubeClient = newSleepOnWaitKubeClient() 230 if err := ts.Run(env); err != nil { 231 t.Errorf("%s", err) 232 } 233 234 if len(ts.Results) != 2 { 235 t.Errorf("Expected 2 test result. Got %v", len(ts.Results)) 236 } 237 238 stream := env.Stream.(*mockStream) 239 if len(stream.messages) != 4 { 240 t.Errorf("Expected four messages, Got: %v", len(stream.messages)) 241 } 242 243 if stream.messages[0].Status != release.TestRun_RUNNING { 244 t.Errorf("Expected first message status to be RUNNING, Got: %v", stream.messages[0].Status) 245 } 246 if stream.messages[1].Status != release.TestRun_RUNNING { 247 t.Errorf("Expected second message status to be RUNNING, Got: %v", stream.messages[1].Status) 248 } 249 if stream.messages[2].Status != release.TestRun_SUCCESS { 250 t.Errorf("Expected third message status to be SUCCESS, Got: %v", stream.messages[2].Status) 251 } 252 if stream.messages[3].Status != release.TestRun_SUCCESS { 253 t.Errorf("Expected fourth message status to be SUCCESS, Got: %v", stream.messages[3].Status) 254 } 255 } 256 257 func TestParallelTestRunFailure(t *testing.T) { 258 ts := testSuiteFixture([]string{manifestWithTestSuccessHook, manifestWithTestFailureHook}) 259 env := testEnvFixture() 260 env.Parallel = true 261 env.KubeClient = newSleepOnWaitKubeClient() 262 if err := ts.Run(env); err != nil { 263 t.Errorf("%s", err) 264 } 265 266 if len(ts.Results) != 2 { 267 t.Errorf("Expected 2 test result. Got %v", len(ts.Results)) 268 } 269 270 stream := env.Stream.(*mockStream) 271 if len(stream.messages) != 4 { 272 t.Errorf("Expected four messages, Got: %v", len(stream.messages)) 273 } 274 275 if stream.messages[0].Status != release.TestRun_RUNNING { 276 t.Errorf("Expected first message status to be RUNNING, Got: %v", stream.messages[0].Status) 277 } 278 if stream.messages[1].Status != release.TestRun_RUNNING { 279 t.Errorf("Expected second message status to be RUNNING, Got: %v", stream.messages[1].Status) 280 } 281 282 if ts.Results[0].Status != release.TestRun_SUCCESS { 283 t.Errorf("Expected first test result to be successful, got: %v", ts.Results[0].Status) 284 } 285 286 if ts.Results[1].Status != release.TestRun_FAILURE { 287 t.Errorf("Expected second test result to be failure, got: %v", ts.Results[1].Status) 288 } 289 } 290 291 func TestParallelism(t *testing.T) { 292 ts := testSuiteFixture([]string{manifestWithTestSuccessHook, manifestWithTestSuccessHook, manifestWithTestFailureHook}) 293 env := testEnvFixture() 294 env.Parallel = true 295 env.Parallelism = 2 296 env.KubeClient = newSleepOnWaitKubeClient() 297 if err := ts.Run(env); err != nil { 298 t.Errorf("%s", err) 299 } 300 301 stream := env.Stream.(*mockStream) 302 303 if stream.messages[0].Status != release.TestRun_RUNNING { 304 t.Errorf("Expected first message status to be RUNNING, Got: %v", stream.messages[0].Status) 305 } 306 if stream.messages[1].Status != release.TestRun_RUNNING { 307 t.Errorf("Expected second message status to be RUNNING, Got: %v", stream.messages[1].Status) 308 } 309 if stream.messages[2].Status == release.TestRun_RUNNING { 310 t.Errorf("Expected third message status to be not be RUNNING") 311 } 312 313 if ts.Results[0].Status != release.TestRun_SUCCESS { 314 t.Errorf("Expected first test result to be successful, got: %v", ts.Results[0].Status) 315 } 316 317 if ts.Results[1].Status != release.TestRun_SUCCESS { 318 t.Errorf("Expected second test result to be successful, got: %v", ts.Results[1].Status) 319 } 320 321 if ts.Results[2].Status != release.TestRun_FAILURE { 322 t.Errorf("Expected third test result to be failure, got: %v", ts.Results[2].Status) 323 } 324 } 325 326 func chartStub() *chart.Chart { 327 return &chart.Chart{ 328 Metadata: &chart.Metadata{ 329 Name: "nemo", 330 }, 331 Templates: []*chart.Template{ 332 {Name: "templates/hello", Data: []byte("hello: world")}, 333 {Name: "templates/hooks", Data: []byte(manifestWithTestSuccessHook)}, 334 }, 335 } 336 } 337 338 func releaseStub() *release.Release { 339 date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} 340 return &release.Release{ 341 Name: "lost-fish", 342 Info: &release.Info{ 343 FirstDeployed: &date, 344 LastDeployed: &date, 345 Status: &release.Status{Code: release.Status_DEPLOYED}, 346 Description: "a release stub", 347 }, 348 Chart: chartStub(), 349 Config: &chart.Config{Raw: `name: value`}, 350 Version: 1, 351 Hooks: []*release.Hook{ 352 { 353 Name: "finding-nemo", 354 Kind: "Pod", 355 Path: "finding-nemo", 356 Manifest: manifestWithTestSuccessHook, 357 Events: []release.Hook_Event{ 358 release.Hook_RELEASE_TEST_SUCCESS, 359 }, 360 }, 361 { 362 Name: "test-cm", 363 Kind: "ConfigMap", 364 Path: "test-cm", 365 Manifest: manifestWithInstallHooks, 366 Events: []release.Hook_Event{ 367 release.Hook_POST_INSTALL, 368 release.Hook_PRE_DELETE, 369 }, 370 }, 371 }, 372 } 373 } 374 375 func testFixture() *test { 376 return &test{ 377 manifest: manifestWithTestSuccessHook, 378 result: &release.TestRun{}, 379 } 380 } 381 382 func testSuiteFixture(testManifests []string) *TestSuite { 383 testResults := []*release.TestRun{} 384 ts := &TestSuite{ 385 TestManifests: testManifests, 386 Results: testResults, 387 } 388 389 return ts 390 } 391 392 func testEnvFixture() *Environment { 393 return newMockTestingEnvironment().Environment 394 } 395 396 func mockTillerEnvironment() *tillerEnv.Environment { 397 e := tillerEnv.New() 398 e.Releases = storage.Init(driver.NewMemory()) 399 e.KubeClient = newPodSucceededKubeClient() 400 return e 401 } 402 403 type mockStream struct { 404 stream grpc.ServerStream 405 messages []*services.TestReleaseResponse 406 } 407 408 func (rs *mockStream) Send(m *services.TestReleaseResponse) error { 409 rs.messages = append(rs.messages, m) 410 return nil 411 } 412 413 func (rs mockStream) SetHeader(m metadata.MD) error { return nil } 414 func (rs mockStream) SendHeader(m metadata.MD) error { return nil } 415 func (rs mockStream) SetTrailer(m metadata.MD) {} 416 func (rs mockStream) SendMsg(v interface{}) error { return nil } 417 func (rs mockStream) RecvMsg(v interface{}) error { return nil } 418 func (rs mockStream) Context() context.Context { 419 return helm.NewContext() 420 } 421 422 type podSucceededKubeClient struct { 423 tillerEnv.PrintingKubeClient 424 } 425 426 func newPodSucceededKubeClient() *podSucceededKubeClient { 427 return &podSucceededKubeClient{ 428 PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, 429 } 430 } 431 432 func (p *podSucceededKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (v1.PodPhase, error) { 433 return v1.PodSucceeded, nil 434 } 435 436 // For testing parallelism, this kube client 437 // will sleep for 1ms before returning completed pod 438 // phase. 439 type sleepOnWaitKubeClient struct { 440 tillerEnv.PrintingKubeClient 441 firstWait bool 442 } 443 444 func newSleepOnWaitKubeClient() *sleepOnWaitKubeClient { 445 return &sleepOnWaitKubeClient{ 446 PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, 447 } 448 } 449 450 func (p *sleepOnWaitKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (v1.PodPhase, error) { 451 time.Sleep(1 * time.Millisecond) 452 453 return v1.PodSucceeded, nil 454 } 455 456 type podFailedKubeClient struct { 457 tillerEnv.PrintingKubeClient 458 } 459 460 func newPodFailedKubeClient() *podFailedKubeClient { 461 return &podFailedKubeClient{ 462 PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, 463 } 464 } 465 466 func (p *podFailedKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (v1.PodPhase, error) { 467 return v1.PodFailed, nil 468 }