github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/starkit/environment_test.go (about) 1 package starkit 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 "go.starlark.net/starlark" 12 ) 13 14 func TestLoadError(t *testing.T) { 15 f := NewFixture(t) 16 f.File("Tiltfile", ` 17 load('./foo/Tiltfile', "x") 18 `) 19 f.File("foo/Tiltfile", ` 20 x = 1 21 y = x // 0 22 `) 23 24 _, err := f.ExecFile("Tiltfile") 25 if assert.Error(t, err) { 26 backtrace := err.(*starlark.EvalError).Backtrace() 27 assert.Contains(t, backtrace, fmt.Sprintf("%s:2:1: in <toplevel>", f.JoinPath("Tiltfile"))) 28 assert.Contains(t, backtrace, "cannot load ./foo/Tiltfile") 29 } 30 } 31 32 func TestLoadInterceptor(t *testing.T) { 33 f := NewFixture(t) 34 f.UseRealFS() 35 36 f.temp.WriteFile("Tiltfile", ` 37 load('this_path_does_not_matter', "x") 38 `) 39 f.temp.WriteFile("foo/Tiltfile", ` 40 x = 1 41 y = x +1 42 `) 43 44 fi := fakeLoadInterceptor{} 45 f.SetLoadInterceptor(fi) 46 _, err := f.ExecFile("Tiltfile") 47 require.NoError(t, err) 48 } 49 50 func TestLoadInterceptorThatFails(t *testing.T) { 51 f := NewFixture(t) 52 f.File("Tiltfile", ` 53 load('./foo/Tiltfile', "x") 54 `) 55 f.File("foo/Tiltfile", ` 56 x = 1 57 y = x + 1 58 `) 59 60 fi := failLoadInterceptor{} 61 f.SetLoadInterceptor(fi) 62 _, err := f.ExecFile("Tiltfile") 63 require.Error(t, err) 64 assert.Contains(t, err.Error(), "I'm an error look at me!") 65 } 66 67 type fakeLoadInterceptor struct{} 68 69 func (fakeLoadInterceptor) LocalPath(t *starlark.Thread, path string) (string, error) { 70 return "./foo/Tiltfile", nil 71 } 72 73 type failLoadInterceptor struct{} 74 75 func (failLoadInterceptor) LocalPath(t *starlark.Thread, path string) (string, error) { 76 return "", fmt.Errorf("I'm an error look at me!") 77 } 78 79 func NewPluginWithIdentifier(id string) *TestPlugin { 80 return &TestPlugin{identifier: id, callCount: 0} 81 } 82 83 type TestPlugin struct { 84 identifier string 85 86 // Generally, plugins shouldn't store state this way. 87 // They should store it on the Thread object with SetState and friends. 88 // But this is OK for testing. 89 callCount int 90 } 91 92 func (te *TestPlugin) OnStart(e *Environment) error { 93 return e.AddBuiltin(te.identifier, func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (value starlark.Value, err error) { 94 te.callCount++ 95 return starlark.None, nil 96 }) 97 } 98 99 func TestTopLevelBuiltin(t *testing.T) { 100 e := NewPluginWithIdentifier("hi") 101 f := NewFixture(t, e) 102 f.File("Tiltfile", "hi()") 103 _, err := f.ExecFile("Tiltfile") 104 assert.NoError(t, err) 105 assert.Equal(t, 1, e.callCount) 106 } 107 108 func TestModuleBuiltin(t *testing.T) { 109 e := NewPluginWithIdentifier("oh.hai") 110 f := NewFixture(t, e) 111 f.File("Tiltfile", "oh.hai()") 112 _, err := f.ExecFile("Tiltfile") 113 assert.NoError(t, err) 114 assert.Equal(t, 1, e.callCount) 115 } 116 117 func TestNestedModuleBuiltin(t *testing.T) { 118 e := NewPluginWithIdentifier("oh.hai.cat") 119 f := NewFixture(t, e) 120 f.File("Tiltfile", "oh.hai.cat()") 121 _, err := f.ExecFile("Tiltfile") 122 assert.NoError(t, err) 123 assert.Equal(t, 1, e.callCount) 124 } 125 126 func TestDuplicateGlobalName(t *testing.T) { 127 e1 := NewPluginWithIdentifier("foo") 128 e2 := NewPluginWithIdentifier("foo") 129 f := NewFixture(t, e1, e2) 130 f.File("Tiltfile", "foo()") 131 132 _, err := f.ExecFile("Tiltfile") 133 require.Errorf(t, err, "Tiltfile exec should fail") 134 require.Contains(t, err.Error(), "multiple values added named foo") 135 require.Contains(t, err.Error(), "internal error: *starkit.TestPlugin") 136 } 137 138 func TestDuplicateNameWithinModule(t *testing.T) { 139 e1 := NewPluginWithIdentifier("bar.foo") 140 e2 := NewPluginWithIdentifier("bar.foo") 141 f := NewFixture(t, e1, e2) 142 f.File("Tiltfile", "bar.foo()") 143 144 _, err := f.ExecFile("Tiltfile") 145 require.Errorf(t, err, "Tiltfile exec should fail") 146 require.Contains(t, err.Error(), "multiple values added named bar.foo") 147 require.Contains(t, err.Error(), "internal error: *starkit.TestPlugin") 148 } 149 150 type PwdPlugin struct{} 151 152 func (e PwdPlugin) OnStart(env *Environment) error { 153 return env.AddBuiltin("pwd", pwd) 154 } 155 156 func pwd(t *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 157 t.Print(t, CurrentExecPath(t)) 158 return starlark.None, nil 159 } 160 161 // foo loads bar 162 // bar defines `hello` and finishes loading 163 // foo calls `hello`, which prints foo 164 func TestUsePwdOfCallSiteLoadingTiltfile(t *testing.T) { 165 f := NewFixture(t, PwdPlugin{}) 166 f.File("bar/Tiltfile", ` 167 def hello(): 168 pwd() 169 `) 170 f.File("foo/Tiltfile", ` 171 load('../bar/Tiltfile', 'hello') 172 hello() 173 `) 174 175 _, err := f.ExecFile("foo/Tiltfile") 176 require.NoError(t, err) 177 178 path := strings.TrimSpace(f.out.String()) 179 require.Equal(t, "foo", filepath.Base(filepath.Dir(path))) 180 } 181 182 // foo loads bar 183 // bar calls pwd while it's loading, which prints bar 184 func TestUsePwdOfCallSiteLoadedTiltfile(t *testing.T) { 185 f := NewFixture(t, PwdPlugin{}) 186 f.File("bar/Tiltfile", ` 187 def unused(): 188 pass 189 pwd() 190 `) 191 f.File("foo/Tiltfile", ` 192 load('../bar/Tiltfile', 'unused') 193 `) 194 195 _, err := f.ExecFile("foo/Tiltfile") 196 require.NoError(t, err) 197 198 path := strings.TrimSpace(f.out.String()) 199 require.Equal(t, "bar", filepath.Base(filepath.Dir(path))) 200 } 201 202 // Tiltfile loads Tiltfile2 203 // 1 prints its __file__ and calls a method in 2 to do the same 204 func TestUseMagicFileVar(t *testing.T) { 205 f := NewFixture(t, PwdPlugin{}) 206 f.File("Tiltfile2", ` 207 def print_mypath(): 208 print(__file__) 209 `) 210 f.File("Tiltfile", ` 211 load('Tiltfile2', 'print_mypath') 212 print(__file__) 213 print_mypath() 214 `) 215 216 _, err := f.ExecFile("Tiltfile") 217 require.NoError(t, err) 218 219 paths := strings.Split(strings.TrimSpace(f.out.String()), "\n") 220 require.Equal(t, "Tiltfile", filepath.Base(paths[0])) 221 require.Equal(t, "Tiltfile2", filepath.Base(paths[1])) 222 } 223 224 func TestSupportsSet(t *testing.T) { 225 f := NewFixture(t, PwdPlugin{}) 226 f.File("Tiltfile", ` 227 x = set([1, 2, 1]) 228 print(x) 229 `) 230 231 _, err := f.ExecFile("Tiltfile") 232 require.NoError(t, err) 233 234 assert.Equal(t, "set([1, 2])\n", f.out.String()) 235 } 236 237 func TestSupportDictUnion(t *testing.T) { 238 f := NewFixture(t, PwdPlugin{}) 239 f.File("Tiltfile", ` 240 x = {'a': 1} | {'b': 2} 241 print(x) 242 `) 243 244 _, err := f.ExecFile("Tiltfile") 245 require.NoError(t, err) 246 247 assert.Equal(t, `{"a": 1, "b": 2} 248 `, f.out.String()) 249 }