github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/tiltextension/plugin_test.go (about)

     1  package tiltextension
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"runtime"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    13  	"github.com/tilt-dev/tilt/internal/tiltfile/include"
    14  	"github.com/tilt-dev/tilt/internal/tiltfile/starkit"
    15  	tiltfilev1alpha1 "github.com/tilt-dev/tilt/internal/tiltfile/v1alpha1"
    16  )
    17  
    18  func TestFetchableAlreadyPresentWorks(t *testing.T) {
    19  	f := newExtensionFixture(t)
    20  
    21  	f.tiltfile(`
    22  load("ext://fetchable", "printFoo")
    23  printFoo()
    24  `)
    25  	f.writeModuleLocally("fetchable", libText)
    26  
    27  	res := f.assertExecOutput("foo")
    28  	f.assertLoadRecorded(res, "fetchable")
    29  }
    30  
    31  func TestAlreadyPresentWorks(t *testing.T) {
    32  	f := newExtensionFixture(t)
    33  
    34  	f.tiltfile(`
    35  load("ext://unfetchable", "printFoo")
    36  printFoo()
    37  `)
    38  	f.writeModuleLocally("unfetchable", libText)
    39  
    40  	res := f.assertExecOutput("foo")
    41  	f.assertLoadRecorded(res, "unfetchable")
    42  }
    43  
    44  func TestExtensionRepoApplyFails(t *testing.T) {
    45  	f := newExtensionFixture(t)
    46  
    47  	f.tiltfile(`
    48  load("ext://module", "printFoo")
    49  printFoo()
    50  `)
    51  	f.extrr.Error = "repo can't be fetched"
    52  
    53  	res := f.assertError("loading extension repo default: repo can't be fetched")
    54  	f.assertNoLoadsRecorded(res)
    55  }
    56  
    57  func TestExtensionApplyFails(t *testing.T) {
    58  	f := newExtensionFixture(t)
    59  
    60  	f.tiltfile(`
    61  load("ext://module", "printFoo")
    62  printFoo()
    63  `)
    64  	f.extr.Error = "ext can't be fetched"
    65  
    66  	res := f.assertError("loading extension module: ext can't be fetched")
    67  	f.assertNoLoadsRecorded(res)
    68  }
    69  
    70  func TestIncludedFileMayIncludeExtension(t *testing.T) {
    71  	f := newExtensionFixture(t)
    72  
    73  	f.tiltfile(`include('Tiltfile.prime')`)
    74  
    75  	f.skf.File("Tiltfile.prime", `
    76  load("ext://fetchable", "printFoo")
    77  printFoo()
    78  `)
    79  
    80  	f.writeModuleLocally("fetchable", libText)
    81  
    82  	res := f.assertExecOutput("foo")
    83  	f.assertLoadRecorded(res, "fetchable")
    84  }
    85  
    86  func TestExtensionMayLoadExtension(t *testing.T) {
    87  	f := newExtensionFixture(t)
    88  
    89  	f.tiltfile(`
    90  load("ext://fooExt", "printFoo")
    91  printFoo()
    92  `)
    93  	f.writeModuleLocally("fooExt", extensionThatLoadsExtension)
    94  	f.writeModuleLocally("barExt", printBar)
    95  
    96  	res := f.assertExecOutput("foo\nbar")
    97  	f.assertLoadRecorded(res, "fooExt", "barExt")
    98  }
    99  
   100  func TestLoadedFilesResolveExtensionsFromRootTiltfile(t *testing.T) {
   101  	f := newExtensionFixture(t)
   102  
   103  	f.tiltfile(`include('./nested/Tiltfile')`)
   104  
   105  	f.tmp.MkdirAll("nested")
   106  	f.skf.File("nested/Tiltfile", `
   107  load("ext://unfetchable", "printFoo")
   108  printFoo()
   109  `)
   110  
   111  	// Note that the extension lives in the tilt_modules directory of the
   112  	// root Tiltfile. (If we look for this extension in the wrong place and
   113  	// try to fetch this extension into ./nested/tilt_modules,
   114  	// the fake fetcher will error.)
   115  	f.writeModuleLocally("unfetchable", libText)
   116  
   117  	res := f.assertExecOutput("foo")
   118  	f.assertLoadRecorded(res, "unfetchable")
   119  }
   120  
   121  func TestRepoAndExtOverride(t *testing.T) {
   122  	if runtime.GOOS == "windows" {
   123  		// We don't want to have to bother with file:// escaping on windows.
   124  		// The repo reconciler already tests this.
   125  		t.Skip()
   126  	}
   127  
   128  	f := newExtensionFixture(t)
   129  
   130  	f.tiltfile(fmt.Sprintf(`
   131  v1alpha1.extension_repo(name='default', url='file://%s/my-custom-repo')
   132  v1alpha1.extension(name='my-extension', repo_name='default', repo_path='my-custom-path')
   133  
   134  load("ext://my-extension", "printFoo")
   135  printFoo()
   136  `, f.tmp.Path()))
   137  
   138  	f.tmp.WriteFile(filepath.Join("my-custom-repo", "my-custom-path", "Tiltfile"), libText)
   139  
   140  	res := f.assertExecOutput("foo")
   141  	f.assertLoadRecorded(res, "my-extension")
   142  }
   143  
   144  func TestRepoOverride(t *testing.T) {
   145  	if runtime.GOOS == "windows" {
   146  		// We don't want to have to bother with file:// escaping on windows.
   147  		// The repo reconciler already tests this.
   148  		t.Skip()
   149  	}
   150  
   151  	f := newExtensionFixture(t)
   152  
   153  	f.tiltfile(fmt.Sprintf(`
   154  v1alpha1.extension_repo(name='default', url='file://%s/my-custom-repo')
   155  
   156  load("ext://my-extension", "printFoo")
   157  printFoo()
   158  `, f.tmp.Path()))
   159  
   160  	f.tmp.WriteFile(filepath.Join("my-custom-repo", "my-extension", "Tiltfile"), libText)
   161  
   162  	res := f.assertExecOutput("foo")
   163  	f.assertLoadRecorded(res, "my-extension")
   164  }
   165  
   166  func TestLoadedExtensionTwiceDifferentFiles(t *testing.T) {
   167  	if runtime.GOOS == "windows" {
   168  		// We don't want to have to bother with file:// escaping on windows.
   169  		// The repo reconciler already tests this.
   170  		t.Skip()
   171  	}
   172  
   173  	f := newExtensionFixture(t)
   174  
   175  	f.tmp.WriteFile(filepath.Join("my-custom-repo", "my-custom-path", "Tiltfile"), libText)
   176  
   177  	subfileContent := fmt.Sprintf(`
   178  v1alpha1.extension_repo(name='my-extension-repo', url='file://%s/my-custom-repo')
   179  v1alpha1.extension(name='my-extension', repo_name='my-extension-repo', repo_path='my-custom-path')
   180  load('ext://my-extension', 'printFoo')
   181  printFoo()
   182  `, f.tmp.Path())
   183  
   184  	f.skf.File("Tiltfile.a", subfileContent)
   185  	f.skf.File("Tiltfile.b", subfileContent)
   186  	f.tiltfile(`
   187  include('Tiltfile.a')
   188  include('Tiltfile.b')
   189  `)
   190  	res := f.assertExecOutput("foo\nfoo")
   191  	f.assertLoadRecorded(res, "my-extension")
   192  }
   193  
   194  type extensionFixture struct {
   195  	t     *testing.T
   196  	skf   *starkit.Fixture
   197  	tmp   *tempdir.TempDirFixture
   198  	extr  *FakeExtReconciler
   199  	extrr *FakeExtRepoReconciler
   200  }
   201  
   202  func newExtensionFixture(t *testing.T) *extensionFixture {
   203  	tmp := tempdir.NewTempDirFixture(t)
   204  	extr := NewFakeExtReconciler(tmp.Path())
   205  	extrr := NewFakeExtRepoReconciler(tmp.Path())
   206  
   207  	ext := NewFakePlugin(
   208  		extrr,
   209  		extr,
   210  	)
   211  	skf := starkit.NewFixture(t, ext, include.IncludeFn{}, tiltfilev1alpha1.NewPlugin())
   212  	skf.UseRealFS()
   213  
   214  	return &extensionFixture{
   215  		t:     t,
   216  		skf:   skf,
   217  		tmp:   tmp,
   218  		extr:  extr,
   219  		extrr: extrr,
   220  	}
   221  }
   222  
   223  func (f *extensionFixture) tiltfile(contents string) {
   224  	f.skf.File("Tiltfile", contents)
   225  }
   226  
   227  func (f *extensionFixture) assertExecOutput(expected string) starkit.Model {
   228  	result, err := f.skf.ExecFile("Tiltfile")
   229  	if err != nil {
   230  		f.t.Fatalf("unexpected error %v", err)
   231  	}
   232  	if !strings.Contains(f.skf.PrintOutput(), expected) {
   233  		f.t.Fatalf("output %q doesn't contain expected output %q", f.skf.PrintOutput(), expected)
   234  	}
   235  	return result
   236  }
   237  
   238  func (f *extensionFixture) assertError(expected string) starkit.Model {
   239  	result, err := f.skf.ExecFile("Tiltfile")
   240  	if err == nil {
   241  		f.t.Fatalf("expected error; got none (output %q)", f.skf.PrintOutput())
   242  	}
   243  	if !strings.Contains(err.Error(), expected) {
   244  		f.t.Fatalf("error %v doesn't contain expected text %q", err, expected)
   245  	}
   246  	return result
   247  }
   248  
   249  func (f *extensionFixture) assertLoadRecorded(model starkit.Model, expected ...string) {
   250  	state := MustState(model)
   251  
   252  	expectedSet := map[string]bool{}
   253  	for _, exp := range expected {
   254  		expectedSet[exp] = true
   255  	}
   256  
   257  	assert.Equal(f.t, expectedSet, state.ExtsLoaded)
   258  }
   259  
   260  func (f *extensionFixture) assertNoLoadsRecorded(model starkit.Model) {
   261  	f.assertLoadRecorded(model)
   262  }
   263  
   264  func (f *extensionFixture) writeModuleLocally(name string, contents string) {
   265  	f.tmp.WriteFile(filepath.Join("tilt-extensions", name, "Tiltfile"), contents)
   266  }
   267  
   268  const libText = `
   269  def printFoo():
   270    print("foo")
   271  `
   272  
   273  const printBar = `
   274  def printBar():
   275    print("bar")
   276  `
   277  
   278  const extensionThatLoadsExtension = `
   279  load("ext://barExt", "printBar")
   280  
   281  def printFoo():
   282  	print("foo")
   283  	printBar()
   284  `