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 `