github.com/coyove/nj@v0.0.0-20221110084952-c7f8db1065c3/main_test.go (about)

     1  package nj
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"go/parser"
     8  	"log"
     9  	"math"
    10  	"math/rand"
    11  	"os"
    12  	"reflect"
    13  	"runtime"
    14  	"runtime/pprof"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/coyove/nj/bas"
    22  	"github.com/coyove/nj/internal"
    23  	_parser "github.com/coyove/nj/parser"
    24  	"github.com/coyove/nj/typ"
    25  )
    26  
    27  func init() {
    28  	runtime.GOMAXPROCS(runtime.NumCPU() * 2)
    29  	log.SetFlags(log.Lshortfile | log.Ltime)
    30  	bas.AddTopValue("G", bas.Int(1))
    31  }
    32  
    33  type testStruct struct {
    34  	A int
    35  }
    36  
    37  func (ts testStruct) Foo() int { return ts.A }
    38  
    39  func (ts *testStruct) SetFoo(a int) { ts.A = a }
    40  
    41  type testStructEmbed struct {
    42  	T testStruct
    43  }
    44  
    45  type testStructPtr struct {
    46  	T    *testStruct
    47  	Next *testStructPtr
    48  }
    49  
    50  func runFile(t *testing.T, path string) {
    51  	if !flag.Parsed() {
    52  		flag.Parse()
    53  	}
    54  
    55  	b, err := LoadFile(path, &LoadOptions{
    56  		Globals: bas.NewObject(0).
    57  			SetProp("syncMap", bas.ValueOf(&sync.Map{})).
    58  			SetProp("structAddrTest", bas.ValueOf(&testStruct{2})).
    59  			SetProp("structAddrTest2", bas.ValueOf(testStruct{3})).
    60  			SetProp("structAddrTestEmbed", bas.ValueOf(&testStructEmbed{testStruct{4}})).
    61  			SetProp("nativeVarargTest", bas.ValueOf(func(a ...int) int {
    62  				return len(a)
    63  			})).
    64  			SetProp("nativeVarargTest2", bas.ValueOf(func(b string, a ...int) string {
    65  				return b + strconv.Itoa(len(a))
    66  			})).
    67  			SetProp("nativeVarargTest3", bas.ValueOf(func(s testStructPtr) int {
    68  				return s.T.A
    69  			})).
    70  			SetProp("nativeVarargTest4", bas.ValueOf(func(s *testStructPtr, v int) {
    71  				s.T.A = v
    72  			})).
    73  			SetProp("gomap", bas.ValueOf(func(m map[string]int, k string, v int) map[string]int {
    74  				m[k] = v
    75  				return m
    76  			})).
    77  			SetProp("intAlias", bas.ValueOf(func(d time.Duration) time.Time {
    78  				return time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).Add(d)
    79  			})).
    80  			SetProp("goVarg", bas.ValueOf(func(a int, f func(a int, b ...int) int) int {
    81  				return f(a, a+1, a+2)
    82  			})).
    83  			SetProp("boolConvert", bas.ValueOf(func(v bool) {
    84  				if !v {
    85  					panic("bad")
    86  				}
    87  			})).
    88  			SetProp("findGlobal", bas.ValueOf(func(env *bas.Env) {
    89  				v, err := env.MustProgram().Get("G_FLAG")
    90  				fmt.Println(err)
    91  				if v.IsFalse() {
    92  					panic("findGlobal failed")
    93  				}
    94  				env.MustProgram().Set("G_FLAG", bas.Str("ok"))
    95  				fmt.Println("find global")
    96  			})).
    97  			SetProp("G", bas.Str("test")).ToMap(),
    98  	})
    99  	if err != nil {
   100  		t.Fatal(err)
   101  	}
   102  
   103  	if internal.IsDebug() {
   104  		fmt.Println(b.GoString())
   105  	}
   106  
   107  	out := b.Run()
   108  	if out.IsError() {
   109  		t.Fatal(out)
   110  	}
   111  }
   112  
   113  func TestFileTest(t *testing.T) { runFile(t, "tests/test.nj.lua") }
   114  
   115  func TestFileStruct(t *testing.T) { runFile(t, "tests/struct.nj.lua") }
   116  
   117  func TestFileString(t *testing.T) { runFile(t, "tests/string.nj.lua") }
   118  
   119  func TestFileGoto(t *testing.T) { runFile(t, "tests/goto.nj.lua") }
   120  
   121  func TestFileR2(t *testing.T) { runFile(t, "tests/r2.nj.lua") }
   122  
   123  func TestFileStringIndex(t *testing.T) { runFile(t, "tests/indexstr.nj.lua") }
   124  
   125  func TestFileCurry(t *testing.T) { runFile(t, "tests/curry.nj.lua") }
   126  
   127  func TestFileEvaluator(t *testing.T) { runFile(t, "tests/eval.nj.lua") }
   128  
   129  func TestReturnFunction(t *testing.T) {
   130  	{
   131  		cls, _ := LoadString(`
   132  print(init)
   133  a = init
   134  function foo(n) 
   135  a=a+n
   136  return a
   137  end
   138  return foo
   139  `, &LoadOptions{Globals: bas.NewObject(0).SetProp("init", bas.Int(1)).ToMap()})
   140  		v := cls.Run()
   141  		if v := v.Object().Call(nil, bas.Int64(10)); v.Int64() != 11 {
   142  			t.Fatal(v)
   143  		}
   144  
   145  		if v := v.Object().Call(nil, bas.Int64(100)); v.Int64() != 111 {
   146  			t.Fatal(v)
   147  		}
   148  	}
   149  	{
   150  		cls, _ := LoadString(`
   151  a = 1
   152  function foo(x) 
   153  if not x then return a end
   154  for i=0,#(x) do
   155  a=a+x[i]
   156  end
   157  return a
   158  end
   159  return foo
   160  `, nil)
   161  		v := cls.Run()
   162  		if v := v.Object().Call(nil, bas.Array(bas.Int64(1), bas.Int64(2), bas.Int64(3), bas.Int64(4))); v.Int64() != 11 {
   163  			t.Fatal(v)
   164  		}
   165  
   166  		if v := v.Object().Call(nil, bas.Array(bas.Int64(10), bas.Int64(20))); v.Int64() != 41 {
   167  			t.Fatal(v)
   168  		}
   169  	}
   170  }
   171  
   172  func TestTailCallPanic(t *testing.T) {
   173  	cls, err := LoadString(`
   174  x = 0
   175  function foo()
   176  x=x+1
   177  if x == 1e5 then assert(false) end
   178  foo()
   179  end
   180  foo()
   181  `, nil)
   182  	fmt.Println(err, cls.GoString())
   183  	if s := cls.GoString(); !strings.Contains(s, "tailcall") {
   184  		t.Fatal(s)
   185  	}
   186  
   187  	outErr := cls.Run()
   188  	if !outErr.IsError() {
   189  		t.FailNow()
   190  	}
   191  	if len(outErr.String()) > 1e6 { // error too long, which means tail call is not effective
   192  		t.Fatal(len(err.Error()))
   193  	}
   194  }
   195  
   196  func TestArithmeticUnfold(t *testing.T) {
   197  	cls, err := LoadString(`
   198  		return 1 + 2 * 3 / 4
   199  `, nil)
   200  	if err != nil {
   201  		t.Error(err)
   202  	}
   203  
   204  	if v := cls.Run(); v.Float64() != 2.5 {
   205  		t.Error("exec failed")
   206  	}
   207  }
   208  
   209  func TestArithmeticNAN(t *testing.T) {
   210  	cls, err := LoadString(`
   211  a = 0 
   212  		return (1 / a + 1) * a
   213  `, nil)
   214  	if err != nil {
   215  		t.Error(err)
   216  	}
   217  
   218  	if v := cls.Run(); !math.IsNaN(v.Float64()) {
   219  		t.Error("wrong answer")
   220  	}
   221  }
   222  
   223  func init() {
   224  	if os.Getenv("njb") == "1" {
   225  		f, err := os.Create("cpuprofile")
   226  		if err != nil {
   227  			log.Fatal(err)
   228  		}
   229  		pprof.StartCPUProfile(f)
   230  		fmt.Println("cpuprofile")
   231  		for i := 0; i < 1e5; i++ {
   232  			// _parser.Parse("(a+1)", "")
   233  			LoadString("(a+1)", nil)
   234  		}
   235  		pprof.StopCPUProfile()
   236  	}
   237  }
   238  
   239  func BenchmarkParsing(b *testing.B) {
   240  	for i := 0; i < b.N; i++ {
   241  		_parser.Parse("(a+1)", "")
   242  	}
   243  }
   244  
   245  func BenchmarkCompiling(b *testing.B) {
   246  	for i := 0; i < b.N; i++ {
   247  		LoadString("(a+1)", nil)
   248  	}
   249  }
   250  
   251  func BenchmarkGoCompiling(b *testing.B) {
   252  	for i := 0; i < b.N; i++ {
   253  		parser.ParseExpr("(a+1)")
   254  	}
   255  }
   256  
   257  func TestBigList(t *testing.T) {
   258  	n := typ.RegMaxAddress/2 - bas.TopSymbols().Len()
   259  
   260  	makeCode := func(n int) string {
   261  		buf := bytes.Buffer{}
   262  		for i := 0; i < n; i++ {
   263  			buf.WriteString(fmt.Sprintf("a%d = %d\n", i, i))
   264  		}
   265  		buf.WriteString("return [")
   266  		for i := 0; i < n; i++ {
   267  			buf.WriteString(fmt.Sprintf("a%d,", i))
   268  		}
   269  		buf.Truncate(buf.Len() - 1)
   270  		return buf.String() + "]"
   271  	}
   272  
   273  	f, _ := LoadString(makeCode(n), nil)
   274  	// fmt.Println(f.GoString())
   275  	v2 := f.Run()
   276  	if v2.IsError() {
   277  		t.Fatal(v2)
   278  	}
   279  
   280  	for i := 0; i < n; i++ {
   281  		if v2.Native().Get(i).Int() != i {
   282  			t.Fatal(v2)
   283  		}
   284  	}
   285  
   286  	start := time.Now()
   287  	_, err := LoadString(makeCode(typ.RegMaxAddress), nil)
   288  	fmt.Println("load", time.Since(start))
   289  	if !strings.Contains(err.Error(), "too many") {
   290  		t.Fatal(err)
   291  	}
   292  
   293  	{
   294  		buf := bytes.NewBufferString("function foo(")
   295  		for i := 0; i < 256; i++ {
   296  			buf.WriteString(fmt.Sprintf("a%d,", i))
   297  		}
   298  		buf.WriteString("x) end")
   299  		_, err = LoadString(buf.String(), nil)
   300  		if !strings.Contains(err.Error(), "too many") {
   301  			t.Fatal(err)
   302  		}
   303  	}
   304  }
   305  
   306  func TestPlainReturn(t *testing.T) {
   307  	if _, err := LoadString("return", nil); err != nil {
   308  		t.FailNow()
   309  	}
   310  	if _, err := LoadString("return ", nil); err != nil {
   311  		t.FailNow()
   312  	}
   313  	if _, err := LoadString("return \n ", nil); err != nil {
   314  		t.FailNow()
   315  	}
   316  }
   317  
   318  func TestFunctionClosure(t *testing.T) {
   319  	p, _ := LoadString(` local a = 0
   320  function add ()
   321  a=a+1
   322  return a
   323  end
   324  return add`, nil)
   325  	add := p.Run()
   326  
   327  	p2, _ := LoadString(`
   328  local a = 100
   329  return [a + add(), a + add(), a + add()]
   330  `, &LoadOptions{Globals: bas.NewObject(0).SetProp("add", bas.ValueOf(add)).ToMap()})
   331  	v := p2.Run()
   332  	if v.IsError() {
   333  		panic(v)
   334  	}
   335  	fmt.Println(p2.GoString())
   336  	if v1 := v.Native().Values(); v1[0].Int64() != 101 || v1[1].Int64() != 102 || v1[2].Int64() != 103 {
   337  		t.Fatal(v, v1, p2.GoString())
   338  	}
   339  
   340  	add = MustRun(LoadString("function foo(a) panic(a) end return function(b) foo(b) + 1 end", nil))
   341  	outErr := add.Object().TryCall(nil, bas.Int(10))
   342  	if outErr.Error().GetCause() != bas.Int(10) {
   343  		t.Fatal(outErr)
   344  	}
   345  }
   346  
   347  func TestNumberLexer(t *testing.T) {
   348  	assert := func(src string, v bas.Value) {
   349  		_, fn, ln, _ := runtime.Caller(1)
   350  		r := MustRun(LoadString(src, nil))
   351  		if r != v {
   352  			n, _ := _parser.Parse(src, "")
   353  			n.Dump(os.Stdout)
   354  			t.Fatal(fn, ln, r, v)
   355  		}
   356  	}
   357  	assert("1 + 2 ", bas.Int64(3))
   358  	assert("1+ 2 ", bas.Int64(3))
   359  	assert("-1+ 2 ", bas.Int64(1))
   360  	assert("1- 2 ", bas.Int64(-1))
   361  	assert("(1+1)- 2 ", bas.Zero)
   362  	assert("1 - 2 ", bas.Int64(-1))
   363  	assert("1 - -2 ", bas.Int64(3))
   364  	assert("1- -2 ", bas.Int64(3))
   365  	assert("1-2 ", bas.Int64(-1))
   366  	assert("1.5 +2", bas.Float64(3.5))
   367  	assert("1.5+ 2 ", bas.Float64(3.5))
   368  	assert("12.5e-1+ 2 ", bas.Float64(3.25))
   369  	assert("1.5e+1+ 2", bas.Float64(17))
   370  	assert(".5+ 2 ", bas.Float64(2.5))
   371  	assert("-.5+ 2", bas.Float64(1.5))
   372  	assert("0x1+ 2", bas.Int64(3))
   373  	assert("0xE+1 ", bas.Int64(15))
   374  	assert(".5E+1 ", bas.Int64(5))
   375  	assert("0x1_2_e+1", bas.Int64(0x12f))
   376  	assert("([[1]])[0]", bas.Int(49))
   377  	assert("'1'[0]", bas.Int(49))
   378  	assert("([ [1] ])[0][0]", bas.Int(1))
   379  	assert("([ [1]])[0][0]", bas.Int(1))
   380  	assert("[0,1,2][1]", bas.Int(1))
   381  	assert("[[%d]]['format'](1)", bas.Str("1"))
   382  	assert("function()end (1)", bas.Int(1))
   383  	assert("function()end [1][0]", bas.Int(1))
   384  	assert("function() return 1 end()", bas.Int(1))
   385  	assert("function() return-1 end()", bas.Int(-1))
   386  }
   387  
   388  func TestSmallString(t *testing.T) {
   389  	rand.Seed(time.Now().Unix())
   390  	randString := func() string {
   391  		buf := make([]byte, rand.Intn(10))
   392  		for i := range buf {
   393  			buf[i] = byte(rand.Intn(256))
   394  		}
   395  		return string(buf)
   396  	}
   397  	for i := 0; i < 1e6; i++ {
   398  		v := randString()
   399  		if bas.Str(v).Str() != v {
   400  			t.Fatal(bas.Str(v).UnsafeInt64(), v)
   401  		}
   402  	}
   403  }
   404  
   405  func TestStrLess(t *testing.T) {
   406  	a := bas.Str("a")
   407  	b := bas.Str("a\x00")
   408  	t.Log(a, b)
   409  	if !a.Less(b) {
   410  		t.FailNow()
   411  	}
   412  }
   413  
   414  func TestACall(t *testing.T) {
   415  	foo := MustRun(LoadString(`function foo(m...)
   416  	print(m)
   417      assert(m[1] == 1 and m[2] == 2)
   418  	a0 = 0 a1 = 1 a2 = 2 a3 = 3
   419      end
   420      return foo`, nil))
   421  	foo.Object().Call(nil, bas.Nil, bas.Int64(1), bas.Int64(2))
   422  
   423  	foo = MustRun(LoadString(`function foo(a, b, m...)
   424  	assert(a == 1 and #(m) == 0)
   425      end
   426      return foo`, nil))
   427  	foo.Object().Call(nil, bas.Int64(1), bas.Nil)
   428  
   429  	foo = MustRun(LoadString(`m = {a=1}
   430  	function m.pow2()
   431  		return this.a * this.a
   432  	end
   433  	a = new(m, {a=10})
   434      return a`, nil))
   435  	v := foo.Object().Get(bas.Str("pow2")).Object().Call(nil)
   436  	if v.Int64() != 100 {
   437  		t.Fatal(v)
   438  	}
   439  
   440  	foo = MustRun(LoadString(`m.a = 11
   441      return m.pow2()`, &LoadOptions{
   442  		Globals: bas.NewObject(0).
   443  			SetProp("m", bas.NewObject(0).SetPrototype(bas.NewObject(0).
   444  				SetProp("a", bas.Int64(0)).
   445  				AddMethod("pow2", func(e *bas.Env) {
   446  					i := e.Object(-1).Get(bas.Str("a")).Int64()
   447  					e.A = bas.Int64(i * i)
   448  				})).ToValue()).
   449  			ToMap(),
   450  	}))
   451  	if foo.Int64() != 121 {
   452  		t.Fatal(foo)
   453  	}
   454  
   455  	foo = MustRun(LoadString(`function foo(m...)
   456  	return sum(m.concat(m)...) + sum2(m[:2]...)
   457      end
   458      return foo`, &LoadOptions{
   459  		Globals: bas.NewObject(0).
   460  			SetProp("sum", bas.ValueOf(func(a ...int) int {
   461  				s := 0
   462  				for _, a := range a {
   463  					s += a
   464  				}
   465  				return s
   466  			})).
   467  			SetProp("sum2", bas.ValueOf(func(a, b int) int {
   468  				return a + b
   469  			})).
   470  			ToMap(),
   471  	}))
   472  	v = foo.Object().Call(nil, bas.Int64(1), bas.Int64(2), bas.Int64(3))
   473  	if v.Int64() != 15 {
   474  		t.Fatal(v)
   475  	}
   476  }
   477  
   478  func TestReflectedValue(t *testing.T) {
   479  	v := bas.Array(bas.True, bas.False)
   480  	x := v.ToType(reflect.TypeOf([2]bool{})).Interface().([2]bool)
   481  	if x[0] != true || x[1] != false {
   482  		t.Fatal(x)
   483  	}
   484  	v = bas.NewObject(2).SetProp("a", bas.Int64(1)).SetProp("b", bas.Int64(2)).ToValue()
   485  	y := v.ToType(reflect.TypeOf(map[string]byte{})).Interface().(map[string]byte)
   486  	if y["a"] != 1 || y["b"] != 2 {
   487  		t.Fatal(x)
   488  	}
   489  
   490  	p, _ := LoadString(`function foo(v, p)
   491  	p[0] = 99
   492  	return v, v + 1, nil
   493  	end
   494  	bar(foo)`, &LoadOptions{Globals: bas.NewObject(0).
   495  		SetProp("bar", bas.ValueOf(func(cb func(a int, p []byte) (int, int, error)) {
   496  			buf := []byte{0}
   497  			a, b, _ := cb(10, buf)
   498  			if a != 10 || b != 11 || buf[0] != 99 {
   499  				t.Fatal(a, b)
   500  			}
   501  		})).ToMap(),
   502  	})
   503  	err := p.Run()
   504  	if err.IsError() {
   505  		t.Fatal(err)
   506  	}
   507  }
   508  
   509  // func TestRunTimeout(t *testing.T) {
   510  // 	o := bas.NewObject(0)
   511  // 	p, _ := LoadString("for i=0,1e8 do z.a = i end", &bas.Environment{
   512  // 		Globals: bas.NewObject(0).SetProp("z", o.ToValue()),
   513  // 	})
   514  //
   515  // 	p.Deadline = time.Now().Add(time.Second / 2)
   516  // 	_, err := p.Run()
   517  // 	if err.Error() != "timeout" {
   518  // 		t.Fatal(err)
   519  // 	}
   520  // 	if v := o.Prop("a"); v.Maybe().Int(0) == 0 {
   521  // 		t.Fatal(v)
   522  // 	}
   523  //
   524  // 	p.Deadline = time.Now().Add(time.Second / 2)
   525  // 	_, err = p.Run()
   526  // 	if err.Error() != "timeout" {
   527  // 		t.Fatal(err)
   528  // 	}
   529  // 	if v := o.Prop("a"); v.Maybe().Int64(0) == 0 {
   530  // 		t.Fatal(v)
   531  // 	}
   532  // }