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  }