script_test.go 12 KB

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