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  }