github.com/tilt-dev/tilt@v0.36.0/internal/hud/view_test.go (about)

     1  package hud
     2  
     3  import (
     4  	"path/filepath"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	v1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  
    15  	"github.com/tilt-dev/tilt/internal/hud/view"
    16  	"github.com/tilt-dev/tilt/internal/k8s"
    17  	"github.com/tilt-dev/tilt/internal/k8s/testyaml"
    18  	"github.com/tilt-dev/tilt/internal/store"
    19  	"github.com/tilt-dev/tilt/internal/store/k8sconv"
    20  	"github.com/tilt-dev/tilt/internal/testutils/manifestbuilder"
    21  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    22  	"github.com/tilt-dev/tilt/pkg/apis"
    23  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    24  	"github.com/tilt-dev/tilt/pkg/model"
    25  )
    26  
    27  func TestStateToViewRelativeEditPaths(t *testing.T) {
    28  	f := tempdir.NewTempDirFixture(t)
    29  	m := model.Manifest{
    30  		Name: "foo",
    31  	}.WithDeployTarget(model.K8sTarget{}).WithImageTarget(model.ImageTarget{}.
    32  		WithDockerImage(v1alpha1.DockerImageSpec{Context: f.JoinPath("a", "b", "c")}))
    33  
    34  	state := newState([]model.Manifest{m})
    35  	ms := state.ManifestTargets[m.Name].State
    36  	ms.CurrentBuilds["buildcontrol"] = model.BuildRecord{
    37  		Edits: []string{
    38  			f.JoinPath("a", "b", "c", "foo"),
    39  			f.JoinPath("a", "b", "c", "d", "e"),
    40  		},
    41  	}
    42  	ms.BuildHistory = []model.BuildRecord{
    43  		{
    44  			Edits: []string{
    45  				f.JoinPath("a", "b", "c", "foo"),
    46  				f.JoinPath("a", "b", "c", "d", "e"),
    47  			},
    48  		},
    49  	}
    50  	bs, ok := ms.BuildStatus(m.ImageTargets[0].ID())
    51  	require.True(t, ok)
    52  	bs.FileChanges =
    53  		map[string]time.Time{
    54  			f.JoinPath("a", "b", "c", "foo"):    time.Now(),
    55  			f.JoinPath("a", "b", "c", "d", "e"): time.Now(),
    56  		}
    57  	v := StateToTerminalView(*state, &sync.RWMutex{})
    58  
    59  	require.Len(t, v.Resources, 2)
    60  
    61  	r, _ := v.Resource(m.Name)
    62  	assert.Equal(t, []string{"foo", filepath.Join("d", "e")}, r.LastBuild().Edits)
    63  	assert.Equal(t, []string{"foo", filepath.Join("d", "e")}, r.CurrentBuild.Edits)
    64  	assert.Equal(t, []string{filepath.Join("d", "e"), "foo"}, r.PendingBuildEdits) // these are sorted for deterministic ordering
    65  }
    66  
    67  func TestStateToTerminalViewPortForwards(t *testing.T) {
    68  	m := model.Manifest{
    69  		Name: "foo",
    70  	}.WithDeployTarget(model.K8sTarget{
    71  		KubernetesApplySpec: v1alpha1.KubernetesApplySpec{
    72  			PortForwardTemplateSpec: &v1alpha1.PortForwardTemplateSpec{
    73  				Forwards: []v1alpha1.Forward{
    74  					{LocalPort: 8000, ContainerPort: 5000},
    75  					{LocalPort: 7000, ContainerPort: 5001},
    76  				},
    77  			},
    78  		},
    79  	})
    80  	state := newState([]model.Manifest{m})
    81  	v := StateToTerminalView(*state, &sync.RWMutex{})
    82  	res, _ := v.Resource(m.Name)
    83  	assert.Equal(t,
    84  		[]string{"http://localhost:8000/", "http://localhost:7000/"},
    85  		res.Endpoints)
    86  }
    87  
    88  func TestStateToWebViewLinksAndPortForwards(t *testing.T) {
    89  	m := model.Manifest{
    90  		Name: "foo",
    91  	}.WithDeployTarget(model.K8sTarget{
    92  		KubernetesApplySpec: v1alpha1.KubernetesApplySpec{
    93  			PortForwardTemplateSpec: &v1alpha1.PortForwardTemplateSpec{
    94  				Forwards: []v1alpha1.Forward{
    95  					{LocalPort: 8000, ContainerPort: 5000},
    96  					{LocalPort: 8001, ContainerPort: 5001, Name: "debugger"},
    97  				},
    98  			},
    99  		},
   100  		Links: []model.Link{
   101  			model.MustNewLink("www.apple.edu", "apple"),
   102  			model.MustNewLink("www.zombo.com", "zombo"),
   103  		},
   104  	})
   105  	state := newState([]model.Manifest{m})
   106  	v := StateToTerminalView(*state, &sync.RWMutex{})
   107  	res, _ := v.Resource(m.Name)
   108  	assert.Equal(t,
   109  		[]string{"www.apple.edu", "www.zombo.com", "http://localhost:8000/", "http://localhost:8001/"},
   110  		res.Endpoints)
   111  }
   112  func TestStateToTerminalViewLocalResourceLinks(t *testing.T) {
   113  	m := model.Manifest{
   114  		Name: "foo",
   115  	}.WithDeployTarget(model.LocalTarget{
   116  		Links: []model.Link{
   117  			model.MustNewLink("www.apple.edu", "apple"),
   118  			model.MustNewLink("www.zombo.com", "zombo"),
   119  		},
   120  	})
   121  	state := newState([]model.Manifest{m})
   122  	v := StateToTerminalView(*state, &sync.RWMutex{})
   123  	res, _ := v.Resource(m.Name)
   124  	assert.Equal(t,
   125  		[]string{"www.apple.edu", "www.zombo.com"},
   126  		res.Endpoints)
   127  }
   128  
   129  func TestRuntimeStateNonWorkload(t *testing.T) {
   130  	f := tempdir.NewTempDirFixture(t)
   131  
   132  	m := manifestbuilder.New(f, model.UnresourcedYAMLManifestName).
   133  		WithK8sYAML(testyaml.SecretYaml).
   134  		Build()
   135  	state := newState([]model.Manifest{m})
   136  	runtimeState := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   137  	assert.Equal(t, v1alpha1.RuntimeStatusPending, runtimeState.RuntimeStatus())
   138  
   139  	runtimeState.HasEverDeployedSuccessfully = true
   140  
   141  	assert.Equal(t, v1alpha1.RuntimeStatusOK, runtimeState.RuntimeStatus())
   142  }
   143  
   144  func TestRuntimeStateJob(t *testing.T) {
   145  	for _, tc := range []struct {
   146  		phase                 v1.PodPhase
   147  		expectedRuntimeStatus v1alpha1.RuntimeStatus
   148  	}{
   149  		{v1.PodRunning, v1alpha1.RuntimeStatusPending},
   150  		{v1.PodSucceeded, v1alpha1.RuntimeStatusOK},
   151  		{v1.PodFailed, v1alpha1.RuntimeStatusError},
   152  	} {
   153  		t.Run(string(tc.phase), func(t *testing.T) {
   154  			f := tempdir.NewTempDirFixture(t)
   155  
   156  			m := manifestbuilder.New(f, "foo").
   157  				WithK8sYAML(testyaml.JobYAML).
   158  				WithK8sPodReadiness(model.PodReadinessSucceeded).
   159  				Build()
   160  			state := newState([]model.Manifest{m})
   161  			runtimeState := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   162  			assert.Equal(t, v1alpha1.RuntimeStatusPending, runtimeState.RuntimeStatus())
   163  
   164  			runtimeState.HasEverDeployedSuccessfully = true
   165  
   166  			pod := v1alpha1.Pod{
   167  				Name:      "pod",
   168  				CreatedAt: apis.Now(),
   169  				Phase:     string(tc.phase),
   170  			}
   171  			runtimeState.FilteredPods = []v1alpha1.Pod{pod}
   172  
   173  			assert.Equal(t, tc.expectedRuntimeStatus, runtimeState.RuntimeStatus())
   174  		})
   175  	}
   176  }
   177  
   178  func TestRuntimeStateJobCompleteMissingPods(t *testing.T) {
   179  	f := tempdir.NewTempDirFixture(t)
   180  
   181  	m := manifestbuilder.New(f, "foo").
   182  		WithK8sYAML(testyaml.JobYAML).
   183  		WithK8sPodReadiness(model.PodReadinessSucceeded).
   184  		Build()
   185  	state := newState([]model.Manifest{m})
   186  	runtimeState := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   187  	runtimeState.HasEverDeployedSuccessfully = true
   188  	assert.Equal(t, v1alpha1.RuntimeStatusPending, runtimeState.RuntimeStatus())
   189  
   190  	// N.B. there are no pods but a condition attached
   191  	runtimeState.Conditions = []metav1.Condition{
   192  		{
   193  			Type:   v1alpha1.ApplyConditionJobComplete,
   194  			Status: metav1.ConditionTrue,
   195  		},
   196  	}
   197  	assert.Equal(t, v1alpha1.RuntimeStatusOK, runtimeState.RuntimeStatus())
   198  }
   199  
   200  func TestStateToTerminalViewUnresourcedYAMLManifest(t *testing.T) {
   201  	m := k8sManifest(t, model.UnresourcedYAMLManifestName, testyaml.SanchoYAML)
   202  	state := newState([]model.Manifest{m})
   203  	krs := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   204  	krs.ApplyFilter = yamlToApplyFilter(t, testyaml.SanchoYAML)
   205  	state.ManifestTargets[m.Name].State.RuntimeState = krs
   206  
   207  	v := StateToTerminalView(*state, &sync.RWMutex{})
   208  
   209  	assert.Equal(t, 2, len(v.Resources))
   210  
   211  	r, _ := v.Resource(m.Name)
   212  	assert.Equal(t, nil, r.LastBuild().Error)
   213  
   214  	expectedInfo := view.YAMLResourceInfo{
   215  		K8sDisplayNames: []string{"sancho:deployment"},
   216  	}
   217  	assert.Equal(t, expectedInfo, r.ResourceInfo)
   218  }
   219  
   220  func TestStateToTerminalViewNonWorkloadYAMLManifest(t *testing.T) {
   221  	m := k8sManifest(t, "foo", testyaml.SecretYaml)
   222  	state := newState([]model.Manifest{m})
   223  	krs := state.ManifestTargets[m.Name].State.K8sRuntimeState()
   224  	krs.ApplyFilter = yamlToApplyFilter(t, testyaml.SecretYaml)
   225  	state.ManifestTargets[m.Name].State.RuntimeState = krs
   226  
   227  	v := StateToTerminalView(*state, &sync.RWMutex{})
   228  
   229  	assert.Equal(t, 2, len(v.Resources))
   230  
   231  	r, _ := v.Resource(m.Name)
   232  	assert.Equal(t, nil, r.LastBuild().Error)
   233  
   234  	expectedInfo := view.YAMLResourceInfo{
   235  		K8sDisplayNames: []string{"mysecret:secret"},
   236  	}
   237  	assert.Equal(t, expectedInfo, r.ResourceInfo)
   238  }
   239  
   240  func newState(manifests []model.Manifest) *store.EngineState {
   241  	ret := store.NewState()
   242  	for _, m := range manifests {
   243  		ret.ManifestTargets[m.Name] = store.NewManifestTarget(m)
   244  		ret.ManifestDefinitionOrder = append(ret.ManifestDefinitionOrder, m.Name)
   245  	}
   246  
   247  	return ret
   248  }
   249  
   250  func yamlToApplyFilter(t testing.TB, yaml string) *k8sconv.KubernetesApplyFilter {
   251  	t.Helper()
   252  	entities, err := k8s.ParseYAMLFromString(yaml)
   253  	require.NoError(t, err, "Failed to parse YAML")
   254  	for i := range entities {
   255  		entities[i].SetUID(uuid.New().String())
   256  	}
   257  	yaml, err = k8s.SerializeSpecYAML(entities)
   258  	require.NoError(t, err, "Failed to re-serialize YAML")
   259  	applyFilter, err := k8sconv.NewKubernetesApplyFilter(yaml)
   260  	require.NoError(t, err, "Failed to create KubernetesApplyFilter")
   261  	require.NotNil(t, applyFilter, "ApplyFilter was nil")
   262  	return applyFilter
   263  }
   264  
   265  func k8sManifest(t testing.TB, name model.ManifestName, yaml string) model.Manifest {
   266  	t.Helper()
   267  	kt, err := k8s.NewTargetForYAML(name.TargetName(), yaml, nil)
   268  	require.NoError(t, err, "Failed to create Kubernetes deploy target")
   269  	return model.Manifest{Name: name}.WithDeployTarget(kt)
   270  }