github.com/julianthome/gore@v0.0.0-20231109011145-b3a6bbe6fe55/session_test.go (about)

     1  package gore
     2  
     3  import (
     4  	"os"
     5  	"regexp"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  func init() {
    14  	printerPkgs = printerPkgs[1:]
    15  }
    16  
    17  func newTempDir(t *testing.T) string {
    18  	dir, err := os.MkdirTemp("", "gore-test-")
    19  	if err != nil {
    20  		t.Fatal(err)
    21  	}
    22  	t.Cleanup(func() { os.RemoveAll(dir) })
    23  
    24  	wd, err := os.Getwd()
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	t.Cleanup(func() { os.Chdir(wd) })
    29  
    30  	err = os.Chdir(dir)
    31  	if err != nil {
    32  		t.Fatal(err)
    33  	}
    34  
    35  	return dir
    36  }
    37  
    38  func TestSessionEval_import(t *testing.T) {
    39  	var stdout, stderr strings.Builder
    40  	s, err := NewSession(&stdout, &stderr)
    41  	t.Cleanup(func() { s.Clear() })
    42  	require.NoError(t, err)
    43  
    44  	codes := []string{
    45  		":import encoding/json",
    46  		"b, err := json.Marshal(nil)",
    47  		"string(b)",
    48  	}
    49  
    50  	for _, code := range codes {
    51  		err := s.Eval(code)
    52  		require.NoError(t, err)
    53  	}
    54  
    55  	assert.Equal(t, `[]byte{0x6e, 0x75, 0x6c, 0x6c}
    56  <nil>
    57  "null"
    58  `, stdout.String())
    59  	assert.Equal(t, "", stderr.String())
    60  }
    61  
    62  func TestSessionEval_QuickFix_evaluated_but_not_used(t *testing.T) {
    63  	var stdout, stderr strings.Builder
    64  	s, err := NewSession(&stdout, &stderr)
    65  	t.Cleanup(func() { s.Clear() })
    66  	require.NoError(t, err)
    67  
    68  	codes := []string{
    69  		`[]byte("")`,
    70  		`make([]int, 0)`,
    71  		`1+1`,
    72  		`func() {}`,
    73  		`(4 & (1 << 1))`,
    74  		`1`,
    75  	}
    76  
    77  	for _, code := range codes {
    78  		err := s.Eval(code)
    79  		require.NoError(t, err)
    80  	}
    81  
    82  	r := regexp.MustCompile(`0x[0-9a-f]+`)
    83  	assert.Equal(t, `[]byte{}
    84  []int{}
    85  2
    86  (func())(...)
    87  0
    88  1
    89  `, r.ReplaceAllString(stdout.String(), "..."))
    90  	assert.Equal(t, "", stderr.String())
    91  }
    92  
    93  func TestSessionEval_QuickFix_used_as_value(t *testing.T) {
    94  	var stdout, stderr strings.Builder
    95  	s, err := NewSession(&stdout, &stderr)
    96  	t.Cleanup(func() { s.Clear() })
    97  	require.NoError(t, err)
    98  
    99  	codes := []string{
   100  		`:import log`,
   101  		`a := 1`,
   102  		`log.SetPrefix("")`,
   103  	}
   104  
   105  	for _, code := range codes {
   106  		err := s.Eval(code)
   107  		require.NoError(t, err)
   108  	}
   109  
   110  	assert.Equal(t, "1\n", stdout.String())
   111  	assert.Equal(t, "", stderr.String())
   112  }
   113  
   114  func TestSessionEval_QuickFix_no_new_variables(t *testing.T) {
   115  	var stdout, stderr strings.Builder
   116  	s, err := NewSession(&stdout, &stderr)
   117  	t.Cleanup(func() { s.Clear() })
   118  	require.NoError(t, err)
   119  
   120  	codes := []string{
   121  		`var a, b int`,
   122  		`a := 2`,
   123  		`b := a * 2`,
   124  		`a := 3`,
   125  		`c := a * b`,
   126  		`c := b * c`,
   127  		`b := c * a`,
   128  		`a * b * c`,
   129  	}
   130  
   131  	for _, code := range codes {
   132  		err := s.Eval(code)
   133  		require.NoError(t, err)
   134  	}
   135  
   136  	assert.Equal(t, `2
   137  4
   138  3
   139  12
   140  48
   141  144
   142  20736
   143  `, stdout.String())
   144  	assert.Equal(t, "", stderr.String())
   145  }
   146  
   147  func TestSessionEval_AutoImport(t *testing.T) {
   148  	var stdout, stderr strings.Builder
   149  	s, err := NewSession(&stdout, &stderr)
   150  	t.Cleanup(func() { s.Clear() })
   151  	require.NoError(t, err)
   152  	s.autoImport = true
   153  
   154  	codes := []string{
   155  		`filepath.Join("a", "b")`,
   156  	}
   157  
   158  	for _, code := range codes {
   159  		err := s.Eval(code)
   160  		require.NoError(t, err)
   161  	}
   162  
   163  	assert.Equal(t, "\"a/b\"\n", stdout.String())
   164  	assert.Equal(t, "", stderr.String())
   165  }
   166  
   167  func TestSession_IncludePackage(t *testing.T) {
   168  	var stdout, stderr strings.Builder
   169  	s, err := NewSession(&stdout, &stderr)
   170  	t.Cleanup(func() { s.Clear() })
   171  	require.NoError(t, err)
   172  
   173  	err = s.includePackage("github.com/x-motemen/gore/gocode")
   174  	require.NoError(t, err)
   175  
   176  	err = s.Eval("Completer{}")
   177  	require.NoError(t, err)
   178  }
   179  
   180  func TestSessionEval_Copy(t *testing.T) {
   181  	var stdout, stderr strings.Builder
   182  	s, err := NewSession(&stdout, &stderr)
   183  	t.Cleanup(func() { s.Clear() })
   184  	require.NoError(t, err)
   185  
   186  	codes := []string{
   187  		`a := []string{"hello", "world"}`,
   188  		`b := []string{"goodbye", "world"}`,
   189  		`copy(a, b)`,
   190  		`a[0]`,
   191  	}
   192  
   193  	for _, code := range codes {
   194  		err := s.Eval(code)
   195  		require.NoError(t, err)
   196  	}
   197  
   198  	assert.Equal(t, `[]string{"hello", "world"}
   199  []string{"goodbye", "world"}
   200  2
   201  "goodbye"
   202  `, stdout.String())
   203  	assert.Equal(t, "", stderr.String())
   204  }
   205  
   206  func TestSessionEval_Const(t *testing.T) {
   207  	var stdout, stderr strings.Builder
   208  	s, err := NewSession(&stdout, &stderr)
   209  	t.Cleanup(func() { s.Clear() })
   210  	require.NoError(t, err)
   211  
   212  	codes := []string{
   213  		`const ( a = iota; b )`,
   214  		`a`,
   215  		`b`,
   216  	}
   217  
   218  	for _, code := range codes {
   219  		err := s.Eval(code)
   220  		require.NoError(t, err)
   221  	}
   222  
   223  	assert.Equal(t, "0\n1\n0\n1\n", stdout.String())
   224  	assert.Equal(t, "", stderr.String())
   225  }
   226  
   227  func TestSessionEval_Declarations(t *testing.T) {
   228  	var stdout, stderr strings.Builder
   229  	s, err := NewSession(&stdout, &stderr)
   230  	t.Cleanup(func() { s.Clear() })
   231  	require.NoError(t, err)
   232  
   233  	codes := []string{
   234  		`var a, b int = 10, 20`,
   235  		`var (x int = 10; y string = "hello"; z uint64; w error; v func(string)int)`,
   236  	}
   237  
   238  	for _, code := range codes {
   239  		err := s.Eval(code)
   240  		require.NoError(t, err)
   241  	}
   242  
   243  	assert.Equal(t, `10
   244  20
   245  10
   246  "hello"
   247  0x0
   248  <nil>
   249  (func(string) int)(nil)
   250  `, stdout.String())
   251  	assert.Equal(t, "", stderr.String())
   252  }
   253  
   254  func TestSessionEval_NotUsed(t *testing.T) {
   255  	var stdout, stderr strings.Builder
   256  	s, err := NewSession(&stdout, &stderr)
   257  	t.Cleanup(func() { s.Clear() })
   258  	require.NoError(t, err)
   259  
   260  	codes := []string{
   261  		`f := func() []int { return []int{1, 2, 3} }`,
   262  		`len(f())`,
   263  		`3`,
   264  		`len(f()) + len(f())`,
   265  		`var x int`,
   266  		`g := func() int { x++; return 128 }`,
   267  		`g() + g()`,
   268  		`g() * g()`,
   269  		`x`,
   270  	}
   271  
   272  	for _, code := range codes {
   273  		_ = s.Eval(code)
   274  	}
   275  
   276  	r := regexp.MustCompile(`0x[0-9a-f]+`)
   277  	assert.Equal(t, `(func() []int)(...)
   278  3
   279  3
   280  6
   281  (func() int)(...)
   282  256
   283  16384
   284  4
   285  `, r.ReplaceAllString(stdout.String(), "..."))
   286  	assert.Equal(t, "", stderr.String())
   287  }
   288  
   289  func TestSessionEval_MultipleValues(t *testing.T) {
   290  	var stdout, stderr strings.Builder
   291  	s, err := NewSession(&stdout, &stderr)
   292  	t.Cleanup(func() { s.Clear() })
   293  	require.NoError(t, err)
   294  
   295  	codes := []string{
   296  		`var err error`,
   297  		`:import fmt`,
   298  		`fmt.Print()`,
   299  		`fmt.Print()`,
   300  		`:import io`,
   301  		`_, err = func() (int, error) { return 0, io.EOF }()`,
   302  		`err.Error()`,
   303  		`var x int`,
   304  		`x, err = 10, fmt.Errorf("test")`,
   305  		`x`,
   306  		`err.Error()`,
   307  	}
   308  
   309  	for _, code := range codes {
   310  		_ = s.Eval(code)
   311  	}
   312  
   313  	assert.Equal(t, `0
   314  <nil>
   315  0
   316  <nil>
   317  &errors.errorString{s:"EOF"}
   318  "EOF"
   319  10
   320  &errors.errorString{s:"test"}
   321  10
   322  "test"
   323  `, stdout.String())
   324  	assert.Equal(t, "", stderr.String())
   325  }
   326  
   327  func TestSessionEval_Struct(t *testing.T) {
   328  	var stdout, stderr strings.Builder
   329  	s, err := NewSession(&stdout, &stderr)
   330  	t.Cleanup(func() { s.Clear() })
   331  	require.NoError(t, err)
   332  
   333  	codes := []string{
   334  		`type X struct { v int }`,
   335  		`func (x *X) add(v int) { x.v += v }`,
   336  		`var x X`,
   337  		`x`,
   338  		`x.add(1)`,
   339  		`x`,
   340  		`x.add(2)`,
   341  		`x`,
   342  		`type Y X; type Z Y;`,
   343  		`func (z *Z) sub(v int) { z.v -= v }`,
   344  		`var z Z`,
   345  		`z.sub(3)`,
   346  		`z`,
   347  	}
   348  
   349  	for _, code := range codes {
   350  		_ = s.Eval(code)
   351  	}
   352  
   353  	assert.Contains(t, stdout.String(), `main.X{v:0}
   354  main.X{v:1}
   355  main.X{v:3}
   356  main.Z{v:-3}`)
   357  	assert.Equal(t, ``, stderr.String())
   358  }
   359  
   360  func TestSessionEval_Func(t *testing.T) {
   361  	var stdout, stderr strings.Builder
   362  	s, err := NewSession(&stdout, &stderr)
   363  	t.Cleanup(func() { s.Clear() })
   364  	require.NoError(t, err)
   365  
   366  	codes := []string{
   367  		`func f() int { return 100 }`,
   368  		`func g() string { return "hello, world" }`,
   369  		`func h() int { s := ""; return s }`,
   370  		`f() + len(g())`,
   371  		`func f() int { return 200 }`,
   372  		`f() * len(g())`,
   373  		`func f() string { i := 100; return i }`,
   374  		`f() | len(g())`,
   375  	}
   376  
   377  	for _, code := range codes {
   378  		_ = s.Eval(code)
   379  	}
   380  
   381  	assert.Equal(t, "112\n2400\n204\n", stdout.String())
   382  	assert.Regexp(t, `cannot use s \((?:variable of )?type string\) as (?:type int|int value) in return (?:argument|statement)
   383  invalid operation: f\(\) \+ len\(g\(\)\) \(mismatched types string and int\)
   384  invalid operation: f\(\) \* len\(g\(\)\) \(mismatched types string and int\)
   385  cannot use i \((?:variable of )?type int\) as (?:type string|string value) in return (?:argument|statement)
   386  `, stderr.String())
   387  }
   388  
   389  func TestSessionEval_TokenError(t *testing.T) {
   390  	var stdout, stderr strings.Builder
   391  	s, err := NewSession(&stdout, &stderr)
   392  	t.Cleanup(func() { s.Clear() })
   393  	require.NoError(t, err)
   394  
   395  	codes := []string{
   396  		`foo\`,
   397  		`ba # r`,
   398  		`$ + 3`,
   399  		"`foo",
   400  		"`foo\nbar`",
   401  	}
   402  
   403  	for _, code := range codes {
   404  		_ = s.Eval(code)
   405  	}
   406  
   407  	assert.Equal(t, "\"foo\\nbar\"\n", stdout.String())
   408  	assert.Equal(t, `invalid token: "\\"
   409  invalid token: "#"
   410  invalid token: "$"
   411  `, stderr.String())
   412  }
   413  
   414  func TestSessionEval_CompileError(t *testing.T) {
   415  	var stdout, stderr strings.Builder
   416  	s, err := NewSession(&stdout, &stderr)
   417  	t.Cleanup(func() { s.Clear() })
   418  	require.NoError(t, err)
   419  
   420  	codes := []string{
   421  		`foo`,
   422  		`func f() int { return 100 }`,
   423  		`func g() string { return "hello" }`,
   424  		`len(f())`,
   425  		`len(g())`,
   426  		`f() + g()`,
   427  		`f() + len(g())`,
   428  	}
   429  
   430  	for _, code := range codes {
   431  		_ = s.Eval(code)
   432  	}
   433  
   434  	assert.Equal(t, "5\n105\n", stdout.String())
   435  	assert.Regexp(t, `undefined: foo
   436  invalid argument:? f\(\) \((?:value of )?type int\) for len
   437  invalid operation: f\(\) \+ g\(\) \(mismatched types int and string\)
   438  `, stderr.String())
   439  }
   440  
   441  func TestSession_ExtraFiles(t *testing.T) {
   442  	var stdout, stderr strings.Builder
   443  	_ = newTempDir(t)
   444  	require.NoError(t, os.WriteFile("test.go", []byte(`package test
   445  
   446  // V is a value
   447  var V = 42
   448  `), 0o644))
   449  	s, err := NewSession(&stdout, &stderr)
   450  	t.Cleanup(func() { s.Clear() })
   451  	require.NoError(t, err)
   452  
   453  	s.includeFiles([]string{"test.go"})
   454  	codes := []string{
   455  		`V`,
   456  		`:type V`,
   457  		`:doc V`,
   458  	}
   459  
   460  	for _, code := range codes {
   461  		_ = s.Eval(code)
   462  	}
   463  
   464  	assert.Contains(t, stdout.String(), `42
   465  int
   466  package builtin`)
   467  	assert.Equal(t, ``, stderr.String())
   468  }