script_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. package tengo_test
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "math/rand"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. "testing"
  11. "time"
  12. "github.com/d5/tengo/v2"
  13. "github.com/d5/tengo/v2/require"
  14. "github.com/d5/tengo/v2/stdlib"
  15. "github.com/d5/tengo/v2/token"
  16. )
  17. func TestScript_Add(t *testing.T) {
  18. s := tengo.NewScript([]byte(`a := b; c := test(b); d := test(5)`))
  19. require.NoError(t, s.Add("b", 5)) // b = 5
  20. require.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation)
  21. require.NoError(t, s.Add("test",
  22. func(args ...tengo.Object) (ret tengo.Object, err error) {
  23. if len(args) > 0 {
  24. switch arg := args[0].(type) {
  25. case *tengo.Int:
  26. return &tengo.Int{Value: arg.Value + 1}, nil
  27. }
  28. }
  29. return &tengo.Int{Value: 0}, nil
  30. }))
  31. c, err := s.Compile()
  32. require.NoError(t, err)
  33. require.NoError(t, c.Run())
  34. require.Equal(t, "foo", c.Get("a").Value())
  35. require.Equal(t, "foo", c.Get("b").Value())
  36. require.Equal(t, int64(0), c.Get("c").Value())
  37. require.Equal(t, int64(6), c.Get("d").Value())
  38. }
  39. func TestScript_Remove(t *testing.T) {
  40. s := tengo.NewScript([]byte(`a := b`))
  41. err := s.Add("b", 5)
  42. require.NoError(t, err)
  43. require.True(t, s.Remove("b")) // b is removed
  44. _, err = s.Compile() // should not compile because b is undefined
  45. require.Error(t, err)
  46. }
  47. func TestScript_Run(t *testing.T) {
  48. s := tengo.NewScript([]byte(`a := b`))
  49. err := s.Add("b", 5)
  50. require.NoError(t, err)
  51. c, err := s.Run()
  52. require.NoError(t, err)
  53. require.NotNil(t, c)
  54. compiledGet(t, c, "a", int64(5))
  55. }
  56. func TestScript_BuiltinModules(t *testing.T) {
  57. s := tengo.NewScript([]byte(`math := import("math"); a := math.abs(-19.84)`))
  58. s.SetImports(stdlib.GetModuleMap("math"))
  59. c, err := s.Run()
  60. require.NoError(t, err)
  61. require.NotNil(t, c)
  62. compiledGet(t, c, "a", 19.84)
  63. c, err = s.Run()
  64. require.NoError(t, err)
  65. require.NotNil(t, c)
  66. compiledGet(t, c, "a", 19.84)
  67. s.SetImports(stdlib.GetModuleMap("os"))
  68. _, err = s.Run()
  69. require.Error(t, err)
  70. s.SetImports(nil)
  71. _, err = s.Run()
  72. require.Error(t, err)
  73. }
  74. func TestScript_SourceModules(t *testing.T) {
  75. s := tengo.NewScript([]byte(`
  76. enum := import("enum")
  77. a := enum.all([1,2,3], func(_, v) {
  78. return v > 0
  79. })
  80. `))
  81. s.SetImports(stdlib.GetModuleMap("enum"))
  82. c, err := s.Run()
  83. require.NoError(t, err)
  84. require.NotNil(t, c)
  85. compiledGet(t, c, "a", true)
  86. s.SetImports(nil)
  87. _, err = s.Run()
  88. require.Error(t, err)
  89. }
  90. func TestScript_SetMaxConstObjects(t *testing.T) {
  91. // one constant '5'
  92. s := tengo.NewScript([]byte(`a := 5`))
  93. s.SetMaxConstObjects(1) // limit = 1
  94. _, err := s.Compile()
  95. require.NoError(t, err)
  96. s.SetMaxConstObjects(0) // limit = 0
  97. _, err = s.Compile()
  98. require.Error(t, err)
  99. require.Equal(t, "exceeding constant objects limit: 1", err.Error())
  100. // two constants '5' and '1'
  101. s = tengo.NewScript([]byte(`a := 5 + 1`))
  102. s.SetMaxConstObjects(2) // limit = 2
  103. _, err = s.Compile()
  104. require.NoError(t, err)
  105. s.SetMaxConstObjects(1) // limit = 1
  106. _, err = s.Compile()
  107. require.Error(t, err)
  108. require.Equal(t, "exceeding constant objects limit: 2", err.Error())
  109. // duplicates will be removed
  110. s = tengo.NewScript([]byte(`a := 5 + 5`))
  111. s.SetMaxConstObjects(1) // limit = 1
  112. _, err = s.Compile()
  113. require.NoError(t, err)
  114. s.SetMaxConstObjects(0) // limit = 0
  115. _, err = s.Compile()
  116. require.Error(t, err)
  117. require.Equal(t, "exceeding constant objects limit: 1", err.Error())
  118. // no limit set
  119. s = tengo.NewScript([]byte(`a := 1 + 2 + 3 + 4 + 5`))
  120. _, err = s.Compile()
  121. require.NoError(t, err)
  122. }
  123. func TestScriptConcurrency(t *testing.T) {
  124. solve := func(a, b, c int) (d, e int) {
  125. a += 2
  126. b += c
  127. a += b * 2
  128. d = a + b + c
  129. e = 0
  130. for i := 1; i <= d; i++ {
  131. e += i
  132. }
  133. e *= 2
  134. return
  135. }
  136. code := []byte(`
  137. mod1 := import("mod1")
  138. a += 2
  139. b += c
  140. a += b * 2
  141. arr := [a, b, c]
  142. arrstr := string(arr)
  143. map := {a: a, b: b, c: c}
  144. d := a + b + c
  145. s := 0
  146. for i:=1; i<=d; i++ {
  147. s += i
  148. }
  149. e := mod1.double(s)
  150. `)
  151. mod1 := map[string]tengo.Object{
  152. "double": &tengo.UserFunction{
  153. Value: func(args ...tengo.Object) (
  154. ret tengo.Object,
  155. err error,
  156. ) {
  157. arg0, _ := tengo.ToInt64(args[0])
  158. ret = &tengo.Int{Value: arg0 * 2}
  159. return
  160. },
  161. },
  162. }
  163. scr := tengo.NewScript(code)
  164. _ = scr.Add("a", 0)
  165. _ = scr.Add("b", 0)
  166. _ = scr.Add("c", 0)
  167. mods := tengo.NewModuleMap()
  168. mods.AddBuiltinModule("mod1", mod1)
  169. scr.SetImports(mods)
  170. compiled, err := scr.Compile()
  171. require.NoError(t, err)
  172. executeFn := func(compiled *tengo.Compiled, a, b, c int) (d, e int) {
  173. _ = compiled.Set("a", a)
  174. _ = compiled.Set("b", b)
  175. _ = compiled.Set("c", c)
  176. err := compiled.Run()
  177. require.NoError(t, err)
  178. d = compiled.Get("d").Int()
  179. e = compiled.Get("e").Int()
  180. return
  181. }
  182. concurrency := 500
  183. var wg sync.WaitGroup
  184. wg.Add(concurrency)
  185. for i := 0; i < concurrency; i++ {
  186. go func(compiled *tengo.Compiled) {
  187. time.Sleep(time.Duration(rand.Int63n(50)) * time.Millisecond)
  188. defer wg.Done()
  189. a := rand.Intn(10)
  190. b := rand.Intn(10)
  191. c := rand.Intn(10)
  192. d, e := executeFn(compiled, a, b, c)
  193. expectedD, expectedE := solve(a, b, c)
  194. require.Equal(t, expectedD, d, "input: %d, %d, %d", a, b, c)
  195. require.Equal(t, expectedE, e, "input: %d, %d, %d", a, b, c)
  196. }(compiled.Clone())
  197. }
  198. wg.Wait()
  199. }
  200. type Counter struct {
  201. tengo.ObjectImpl
  202. value int64
  203. }
  204. func (o *Counter) TypeName() string {
  205. return "counter"
  206. }
  207. func (o *Counter) String() string {
  208. return fmt.Sprintf("Counter(%d)", o.value)
  209. }
  210. func (o *Counter) BinaryOp(
  211. op token.Token,
  212. rhs tengo.Object,
  213. ) (tengo.Object, error) {
  214. switch rhs := rhs.(type) {
  215. case *Counter:
  216. switch op {
  217. case token.Add:
  218. return &Counter{value: o.value + rhs.value}, nil
  219. case token.Sub:
  220. return &Counter{value: o.value - rhs.value}, nil
  221. }
  222. case *tengo.Int:
  223. switch op {
  224. case token.Add:
  225. return &Counter{value: o.value + rhs.Value}, nil
  226. case token.Sub:
  227. return &Counter{value: o.value - rhs.Value}, nil
  228. }
  229. }
  230. return nil, errors.New("invalid operator")
  231. }
  232. func (o *Counter) IsFalsy() bool {
  233. return o.value == 0
  234. }
  235. func (o *Counter) Equals(t tengo.Object) bool {
  236. if tc, ok := t.(*Counter); ok {
  237. return o.value == tc.value
  238. }
  239. return false
  240. }
  241. func (o *Counter) Copy() tengo.Object {
  242. return &Counter{value: o.value}
  243. }
  244. func (o *Counter) Call(_ ...tengo.Object) (tengo.Object, error) {
  245. return &tengo.Int{Value: o.value}, nil
  246. }
  247. func (o *Counter) CanCall() bool {
  248. return true
  249. }
  250. func TestScript_CustomObjects(t *testing.T) {
  251. c := compile(t, `a := c1(); s := string(c1); c2 := c1; c2++`, M{
  252. "c1": &Counter{value: 5},
  253. })
  254. compiledRun(t, c)
  255. compiledGet(t, c, "a", int64(5))
  256. compiledGet(t, c, "s", "Counter(5)")
  257. compiledGetCounter(t, c, "c2", &Counter{value: 6})
  258. c = compile(t, `
  259. arr := [1, 2, 3, 4]
  260. for x in arr {
  261. c1 += x
  262. }
  263. out := c1()
  264. `, M{
  265. "c1": &Counter{value: 5},
  266. })
  267. compiledRun(t, c)
  268. compiledGet(t, c, "out", int64(15))
  269. }
  270. func compiledGetCounter(
  271. t *testing.T,
  272. c *tengo.Compiled,
  273. name string,
  274. expected *Counter,
  275. ) {
  276. v := c.Get(name)
  277. require.NotNil(t, v)
  278. actual := v.Value().(*Counter)
  279. require.NotNil(t, actual)
  280. require.Equal(t, expected.value, actual.value)
  281. }
  282. func TestScriptSourceModule(t *testing.T) {
  283. // script1 imports "mod1"
  284. scr := tengo.NewScript([]byte(`out := import("mod")`))
  285. mods := tengo.NewModuleMap()
  286. mods.AddSourceModule("mod", []byte(`export 5`))
  287. scr.SetImports(mods)
  288. c, err := scr.Run()
  289. require.NoError(t, err)
  290. require.Equal(t, int64(5), c.Get("out").Value())
  291. // executing module function
  292. scr = tengo.NewScript([]byte(`fn := import("mod"); out := fn()`))
  293. mods = tengo.NewModuleMap()
  294. mods.AddSourceModule("mod",
  295. []byte(`a := 3; export func() { return a + 5 }`))
  296. scr.SetImports(mods)
  297. c, err = scr.Run()
  298. require.NoError(t, err)
  299. require.Equal(t, int64(8), c.Get("out").Value())
  300. scr = tengo.NewScript([]byte(`out := import("mod")`))
  301. mods = tengo.NewModuleMap()
  302. mods.AddSourceModule("mod",
  303. []byte(`text := import("text"); export text.title("foo")`))
  304. mods.AddBuiltinModule("text",
  305. map[string]tengo.Object{
  306. "title": &tengo.UserFunction{
  307. Name: "title",
  308. Value: func(args ...tengo.Object) (tengo.Object, error) {
  309. s, _ := tengo.ToString(args[0])
  310. return &tengo.String{Value: strings.Title(s)}, nil
  311. }},
  312. })
  313. scr.SetImports(mods)
  314. c, err = scr.Run()
  315. require.NoError(t, err)
  316. require.Equal(t, "Foo", c.Get("out").Value())
  317. scr.SetImports(nil)
  318. _, err = scr.Run()
  319. require.Error(t, err)
  320. }
  321. func BenchmarkArrayIndex(b *testing.B) {
  322. bench(b.N, `a := [1, 2, 3, 4, 5, 6, 7, 8, 9];
  323. for i := 0; i < 1000; i++ {
  324. a[0]; a[1]; a[2]; a[3]; a[4]; a[5]; a[6]; a[7]; a[7];
  325. }
  326. `)
  327. }
  328. func BenchmarkArrayIndexCompare(b *testing.B) {
  329. bench(b.N, `a := [1, 2, 3, 4, 5, 6, 7, 8, 9];
  330. for i := 0; i < 1000; i++ {
  331. 1; 2; 3; 4; 5; 6; 7; 8; 9;
  332. }
  333. `)
  334. }
  335. func bench(n int, input string) {
  336. s := tengo.NewScript([]byte(input))
  337. c, err := s.Compile()
  338. if err != nil {
  339. panic(err)
  340. }
  341. for i := 0; i < n; i++ {
  342. if err := c.Run(); err != nil {
  343. panic(err)
  344. }
  345. }
  346. }
  347. type M map[string]interface{}
  348. func TestCompiled_Get(t *testing.T) {
  349. // simple script
  350. c := compile(t, `a := 5`, nil)
  351. compiledRun(t, c)
  352. compiledGet(t, c, "a", int64(5))
  353. // user-defined variables
  354. compileError(t, `a := b`, nil) // compile error because "b" is not defined
  355. c = compile(t, `a := b`, M{"b": "foo"}) // now compile with b = "foo" defined
  356. compiledGet(t, c, "a", nil) // a = undefined; because it's before Compiled.Run()
  357. compiledRun(t, c) // Compiled.Run()
  358. compiledGet(t, c, "a", "foo") // a = "foo"
  359. }
  360. func TestCompiled_GetAll(t *testing.T) {
  361. c := compile(t, `a := 5`, nil)
  362. compiledRun(t, c)
  363. compiledGetAll(t, c, M{"a": int64(5)})
  364. c = compile(t, `a := b`, M{"b": "foo"})
  365. compiledRun(t, c)
  366. compiledGetAll(t, c, M{"a": "foo", "b": "foo"})
  367. c = compile(t, `a := b; b = 5`, M{"b": "foo"})
  368. compiledRun(t, c)
  369. compiledGetAll(t, c, M{"a": "foo", "b": int64(5)})
  370. }
  371. func TestCompiled_IsDefined(t *testing.T) {
  372. c := compile(t, `a := 5`, nil)
  373. compiledIsDefined(t, c, "a", false) // a is not defined before Run()
  374. compiledRun(t, c)
  375. compiledIsDefined(t, c, "a", true)
  376. compiledIsDefined(t, c, "b", false)
  377. }
  378. func TestCompiled_Set(t *testing.T) {
  379. c := compile(t, `a := b`, M{"b": "foo"})
  380. compiledRun(t, c)
  381. compiledGet(t, c, "a", "foo")
  382. // replace value of 'b'
  383. err := c.Set("b", "bar")
  384. require.NoError(t, err)
  385. compiledRun(t, c)
  386. compiledGet(t, c, "a", "bar")
  387. // try to replace undefined variable
  388. err = c.Set("c", 1984)
  389. require.Error(t, err) // 'c' is not defined
  390. // case #2
  391. c = compile(t, `
  392. a := func() {
  393. return func() {
  394. return b + 5
  395. }()
  396. }()`, M{"b": 5})
  397. compiledRun(t, c)
  398. compiledGet(t, c, "a", int64(10))
  399. err = c.Set("b", 10)
  400. require.NoError(t, err)
  401. compiledRun(t, c)
  402. compiledGet(t, c, "a", int64(15))
  403. }
  404. func TestCompiled_RunContext(t *testing.T) {
  405. // machine completes normally
  406. c := compile(t, `a := 5`, nil)
  407. err := c.RunContext(context.Background())
  408. require.NoError(t, err)
  409. compiledGet(t, c, "a", int64(5))
  410. // timeout
  411. c = compile(t, `for true {}`, nil)
  412. ctx, cancel := context.WithTimeout(context.Background(),
  413. 1*time.Millisecond)
  414. defer cancel()
  415. err = c.RunContext(ctx)
  416. require.Equal(t, context.DeadlineExceeded, err)
  417. }
  418. func TestCompiled_CustomObject(t *testing.T) {
  419. c := compile(t, `r := (t<130)`, M{"t": &customNumber{value: 123}})
  420. compiledRun(t, c)
  421. compiledGet(t, c, "r", true)
  422. c = compile(t, `r := (t>13)`, M{"t": &customNumber{value: 123}})
  423. compiledRun(t, c)
  424. compiledGet(t, c, "r", true)
  425. }
  426. // customNumber is a user defined object that can compare to tengo.Int
  427. // very shitty implementation, just to test that token.Less and token.Greater in BinaryOp works
  428. type customNumber struct {
  429. tengo.ObjectImpl
  430. value int64
  431. }
  432. func (n *customNumber) TypeName() string {
  433. return "Number"
  434. }
  435. func (n *customNumber) String() string {
  436. return strconv.FormatInt(n.value, 10)
  437. }
  438. func (n *customNumber) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) {
  439. tengoInt, ok := rhs.(*tengo.Int)
  440. if !ok {
  441. return nil, tengo.ErrInvalidOperator
  442. }
  443. return n.binaryOpInt(op, tengoInt)
  444. }
  445. func (n *customNumber) binaryOpInt(op token.Token, rhs *tengo.Int) (tengo.Object, error) {
  446. i := n.value
  447. switch op {
  448. case token.Less:
  449. if i < rhs.Value {
  450. return tengo.TrueValue, nil
  451. }
  452. return tengo.FalseValue, nil
  453. case token.Greater:
  454. if i > rhs.Value {
  455. return tengo.TrueValue, nil
  456. }
  457. return tengo.FalseValue, nil
  458. case token.LessEq:
  459. if i <= rhs.Value {
  460. return tengo.TrueValue, nil
  461. }
  462. return tengo.FalseValue, nil
  463. case token.GreaterEq:
  464. if i >= rhs.Value {
  465. return tengo.TrueValue, nil
  466. }
  467. return tengo.FalseValue, nil
  468. }
  469. return nil, tengo.ErrInvalidOperator
  470. }
  471. func TestScript_ImportError(t *testing.T) {
  472. m := `
  473. exp := import("expression")
  474. r := exp(ctx)
  475. `
  476. src := `
  477. export func(ctx) {
  478. closure := func() {
  479. if ctx.actiontimes < 0 { // an error is thrown here because actiontimes is undefined
  480. return true
  481. }
  482. return false
  483. }
  484. return closure()
  485. }`
  486. s := tengo.NewScript([]byte(m))
  487. mods := tengo.NewModuleMap()
  488. mods.AddSourceModule("expression", []byte(src))
  489. s.SetImports(mods)
  490. err := s.Add("ctx", map[string]interface{}{
  491. "ctx": 12,
  492. })
  493. require.NoError(t, err)
  494. _, err = s.Run()
  495. require.True(t, strings.Contains(err.Error(), "expression:4:6"))
  496. }
  497. func compile(t *testing.T, input string, vars M) *tengo.Compiled {
  498. s := tengo.NewScript([]byte(input))
  499. for vn, vv := range vars {
  500. err := s.Add(vn, vv)
  501. require.NoError(t, err)
  502. }
  503. c, err := s.Compile()
  504. require.NoError(t, err)
  505. require.NotNil(t, c)
  506. return c
  507. }
  508. func compileError(t *testing.T, input string, vars M) {
  509. s := tengo.NewScript([]byte(input))
  510. for vn, vv := range vars {
  511. err := s.Add(vn, vv)
  512. require.NoError(t, err)
  513. }
  514. _, err := s.Compile()
  515. require.Error(t, err)
  516. }
  517. func compiledRun(t *testing.T, c *tengo.Compiled) {
  518. err := c.Run()
  519. require.NoError(t, err)
  520. }
  521. func compiledGet(
  522. t *testing.T,
  523. c *tengo.Compiled,
  524. name string,
  525. expected interface{},
  526. ) {
  527. v := c.Get(name)
  528. require.NotNil(t, v)
  529. require.Equal(t, expected, v.Value())
  530. }
  531. func compiledGetAll(
  532. t *testing.T,
  533. c *tengo.Compiled,
  534. expected M,
  535. ) {
  536. vars := c.GetAll()
  537. require.Equal(t, len(expected), len(vars))
  538. for k, v := range expected {
  539. var found bool
  540. for _, e := range vars {
  541. if e.Name() == k {
  542. require.Equal(t, v, e.Value())
  543. found = true
  544. }
  545. }
  546. require.True(t, found, "variable '%s' not found", k)
  547. }
  548. }
  549. func compiledIsDefined(
  550. t *testing.T,
  551. c *tengo.Compiled,
  552. name string,
  553. expected bool,
  554. ) {
  555. require.Equal(t, expected, c.IsDefined(name))
  556. }
  557. func TestCompiled_Clone(t *testing.T) {
  558. script := tengo.NewScript([]byte(`
  559. count += 1
  560. data["b"] = 2
  561. `))
  562. err := script.Add("data", map[string]interface{}{"a": 1})
  563. require.NoError(t, err)
  564. err = script.Add("count", 1000)
  565. require.NoError(t, err)
  566. compiled, err := script.Compile()
  567. require.NoError(t, err)
  568. clone := compiled.Clone()
  569. err = clone.RunContext(context.Background())
  570. require.NoError(t, err)
  571. require.Equal(t, 1000, compiled.Get("count").Int())
  572. require.Equal(t, 1, len(compiled.Get("data").Map()))
  573. require.Equal(t, 1001, clone.Get("count").Int())
  574. require.Equal(t, 2, len(clone.Get("data").Map()))
  575. }