test-jinja.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659
  1. #include <string>
  2. #include <iostream>
  3. #include <random>
  4. #include <cstdlib>
  5. #include <nlohmann/json.hpp>
  6. #include "jinja/runtime.h"
  7. #include "jinja/parser.h"
  8. #include "jinja/lexer.h"
  9. #include "testing.h"
  10. using json = nlohmann::ordered_json;
  11. static void test_template(testing & t, const std::string & name, const std::string & tmpl, const json & vars, const std::string & expect);
  12. static void test_whitespace_control(testing & t);
  13. static void test_conditionals(testing & t);
  14. static void test_loops(testing & t);
  15. static void test_expressions(testing & t);
  16. static void test_set_statement(testing & t);
  17. static void test_filters(testing & t);
  18. static void test_literals(testing & t);
  19. static void test_comments(testing & t);
  20. static void test_macros(testing & t);
  21. static void test_namespace(testing & t);
  22. static void test_tests(testing & t);
  23. static void test_string_methods(testing & t);
  24. static void test_array_methods(testing & t);
  25. static void test_object_methods(testing & t);
  26. static void test_fuzzing(testing & t);
  27. int main(int argc, char *argv[]) {
  28. testing t(std::cout);
  29. t.verbose = true;
  30. if (argc >= 2) {
  31. t.set_filter(argv[1]);
  32. }
  33. t.test("whitespace control", test_whitespace_control);
  34. t.test("conditionals", test_conditionals);
  35. t.test("loops", test_loops);
  36. t.test("expressions", test_expressions);
  37. t.test("set statement", test_set_statement);
  38. t.test("filters", test_filters);
  39. t.test("literals", test_literals);
  40. t.test("comments", test_comments);
  41. t.test("macros", test_macros);
  42. t.test("namespace", test_namespace);
  43. t.test("tests", test_tests);
  44. t.test("string methods", test_string_methods);
  45. t.test("array methods", test_array_methods);
  46. t.test("object methods", test_object_methods);
  47. t.test("fuzzing", test_fuzzing);
  48. return t.summary();
  49. }
  50. static void test_whitespace_control(testing & t) {
  51. test_template(t, "trim_blocks removes newline after tag",
  52. "{% if true %}\n"
  53. "hello\n"
  54. "{% endif %}\n",
  55. json::object(),
  56. "hello\n"
  57. );
  58. test_template(t, "lstrip_blocks removes leading whitespace",
  59. " {% if true %}\n"
  60. " hello\n"
  61. " {% endif %}\n",
  62. json::object(),
  63. " hello\n"
  64. );
  65. test_template(t, "for loop with trim_blocks",
  66. "{% for i in items %}\n"
  67. "{{ i }}\n"
  68. "{% endfor %}\n",
  69. {{"items", json::array({1, 2, 3})}},
  70. "1\n2\n3\n"
  71. );
  72. test_template(t, "explicit strip both",
  73. " {%- if true -%} \n"
  74. "hello\n"
  75. " {%- endif -%} \n",
  76. json::object(),
  77. "hello"
  78. );
  79. test_template(t, "expression whitespace control",
  80. " {{- 'hello' -}} \n",
  81. json::object(),
  82. "hello"
  83. );
  84. test_template(t, "inline block no newline",
  85. "{% if true %}yes{% endif %}",
  86. json::object(),
  87. "yes"
  88. );
  89. }
  90. static void test_conditionals(testing & t) {
  91. test_template(t, "if true",
  92. "{% if cond %}yes{% endif %}",
  93. {{"cond", true}},
  94. "yes"
  95. );
  96. test_template(t, "if false",
  97. "{% if cond %}yes{% endif %}",
  98. {{"cond", false}},
  99. ""
  100. );
  101. test_template(t, "if else",
  102. "{% if cond %}yes{% else %}no{% endif %}",
  103. {{"cond", false}},
  104. "no"
  105. );
  106. test_template(t, "if elif else",
  107. "{% if a %}A{% elif b %}B{% else %}C{% endif %}",
  108. {{"a", false}, {"b", true}},
  109. "B"
  110. );
  111. test_template(t, "nested if",
  112. "{% if outer %}{% if inner %}both{% endif %}{% endif %}",
  113. {{"outer", true}, {"inner", true}},
  114. "both"
  115. );
  116. test_template(t, "comparison operators",
  117. "{% if x > 5 %}big{% endif %}",
  118. {{"x", 10}},
  119. "big"
  120. );
  121. test_template(t, "logical and",
  122. "{% if a and b %}both{% endif %}",
  123. {{"a", true}, {"b", true}},
  124. "both"
  125. );
  126. test_template(t, "logical or",
  127. "{% if a or b %}either{% endif %}",
  128. {{"a", false}, {"b", true}},
  129. "either"
  130. );
  131. test_template(t, "logical not",
  132. "{% if not a %}negated{% endif %}",
  133. {{"a", false}},
  134. "negated"
  135. );
  136. test_template(t, "in operator",
  137. "{% if 'x' in items %}found{% endif %}",
  138. {{"items", json::array({"x", "y"})}},
  139. "found"
  140. );
  141. test_template(t, "is defined",
  142. "{% if x is defined %}yes{% else %}no{% endif %}",
  143. {{"x", 1}},
  144. "yes"
  145. );
  146. test_template(t, "is not defined",
  147. "{% if y is not defined %}yes{% else %}no{% endif %}",
  148. json::object(),
  149. "yes"
  150. );
  151. }
  152. static void test_loops(testing & t) {
  153. test_template(t, "simple for",
  154. "{% for i in items %}{{ i }}{% endfor %}",
  155. {{"items", json::array({1, 2, 3})}},
  156. "123"
  157. );
  158. test_template(t, "loop.index",
  159. "{% for i in items %}{{ loop.index }}{% endfor %}",
  160. {{"items", json::array({"a", "b", "c"})}},
  161. "123"
  162. );
  163. test_template(t, "loop.index0",
  164. "{% for i in items %}{{ loop.index0 }}{% endfor %}",
  165. {{"items", json::array({"a", "b", "c"})}},
  166. "012"
  167. );
  168. test_template(t, "loop.first and loop.last",
  169. "{% for i in items %}{% if loop.first %}[{% endif %}{{ i }}{% if loop.last %}]{% endif %}{% endfor %}",
  170. {{"items", json::array({1, 2, 3})}},
  171. "[123]"
  172. );
  173. test_template(t, "loop.length",
  174. "{% for i in items %}{{ loop.length }}{% endfor %}",
  175. {{"items", json::array({"a", "b"})}},
  176. "22"
  177. );
  178. test_template(t, "for over dict items",
  179. "{% for k, v in data.items() %}{{ k }}={{ v }} {% endfor %}",
  180. {{"data", {{"x", 1}, {"y", 2}}}},
  181. "x=1 y=2 "
  182. );
  183. test_template(t, "for else empty",
  184. "{% for i in items %}{{ i }}{% else %}empty{% endfor %}",
  185. {{"items", json::array()}},
  186. "empty"
  187. );
  188. test_template(t, "nested for",
  189. "{% for i in a %}{% for j in b %}{{ i }}{{ j }}{% endfor %}{% endfor %}",
  190. {{"a", json::array({1, 2})}, {"b", json::array({"x", "y"})}},
  191. "1x1y2x2y"
  192. );
  193. test_template(t, "for with range",
  194. "{% for i in range(3) %}{{ i }}{% endfor %}",
  195. json::object(),
  196. "012"
  197. );
  198. }
  199. static void test_expressions(testing & t) {
  200. test_template(t, "simple variable",
  201. "{{ x }}",
  202. {{"x", 42}},
  203. "42"
  204. );
  205. test_template(t, "dot notation",
  206. "{{ user.name }}",
  207. {{"user", {{"name", "Bob"}}}},
  208. "Bob"
  209. );
  210. test_template(t, "negative float (not dot notation)",
  211. "{{ -1.0 }}",
  212. json::object(),
  213. "-1.0"
  214. );
  215. test_template(t, "bracket notation",
  216. "{{ user['name'] }}",
  217. {{"user", {{"name", "Bob"}}}},
  218. "Bob"
  219. );
  220. test_template(t, "array access",
  221. "{{ items[1] }}",
  222. {{"items", json::array({"a", "b", "c"})}},
  223. "b"
  224. );
  225. test_template(t, "arithmetic",
  226. "{{ (a + b) * c }}",
  227. {{"a", 2}, {"b", 3}, {"c", 4}},
  228. "20"
  229. );
  230. test_template(t, "string concat ~",
  231. "{{ 'hello' ~ ' ' ~ 'world' }}",
  232. json::object(),
  233. "hello world"
  234. );
  235. test_template(t, "ternary",
  236. "{{ 'yes' if cond else 'no' }}",
  237. {{"cond", true}},
  238. "yes"
  239. );
  240. }
  241. static void test_set_statement(testing & t) {
  242. test_template(t, "simple set",
  243. "{% set x = 5 %}{{ x }}",
  244. json::object(),
  245. "5"
  246. );
  247. test_template(t, "set with expression",
  248. "{% set x = a + b %}{{ x }}",
  249. {{"a", 10}, {"b", 20}},
  250. "30"
  251. );
  252. test_template(t, "set list",
  253. "{% set items = [1, 2, 3] %}{{ items|length }}",
  254. json::object(),
  255. "3"
  256. );
  257. test_template(t, "set dict",
  258. "{% set d = {'a': 1} %}{{ d.a }}",
  259. json::object(),
  260. "1"
  261. );
  262. }
  263. static void test_filters(testing & t) {
  264. test_template(t, "upper",
  265. "{{ 'hello'|upper }}",
  266. json::object(),
  267. "HELLO"
  268. );
  269. test_template(t, "lower",
  270. "{{ 'HELLO'|lower }}",
  271. json::object(),
  272. "hello"
  273. );
  274. test_template(t, "capitalize",
  275. "{{ 'heLlo World'|capitalize }}",
  276. json::object(),
  277. "Hello world"
  278. );
  279. test_template(t, "title",
  280. "{{ 'hello world'|title }}",
  281. json::object(),
  282. "Hello World"
  283. );
  284. test_template(t, "trim",
  285. "{{ ' \r\n\thello\t\n\r '|trim }}",
  286. json::object(),
  287. "hello"
  288. );
  289. test_template(t, "trim chars",
  290. "{{ 'xyxhelloxyx'|trim('xy') }}",
  291. json::object(),
  292. "hello"
  293. );
  294. test_template(t, "length string",
  295. "{{ 'hello'|length }}",
  296. json::object(),
  297. "5"
  298. );
  299. test_template(t, "replace",
  300. "{{ 'hello world'|replace('world', 'jinja') }}",
  301. json::object(),
  302. "hello jinja"
  303. );
  304. test_template(t, "length list",
  305. "{{ items|length }}",
  306. {{"items", json::array({1, 2, 3})}},
  307. "3"
  308. );
  309. test_template(t, "first",
  310. "{{ items|first }}",
  311. {{"items", json::array({10, 20, 30})}},
  312. "10"
  313. );
  314. test_template(t, "last",
  315. "{{ items|last }}",
  316. {{"items", json::array({10, 20, 30})}},
  317. "30"
  318. );
  319. test_template(t, "reverse",
  320. "{% for i in items|reverse %}{{ i }}{% endfor %}",
  321. {{"items", json::array({1, 2, 3})}},
  322. "321"
  323. );
  324. test_template(t, "sort",
  325. "{% for i in items|sort %}{{ i }}{% endfor %}",
  326. {{"items", json::array({3, 1, 2})}},
  327. "123"
  328. );
  329. test_template(t, "sort reverse",
  330. "{% for i in items|sort(true) %}{{ i }}{% endfor %}",
  331. {{"items", json::array({3, 1, 2})}},
  332. "321"
  333. );
  334. test_template(t, "sort with attribute",
  335. "{{ items|sort(attribute='name')|join(attribute='age') }}",
  336. {{"items", json::array({
  337. json({{"name", "c"}, {"age", 3}}),
  338. json({{"name", "a"}, {"age", 1}}),
  339. json({{"name", "b"}, {"age", 2}}),
  340. })}},
  341. "123"
  342. );
  343. test_template(t, "sort with numeric attribute",
  344. "{{ items|sort(attribute=0)|join(attribute=1) }}",
  345. {{"items", json::array({
  346. json::array({3, "z"}),
  347. json::array({1, "x"}),
  348. json::array({2, "y"}),
  349. })}},
  350. "xyz"
  351. );
  352. test_template(t, "join",
  353. "{{ items|join(', ') }}",
  354. {{"items", json::array({"a", "b", "c"})}},
  355. "a, b, c"
  356. );
  357. test_template(t, "join default separator",
  358. "{{ items|join }}",
  359. {{"items", json::array({"x", "y", "z"})}},
  360. "xyz"
  361. );
  362. test_template(t, "abs",
  363. "{{ -5|abs }}",
  364. json::object(),
  365. "5"
  366. );
  367. test_template(t, "int from string",
  368. "{{ '42'|int }}",
  369. json::object(),
  370. "42"
  371. );
  372. test_template(t, "int from string with default",
  373. "{{ ''|int(1) }}",
  374. json::object(),
  375. "1"
  376. );
  377. test_template(t, "int from string with base",
  378. "{{ '11'|int(base=2) }}",
  379. json::object(),
  380. "3"
  381. );
  382. test_template(t, "float from string",
  383. "{{ '3.14'|float }}",
  384. json::object(),
  385. "3.14"
  386. );
  387. test_template(t, "default with value",
  388. "{{ x|default('fallback') }}",
  389. {{"x", "actual"}},
  390. "actual"
  391. );
  392. test_template(t, "default without value",
  393. "{{ y|default('fallback') }}",
  394. json::object(),
  395. "fallback"
  396. );
  397. test_template(t, "default with falsy value",
  398. "{{ ''|default('fallback', true) }}",
  399. json::object(),
  400. "fallback"
  401. );
  402. test_template(t, "tojson ensure_ascii=true",
  403. "{{ data|tojson(ensure_ascii=true) }}",
  404. {{"data", "\u2713"}},
  405. "\"\\u2713\""
  406. );
  407. test_template(t, "tojson sort_keys=true",
  408. "{{ data|tojson(sort_keys=true) }}",
  409. {{"data", {{"b", 2}, {"a", 1}}}},
  410. "{\"a\": 1, \"b\": 2}"
  411. );
  412. test_template(t, "tojson",
  413. "{{ data|tojson }}",
  414. {{"data", {{"a", 1}, {"b", json::array({1, 2})}}}},
  415. "{\"a\": 1, \"b\": [1, 2]}"
  416. );
  417. test_template(t, "tojson indent=4",
  418. "{{ data|tojson(indent=4) }}",
  419. {{"data", {{"a", 1}, {"b", json::array({1, 2})}}}},
  420. "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}"
  421. );
  422. test_template(t, "tojson separators=(',',':')",
  423. "{{ data|tojson(separators=(',',':')) }}",
  424. {{"data", {{"a", 1}, {"b", json::array({1, 2})}}}},
  425. "{\"a\":1,\"b\":[1,2]}"
  426. );
  427. test_template(t, "tojson separators=(',',': ') indent=2",
  428. "{{ data|tojson(separators=(',',': '), indent=2) }}",
  429. {{"data", {{"a", 1}, {"b", json::array({1, 2})}}}},
  430. "{\n \"a\": 1,\n \"b\": [\n 1,\n 2\n ]\n}"
  431. );
  432. test_template(t, "chained filters",
  433. "{{ ' HELLO '|trim|lower }}",
  434. json::object(),
  435. "hello"
  436. );
  437. }
  438. static void test_literals(testing & t) {
  439. test_template(t, "integer",
  440. "{{ 42 }}",
  441. json::object(),
  442. "42"
  443. );
  444. test_template(t, "float",
  445. "{{ 3.14 }}",
  446. json::object(),
  447. "3.14"
  448. );
  449. test_template(t, "string",
  450. "{{ 'hello' }}",
  451. json::object(),
  452. "hello"
  453. );
  454. test_template(t, "boolean true",
  455. "{{ true }}",
  456. json::object(),
  457. "True"
  458. );
  459. test_template(t, "boolean false",
  460. "{{ false }}",
  461. json::object(),
  462. "False"
  463. );
  464. test_template(t, "none",
  465. "{% if x is none %}null{% endif %}",
  466. {{"x", nullptr}},
  467. "null"
  468. );
  469. test_template(t, "list literal",
  470. "{% for i in [1, 2, 3] %}{{ i }}{% endfor %}",
  471. json::object(),
  472. "123"
  473. );
  474. test_template(t, "dict literal",
  475. "{% set d = {'a': 1} %}{{ d.a }}",
  476. json::object(),
  477. "1"
  478. );
  479. test_template(t, "integer|abs",
  480. "{{ -42 | abs }}",
  481. json::object(),
  482. "42"
  483. );
  484. test_template(t, "integer|float",
  485. "{{ 42 | float }}",
  486. json::object(),
  487. "42.0"
  488. );
  489. test_template(t, "integer|tojson",
  490. "{{ 42 | tojson }}",
  491. json::object(),
  492. "42"
  493. );
  494. test_template(t, "float|abs",
  495. "{{ -3.14 | abs }}",
  496. json::object(),
  497. "3.14"
  498. );
  499. test_template(t, "float|int",
  500. "{{ 3.14 | int }}",
  501. json::object(),
  502. "3"
  503. );
  504. test_template(t, "float|tojson",
  505. "{{ 3.14 | tojson }}",
  506. json::object(),
  507. "3.14"
  508. );
  509. test_template(t, "string|tojson",
  510. "{{ 'hello' | tojson }}",
  511. json::object(),
  512. "\"hello\""
  513. );
  514. test_template(t, "boolean|int",
  515. "{{ true | int }}",
  516. json::object(),
  517. "1"
  518. );
  519. test_template(t, "boolean|float",
  520. "{{ true | float }}",
  521. json::object(),
  522. "1.0"
  523. );
  524. test_template(t, "boolean|tojson",
  525. "{{ true | tojson }}",
  526. json::object(),
  527. "true"
  528. );
  529. }
  530. static void test_comments(testing & t) {
  531. test_template(t, "inline comment",
  532. "before{# comment #}after",
  533. json::object(),
  534. "beforeafter"
  535. );
  536. test_template(t, "comment ignores code",
  537. "{% set x = 1 %}{# {% set x = 999 %} #}{{ x }}",
  538. json::object(),
  539. "1"
  540. );
  541. }
  542. static void test_macros(testing & t) {
  543. test_template(t, "simple macro",
  544. "{% macro greet(name) %}Hello {{ name }}{% endmacro %}{{ greet('World') }}",
  545. json::object(),
  546. "Hello World"
  547. );
  548. test_template(t, "macro default arg",
  549. "{% macro greet(name='Guest') %}Hi {{ name }}{% endmacro %}{{ greet() }}",
  550. json::object(),
  551. "Hi Guest"
  552. );
  553. }
  554. static void test_namespace(testing & t) {
  555. test_template(t, "namespace counter",
  556. "{% set ns = namespace(count=0) %}{% for i in range(3) %}{% set ns.count = ns.count + 1 %}{% endfor %}{{ ns.count }}",
  557. json::object(),
  558. "3"
  559. );
  560. }
  561. static void test_tests(testing & t) {
  562. test_template(t, "is odd",
  563. "{% if 3 is odd %}yes{% endif %}",
  564. json::object(),
  565. "yes"
  566. );
  567. test_template(t, "is even",
  568. "{% if 4 is even %}yes{% endif %}",
  569. json::object(),
  570. "yes"
  571. );
  572. test_template(t, "is false",
  573. "{{ 'yes' if x is false }}",
  574. {{"x", false}},
  575. "yes"
  576. );
  577. test_template(t, "is true",
  578. "{{ 'yes' if x is true }}",
  579. {{"x", true}},
  580. "yes"
  581. );
  582. test_template(t, "string is false",
  583. "{{ 'yes' if x is false else 'no' }}",
  584. {{"x", ""}},
  585. "no"
  586. );
  587. test_template(t, "is divisibleby",
  588. "{{ 'yes' if x is divisibleby(2) }}",
  589. {{"x", 2}},
  590. "yes"
  591. );
  592. test_template(t, "is eq",
  593. "{{ 'yes' if 3 is eq(3) }}",
  594. json::object(),
  595. "yes"
  596. );
  597. test_template(t, "is not equalto",
  598. "{{ 'yes' if 3 is not equalto(4) }}",
  599. json::object(),
  600. "yes"
  601. );
  602. test_template(t, "is ge",
  603. "{{ 'yes' if 3 is ge(3) }}",
  604. json::object(),
  605. "yes"
  606. );
  607. test_template(t, "is gt",
  608. "{{ 'yes' if 3 is gt(2) }}",
  609. json::object(),
  610. "yes"
  611. );
  612. test_template(t, "is greaterthan",
  613. "{{ 'yes' if 3 is greaterthan(2) }}",
  614. json::object(),
  615. "yes"
  616. );
  617. test_template(t, "is lt",
  618. "{{ 'yes' if 2 is lt(3) }}",
  619. json::object(),
  620. "yes"
  621. );
  622. test_template(t, "is lessthan",
  623. "{{ 'yes' if 2 is lessthan(3) }}",
  624. json::object(),
  625. "yes"
  626. );
  627. test_template(t, "is ne",
  628. "{{ 'yes' if 2 is ne(3) }}",
  629. json::object(),
  630. "yes"
  631. );
  632. test_template(t, "is lower",
  633. "{{ 'yes' if 'lowercase' is lower }}",
  634. json::object(),
  635. "yes"
  636. );
  637. test_template(t, "is upper",
  638. "{{ 'yes' if 'UPPERCASE' is upper }}",
  639. json::object(),
  640. "yes"
  641. );
  642. test_template(t, "is sameas",
  643. "{{ 'yes' if x is sameas(false) }}",
  644. {{"x", false}},
  645. "yes"
  646. );
  647. test_template(t, "is boolean",
  648. "{{ 'yes' if x is boolean }}",
  649. {{"x", true}},
  650. "yes"
  651. );
  652. test_template(t, "is callable",
  653. "{{ 'yes' if ''.strip is callable }}",
  654. json::object(),
  655. "yes"
  656. );
  657. test_template(t, "is escaped",
  658. "{{ 'yes' if 'foo'|safe is escaped }}",
  659. json::object(),
  660. "yes"
  661. );
  662. test_template(t, "is filter",
  663. "{{ 'yes' if 'trim' is filter }}",
  664. json::object(),
  665. "yes"
  666. );
  667. test_template(t, "is float",
  668. "{{ 'yes' if x is float }}",
  669. {{"x", 1.1}},
  670. "yes"
  671. );
  672. test_template(t, "is integer",
  673. "{{ 'yes' if x is integer }}",
  674. {{"x", 1}},
  675. "yes"
  676. );
  677. test_template(t, "is sequence",
  678. "{{ 'yes' if x is sequence }}",
  679. {{"x", json::array({1, 2, 3})}},
  680. "yes"
  681. );
  682. test_template(t, "is test",
  683. "{{ 'yes' if 'sequence' is test }}",
  684. json::object(),
  685. "yes"
  686. );
  687. test_template(t, "is undefined",
  688. "{{ 'yes' if x is undefined }}",
  689. json::object(),
  690. "yes"
  691. );
  692. test_template(t, "is none",
  693. "{% if x is none %}yes{% endif %}",
  694. {{"x", nullptr}},
  695. "yes"
  696. );
  697. test_template(t, "is string",
  698. "{% if x is string %}yes{% endif %}",
  699. {{"x", "hello"}},
  700. "yes"
  701. );
  702. test_template(t, "is number",
  703. "{% if x is number %}yes{% endif %}",
  704. {{"x", 42}},
  705. "yes"
  706. );
  707. test_template(t, "is iterable",
  708. "{% if x is iterable %}yes{% endif %}",
  709. {{"x", json::array({1, 2, 3})}},
  710. "yes"
  711. );
  712. test_template(t, "is mapping",
  713. "{% if x is mapping %}yes{% endif %}",
  714. {{"x", {{"a", 1}}}},
  715. "yes"
  716. );
  717. }
  718. static void test_string_methods(testing & t) {
  719. test_template(t, "string.upper()",
  720. "{{ s.upper() }}",
  721. {{"s", "hello"}},
  722. "HELLO"
  723. );
  724. test_template(t, "string.lower()",
  725. "{{ s.lower() }}",
  726. {{"s", "HELLO"}},
  727. "hello"
  728. );
  729. test_template(t, "string.strip()",
  730. "[{{ s.strip() }}]",
  731. {{"s", " hello "}},
  732. "[hello]"
  733. );
  734. test_template(t, "string.lstrip()",
  735. "[{{ s.lstrip() }}]",
  736. {{"s", " hello"}},
  737. "[hello]"
  738. );
  739. test_template(t, "string.rstrip()",
  740. "[{{ s.rstrip() }}]",
  741. {{"s", "hello "}},
  742. "[hello]"
  743. );
  744. test_template(t, "string.title()",
  745. "{{ s.title() }}",
  746. {{"s", "hello world"}},
  747. "Hello World"
  748. );
  749. test_template(t, "string.capitalize()",
  750. "{{ s.capitalize() }}",
  751. {{"s", "heLlo World"}},
  752. "Hello world"
  753. );
  754. test_template(t, "string.startswith() true",
  755. "{% if s.startswith('hel') %}yes{% endif %}",
  756. {{"s", "hello"}},
  757. "yes"
  758. );
  759. test_template(t, "string.startswith() false",
  760. "{% if s.startswith('xyz') %}yes{% else %}no{% endif %}",
  761. {{"s", "hello"}},
  762. "no"
  763. );
  764. test_template(t, "string.endswith() true",
  765. "{% if s.endswith('lo') %}yes{% endif %}",
  766. {{"s", "hello"}},
  767. "yes"
  768. );
  769. test_template(t, "string.endswith() false",
  770. "{% if s.endswith('xyz') %}yes{% else %}no{% endif %}",
  771. {{"s", "hello"}},
  772. "no"
  773. );
  774. test_template(t, "string.split() with sep",
  775. "{{ s.split(',')|join('-') }}",
  776. {{"s", "a,b,c"}},
  777. "a-b-c"
  778. );
  779. test_template(t, "string.split() with maxsplit",
  780. "{{ s.split(',', 1)|join('-') }}",
  781. {{"s", "a,b,c"}},
  782. "a-b,c"
  783. );
  784. test_template(t, "string.rsplit() with sep",
  785. "{{ s.rsplit(',')|join('-') }}",
  786. {{"s", "a,b,c"}},
  787. "a-b-c"
  788. );
  789. test_template(t, "string.rsplit() with maxsplit",
  790. "{{ s.rsplit(',', 1)|join('-') }}",
  791. {{"s", "a,b,c"}},
  792. "a,b-c"
  793. );
  794. test_template(t, "string.replace() basic",
  795. "{{ s.replace('world', 'jinja') }}",
  796. {{"s", "hello world"}},
  797. "hello jinja"
  798. );
  799. test_template(t, "string.replace() with count",
  800. "{{ s.replace('a', 'X', 2) }}",
  801. {{"s", "banana"}},
  802. "bXnXna"
  803. );
  804. }
  805. static void test_array_methods(testing & t) {
  806. test_template(t, "array|selectattr by attribute",
  807. "{% for item in items|selectattr('active') %}{{ item.name }} {% endfor %}",
  808. {{"items", json::array({
  809. {{"name", "a"}, {"active", true}},
  810. {{"name", "b"}, {"active", false}},
  811. {{"name", "c"}, {"active", true}}
  812. })}},
  813. "a c "
  814. );
  815. test_template(t, "array|selectattr with operator",
  816. "{% for item in items|selectattr('value', 'equalto', 5) %}{{ item.name }} {% endfor %}",
  817. {{"items", json::array({
  818. {{"name", "a"}, {"value", 3}},
  819. {{"name", "b"}, {"value", 5}},
  820. {{"name", "c"}, {"value", 5}}
  821. })}},
  822. "b c "
  823. );
  824. test_template(t, "array|tojson",
  825. "{{ arr|tojson }}",
  826. {{"arr", json::array({1, 2, 3})}},
  827. "[1, 2, 3]"
  828. );
  829. test_template(t, "array|tojson with strings",
  830. "{{ arr|tojson }}",
  831. {{"arr", json::array({"a", "b", "c"})}},
  832. "[\"a\", \"b\", \"c\"]"
  833. );
  834. test_template(t, "array|tojson nested",
  835. "{{ arr|tojson }}",
  836. {{"arr", json::array({json::array({1, 2}), json::array({3, 4})})}},
  837. "[[1, 2], [3, 4]]"
  838. );
  839. test_template(t, "array|last",
  840. "{{ arr|last }}",
  841. {{"arr", json::array({10, 20, 30})}},
  842. "30"
  843. );
  844. test_template(t, "array|last single element",
  845. "{{ arr|last }}",
  846. {{"arr", json::array({42})}},
  847. "42"
  848. );
  849. test_template(t, "array|join with separator",
  850. "{{ arr|join(', ') }}",
  851. {{"arr", json::array({"a", "b", "c"})}},
  852. "a, b, c"
  853. );
  854. test_template(t, "array|join with custom separator",
  855. "{{ arr|join(' | ') }}",
  856. {{"arr", json::array({1, 2, 3})}},
  857. "1 | 2 | 3"
  858. );
  859. test_template(t, "array|join default separator",
  860. "{{ arr|join }}",
  861. {{"arr", json::array({"x", "y", "z"})}},
  862. "xyz"
  863. );
  864. test_template(t, "array|join attribute",
  865. "{{ arr|join(attribute='age') }}",
  866. {{"arr", json::array({
  867. json({{"name", "a"}, {"age", 1}}),
  868. json({{"name", "b"}, {"age", 2}}),
  869. json({{"name", "c"}, {"age", 3}}),
  870. })}},
  871. "123"
  872. );
  873. test_template(t, "array|join numeric attribute",
  874. "{{ arr|join(attribute=-1) }}",
  875. {{"arr", json::array({json::array({1}), json::array({2}), json::array({3})})}},
  876. "123"
  877. );
  878. test_template(t, "array.pop() last",
  879. "{{ arr.pop() }}-{{ arr|join(',') }}",
  880. {{"arr", json::array({"a", "b", "c"})}},
  881. "c-a,b"
  882. );
  883. test_template(t, "array.pop() with index",
  884. "{{ arr.pop(0) }}-{{ arr|join(',') }}",
  885. {{"arr", json::array({"a", "b", "c"})}},
  886. "a-b,c"
  887. );
  888. test_template(t, "array.append()",
  889. "{% set _ = arr.append('d') %}{{ arr|join(',') }}",
  890. {{"arr", json::array({"a", "b", "c"})}},
  891. "a,b,c,d"
  892. );
  893. test_template(t, "array|map with attribute",
  894. "{% for v in arr|map(attribute='age') %}{{ v }} {% endfor %}",
  895. {{"arr", json::array({
  896. json({{"name", "a"}, {"age", 1}}),
  897. json({{"name", "b"}, {"age", 2}}),
  898. json({{"name", "c"}, {"age", 3}}),
  899. })}},
  900. "1 2 3 "
  901. );
  902. test_template(t, "array|map with attribute default",
  903. "{% for v in arr|map(attribute='age', default=3) %}{{ v }} {% endfor %}",
  904. {{"arr", json::array({
  905. json({{"name", "a"}, {"age", 1}}),
  906. json({{"name", "b"}, {"age", 2}}),
  907. json({{"name", "c"}}),
  908. })}},
  909. "1 2 3 "
  910. );
  911. test_template(t, "array|map without attribute default",
  912. "{% for v in arr|map(attribute='age') %}{{ v }} {% endfor %}",
  913. {{"arr", json::array({
  914. json({{"name", "a"}, {"age", 1}}),
  915. json({{"name", "b"}, {"age", 2}}),
  916. json({{"name", "c"}}),
  917. })}},
  918. "1 2 "
  919. );
  920. test_template(t, "array|map with numeric attribute",
  921. "{% for v in arr|map(attribute=0) %}{{ v }} {% endfor %}",
  922. {{"arr", json::array({
  923. json::array({10, "x"}),
  924. json::array({20, "y"}),
  925. json::array({30, "z"}),
  926. })}},
  927. "10 20 30 "
  928. );
  929. test_template(t, "array|map with negative attribute",
  930. "{% for v in arr|map(attribute=-1) %}{{ v }} {% endfor %}",
  931. {{"arr", json::array({
  932. json::array({10, "x"}),
  933. json::array({20, "y"}),
  934. json::array({30, "z"}),
  935. })}},
  936. "x y z "
  937. );
  938. test_template(t, "array|map with filter",
  939. "{{ arr|map('int')|sum }}",
  940. {{"arr", json::array({"1", "2", "3"})}},
  941. "6"
  942. );
  943. // not used by any chat templates
  944. // test_template(t, "array.insert()",
  945. // "{% set _ = arr.insert(1, 'x') %}{{ arr|join(',') }}",
  946. // {{"arr", json::array({"a", "b", "c"})}},
  947. // "a,x,b,c"
  948. // );
  949. }
  950. static void test_object_methods(testing & t) {
  951. test_template(t, "object.get() existing key",
  952. "{{ obj.get('a') }}",
  953. {{"obj", {{"a", 1}, {"b", 2}}}},
  954. "1"
  955. );
  956. test_template(t, "object.get() missing key",
  957. "[{{ obj.get('c') is none }}]",
  958. {{"obj", {{"a", 1}}}},
  959. "[True]"
  960. );
  961. test_template(t, "object.get() missing key with default",
  962. "{{ obj.get('c', 'default') }}",
  963. {{"obj", {{"a", 1}}}},
  964. "default"
  965. );
  966. test_template(t, "object.items()",
  967. "{% for k, v in obj.items() %}{{ k }}={{ v }} {% endfor %}",
  968. {{"obj", {{"x", 1}, {"y", 2}}}},
  969. "x=1 y=2 "
  970. );
  971. test_template(t, "object.keys()",
  972. "{% for k in obj.keys() %}{{ k }} {% endfor %}",
  973. {{"obj", {{"a", 1}, {"b", 2}}}},
  974. "a b "
  975. );
  976. test_template(t, "object.values()",
  977. "{% for v in obj.values() %}{{ v }} {% endfor %}",
  978. {{"obj", {{"a", 1}, {"b", 2}}}},
  979. "1 2 "
  980. );
  981. test_template(t, "dictsort ascending by key",
  982. "{% for k, v in obj|dictsort %}{{ k }}={{ v }} {% endfor %}",
  983. {{"obj", {{"z", 2}, {"a", 3}, {"m", 1}}}},
  984. "a=3 m=1 z=2 "
  985. );
  986. test_template(t, "dictsort descending by key",
  987. "{% for k, v in obj|dictsort(reverse=true) %}{{ k }}={{ v }} {% endfor %}",
  988. {{"obj", {{"a", 1}, {"b", 2}, {"c", 3}}}},
  989. "c=3 b=2 a=1 "
  990. );
  991. test_template(t, "dictsort by value",
  992. "{% for k, v in obj|dictsort(by='value') %}{{ k }}={{ v }} {% endfor %}",
  993. {{"obj", {{"a", 3}, {"b", 1}, {"c", 2}}}},
  994. "b=1 c=2 a=3 "
  995. );
  996. test_template(t, "dictsort case sensitive",
  997. "{% for k, v in obj|dictsort(case_sensitive=true) %}{{ k }}={{ v }} {% endfor %}",
  998. {{"obj", {{"a", 1}, {"A", 1}, {"b", 2}, {"B", 2}, {"c", 3}}}},
  999. "A=1 B=2 a=1 b=2 c=3 "
  1000. );
  1001. test_template(t, "object|tojson",
  1002. "{{ obj|tojson }}",
  1003. {{"obj", {{"name", "test"}, {"value", 42}}}},
  1004. "{\"name\": \"test\", \"value\": 42}"
  1005. );
  1006. test_template(t, "nested object|tojson",
  1007. "{{ obj|tojson }}",
  1008. {{"obj", {{"outer", {{"inner", "value"}}}}}},
  1009. "{\"outer\": {\"inner\": \"value\"}}"
  1010. );
  1011. test_template(t, "array in object|tojson",
  1012. "{{ obj|tojson }}",
  1013. {{"obj", {{"items", json::array({1, 2, 3})}}}},
  1014. "{\"items\": [1, 2, 3]}"
  1015. );
  1016. test_template(t, "object attribute and key access",
  1017. "{{ obj.keys()|join(',') }} vs {{ obj['keys'] }} vs {{ obj.test }}",
  1018. {{"obj", {{"keys", "value"}, {"test", "attr_value"}}}},
  1019. "keys,test vs value vs attr_value"
  1020. );
  1021. test_template(t, "env should not have object methods",
  1022. "{{ keys is undefined }} {{ obj.keys is defined }}",
  1023. {{"obj", {{"a", "b"}}}},
  1024. "True True"
  1025. );
  1026. }
  1027. static void test_template(testing & t, const std::string & name, const std::string & tmpl, const json & vars, const std::string & expect) {
  1028. t.test(name, [&tmpl, &vars, &expect](testing & t) {
  1029. jinja::lexer lexer;
  1030. auto lexer_res = lexer.tokenize(tmpl);
  1031. jinja::program ast = jinja::parse_from_tokens(lexer_res);
  1032. jinja::context ctx(tmpl);
  1033. jinja::global_from_json(ctx, vars, true);
  1034. jinja::runtime runtime(ctx);
  1035. try {
  1036. const jinja::value results = runtime.execute(ast);
  1037. auto parts = runtime.gather_string_parts(results);
  1038. std::string rendered;
  1039. for (const auto & part : parts->as_string().parts) {
  1040. rendered += part.val;
  1041. }
  1042. if (!t.assert_true("Template render mismatch", expect == rendered)) {
  1043. t.log("Template: " + json(tmpl).dump());
  1044. t.log("Expected: " + json(expect).dump());
  1045. t.log("Actual : " + json(rendered).dump());
  1046. }
  1047. } catch (const jinja::not_implemented_exception & e) {
  1048. // TODO @ngxson : remove this when the test framework supports skipping tests
  1049. t.log("Skipped: " + std::string(e.what()));
  1050. }
  1051. });
  1052. }
  1053. //
  1054. // fuzz tests to ensure no crashes occur on malformed inputs
  1055. //
  1056. constexpr int JINJA_FUZZ_ITERATIONS = 100;
  1057. // Helper to generate random string
  1058. static std::string random_string(std::mt19937 & rng, size_t max_len) {
  1059. static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
  1060. std::uniform_int_distribution<size_t> len_dist(0, max_len);
  1061. std::uniform_int_distribution<size_t> char_dist(0, sizeof(charset) - 2);
  1062. size_t len = len_dist(rng);
  1063. std::string result;
  1064. result.reserve(len);
  1065. for (size_t i = 0; i < len; ++i) {
  1066. result += charset[char_dist(rng)];
  1067. }
  1068. return result;
  1069. }
  1070. // Helper to execute a fuzz test case - returns true if no crash occurred
  1071. static bool fuzz_test_template(const std::string & tmpl, const json & vars) {
  1072. try {
  1073. // printf("Fuzz testing template: %s\n", tmpl.c_str());
  1074. jinja::lexer lexer;
  1075. auto lexer_res = lexer.tokenize(tmpl);
  1076. jinja::program ast = jinja::parse_from_tokens(lexer_res);
  1077. jinja::context ctx(tmpl);
  1078. jinja::global_from_json(ctx, vars, true);
  1079. jinja::runtime runtime(ctx);
  1080. const jinja::value results = runtime.execute(ast);
  1081. runtime.gather_string_parts(results);
  1082. return true; // success
  1083. } catch (const std::exception &) {
  1084. return true; // exception is acceptable, not a crash
  1085. } catch (...) {
  1086. return true; // any exception is acceptable, not a crash
  1087. }
  1088. }
  1089. static void test_fuzzing(testing & t) {
  1090. const int num_iterations = JINJA_FUZZ_ITERATIONS;
  1091. const unsigned int seed = 42; // fixed seed for reproducibility
  1092. std::mt19937 rng(seed);
  1093. // Distribution helpers
  1094. std::uniform_int_distribution<int> choice_dist(0, 100);
  1095. std::uniform_int_distribution<int> int_dist(-1000, 1000);
  1096. std::uniform_int_distribution<size_t> idx_dist(0, 1000);
  1097. // Template fragments for fuzzing
  1098. const std::vector<std::string> var_names = {
  1099. "x", "y", "z", "arr", "obj", "items", "foo", "bar", "undefined_var",
  1100. "none", "true", "false", "None", "True", "False"
  1101. };
  1102. const std::vector<std::string> filters = {
  1103. "length", "first", "last", "reverse", "sort", "unique", "join", "upper", "lower",
  1104. "trim", "default", "tojson", "string", "int", "float", "abs", "list", "dictsort"
  1105. };
  1106. const std::vector<std::string> builtins = {
  1107. "range", "len", "dict", "list", "join", "str", "int", "float", "namespace"
  1108. };
  1109. t.test("out of bound array access", [&](testing & t) {
  1110. for (int i = 0; i < num_iterations; ++i) {
  1111. int idx = int_dist(rng);
  1112. std::string tmpl = "{{ arr[" + std::to_string(idx) + "] }}";
  1113. json vars = {{"arr", json::array({1, 2, 3})}};
  1114. t.assert_true("should not crash", fuzz_test_template(tmpl, vars));
  1115. }
  1116. });
  1117. t.test("non-existing variables", [&](testing & t) {
  1118. for (int i = 0; i < num_iterations; ++i) {
  1119. std::string var = random_string(rng, 20);
  1120. std::string tmpl = "{{ " + var + " }}";
  1121. json vars = json::object(); // empty context
  1122. t.assert_true("should not crash", fuzz_test_template(tmpl, vars));
  1123. }
  1124. });
  1125. t.test("non-existing nested attributes", [&](testing & t) {
  1126. for (int i = 0; i < num_iterations; ++i) {
  1127. std::string var1 = var_names[choice_dist(rng) % var_names.size()];
  1128. std::string var2 = random_string(rng, 10);
  1129. std::string var3 = random_string(rng, 10);
  1130. std::string tmpl = "{{ " + var1 + "." + var2 + "." + var3 + " }}";
  1131. json vars = {{var1, {{"other", 123}}}};
  1132. t.assert_true("should not crash", fuzz_test_template(tmpl, vars));
  1133. }
  1134. });
  1135. t.test("invalid filter arguments", [&](testing & t) {
  1136. for (int i = 0; i < num_iterations; ++i) {
  1137. std::string filter = filters[choice_dist(rng) % filters.size()];
  1138. int val = int_dist(rng);
  1139. std::string tmpl = "{{ " + std::to_string(val) + " | " + filter + " }}";
  1140. json vars = json::object();
  1141. t.assert_true("should not crash", fuzz_test_template(tmpl, vars));
  1142. }
  1143. });
  1144. t.test("chained filters on various types", [&](testing & t) {
  1145. for (int i = 0; i < num_iterations; ++i) {
  1146. std::string f1 = filters[choice_dist(rng) % filters.size()];
  1147. std::string f2 = filters[choice_dist(rng) % filters.size()];
  1148. std::string var = var_names[choice_dist(rng) % var_names.size()];
  1149. std::string tmpl = "{{ " + var + " | " + f1 + " | " + f2 + " }}";
  1150. json vars = {
  1151. {"x", 42},
  1152. {"y", "hello"},
  1153. {"arr", json::array({1, 2, 3})},
  1154. {"obj", {{"a", 1}, {"b", 2}}},
  1155. {"items", json::array({"a", "b", "c"})}
  1156. };
  1157. t.assert_true("should not crash", fuzz_test_template(tmpl, vars));
  1158. }
  1159. });
  1160. t.test("invalid builtin calls", [&](testing & t) {
  1161. for (int i = 0; i < num_iterations; ++i) {
  1162. std::string builtin = builtins[choice_dist(rng) % builtins.size()];
  1163. std::string arg;
  1164. int arg_type = choice_dist(rng) % 4;
  1165. switch (arg_type) {
  1166. case 0: arg = "\"not a number\""; break;
  1167. case 1: arg = "none"; break;
  1168. case 2: arg = std::to_string(int_dist(rng)); break;
  1169. case 3: arg = "[]"; break;
  1170. }
  1171. std::string tmpl = "{{ " + builtin + "(" + arg + ") }}";
  1172. json vars = json::object();
  1173. t.assert_true("should not crash", fuzz_test_template(tmpl, vars));
  1174. }
  1175. });
  1176. t.test("macro edge cases", [&](testing & t) {
  1177. // Macro with no args called with args
  1178. t.assert_true("macro no args with args", fuzz_test_template(
  1179. "{% macro foo() %}hello{% endmacro %}{{ foo(1, 2, 3) }}",
  1180. json::object()
  1181. ));
  1182. // Macro with args called with no args
  1183. t.assert_true("macro with args no args", fuzz_test_template(
  1184. "{% macro foo(a, b, c) %}{{ a }}{{ b }}{{ c }}{% endmacro %}{{ foo() }}",
  1185. json::object()
  1186. ));
  1187. // Recursive macro reference
  1188. t.assert_true("recursive macro", fuzz_test_template(
  1189. "{% macro foo(n) %}{% if n > 0 %}{{ foo(n - 1) }}{% endif %}{% endmacro %}{{ foo(5) }}",
  1190. json::object()
  1191. ));
  1192. // Nested macro definitions
  1193. for (int i = 0; i < num_iterations / 10; ++i) {
  1194. std::string tmpl = "{% macro outer() %}{% macro inner() %}x{% endmacro %}{{ inner() }}{% endmacro %}{{ outer() }}";
  1195. t.assert_true("nested macro", fuzz_test_template(tmpl, json::object()));
  1196. }
  1197. });
  1198. t.test("empty and none operations", [&](testing & t) {
  1199. const std::vector<std::string> empty_tests = {
  1200. "{{ \"\" | first }}",
  1201. "{{ \"\" | last }}",
  1202. "{{ [] | first }}",
  1203. "{{ [] | last }}",
  1204. "{{ none.attr }}",
  1205. "{{ none | length }}",
  1206. "{{ none | default('fallback') }}",
  1207. "{{ {} | first }}",
  1208. "{{ {} | dictsort }}",
  1209. };
  1210. for (const auto & tmpl : empty_tests) {
  1211. t.assert_true("empty/none: " + tmpl, fuzz_test_template(tmpl, json::object()));
  1212. }
  1213. });
  1214. t.test("arithmetic edge cases", [&](testing & t) {
  1215. const std::vector<std::string> arith_tests = {
  1216. "{{ 1 / 0 }}",
  1217. "{{ 1 // 0 }}",
  1218. "{{ 1 % 0 }}",
  1219. "{{ 999999999999999999 * 999999999999999999 }}",
  1220. "{{ -999999999999999999 - 999999999999999999 }}",
  1221. "{{ 1.0 / 0.0 }}",
  1222. "{{ 0.0 / 0.0 }}",
  1223. };
  1224. for (const auto & tmpl : arith_tests) {
  1225. t.assert_true("arith: " + tmpl, fuzz_test_template(tmpl, json::object()));
  1226. }
  1227. });
  1228. t.test("deeply nested structures", [&](testing & t) {
  1229. // Deeply nested loops
  1230. for (int depth = 1; depth <= 10; ++depth) {
  1231. std::string tmpl;
  1232. for (int d = 0; d < depth; ++d) {
  1233. tmpl += "{% for i" + std::to_string(d) + " in arr %}";
  1234. }
  1235. tmpl += "x";
  1236. for (int d = 0; d < depth; ++d) {
  1237. tmpl += "{% endfor %}";
  1238. }
  1239. json vars = {{"arr", json::array({1, 2})}};
  1240. t.assert_true("nested loops depth " + std::to_string(depth), fuzz_test_template(tmpl, vars));
  1241. }
  1242. // Deeply nested conditionals
  1243. for (int depth = 1; depth <= 10; ++depth) {
  1244. std::string tmpl;
  1245. for (int d = 0; d < depth; ++d) {
  1246. tmpl += "{% if true %}";
  1247. }
  1248. tmpl += "x";
  1249. for (int d = 0; d < depth; ++d) {
  1250. tmpl += "{% endif %}";
  1251. }
  1252. t.assert_true("nested ifs depth " + std::to_string(depth), fuzz_test_template(tmpl, json::object()));
  1253. }
  1254. });
  1255. t.test("special characters in strings", [&](testing & t) {
  1256. const std::vector<std::string> special_tests = {
  1257. "{{ \"}{%\" }}",
  1258. "{{ \"}}{{\" }}",
  1259. "{{ \"{%%}\" }}",
  1260. "{{ \"\\n\\t\\r\" }}",
  1261. "{{ \"'\\\"'\" }}",
  1262. "{{ \"hello\\x00world\" }}",
  1263. };
  1264. for (const auto & tmpl : special_tests) {
  1265. t.assert_true("special: " + tmpl, fuzz_test_template(tmpl, json::object()));
  1266. }
  1267. });
  1268. t.test("random template generation", [&](testing & t) {
  1269. const std::vector<std::string> fragments = {
  1270. "{{ x }}", "{{ y }}", "{{ arr }}", "{{ obj }}",
  1271. "{% if true %}a{% endif %}",
  1272. "{% if false %}b{% else %}c{% endif %}",
  1273. "{% for i in arr %}{{ i }}{% endfor %}",
  1274. "{{ x | length }}", "{{ x | first }}", "{{ x | default(0) }}",
  1275. "{{ x + y }}", "{{ x - y }}", "{{ x * y }}",
  1276. "{{ x == y }}", "{{ x != y }}", "{{ x > y }}",
  1277. "{{ range(3) }}", "{{ \"hello\" | upper }}",
  1278. "text", " ", "\n",
  1279. };
  1280. for (int i = 0; i < num_iterations; ++i) {
  1281. std::string tmpl;
  1282. int num_frags = choice_dist(rng) % 10 + 1;
  1283. for (int f = 0; f < num_frags; ++f) {
  1284. tmpl += fragments[choice_dist(rng) % fragments.size()];
  1285. }
  1286. json vars = {
  1287. {"x", int_dist(rng)},
  1288. {"y", int_dist(rng)},
  1289. {"arr", json::array({1, 2, 3})},
  1290. {"obj", {{"a", 1}, {"b", 2}}}
  1291. };
  1292. t.assert_true("random template #" + std::to_string(i), fuzz_test_template(tmpl, vars));
  1293. }
  1294. });
  1295. t.test("malformed templates (should error, not crash)", [&](testing & t) {
  1296. const std::vector<std::string> malformed = {
  1297. "{{ x",
  1298. "{% if %}",
  1299. "{% for %}",
  1300. "{% for x in %}",
  1301. "{% endfor %}",
  1302. "{% endif %}",
  1303. "{{ | filter }}",
  1304. "{% if x %}", // unclosed
  1305. "{% for i in x %}", // unclosed
  1306. "{{ x | }}",
  1307. "{% macro %}{% endmacro %}",
  1308. "{{{{",
  1309. "}}}}",
  1310. "{%%}",
  1311. "{% set %}",
  1312. "{% set x %}",
  1313. };
  1314. for (const auto & tmpl : malformed) {
  1315. t.assert_true("malformed: " + tmpl, fuzz_test_template(tmpl, json::object()));
  1316. }
  1317. });
  1318. t.test("type coercion edge cases", [&](testing & t) {
  1319. for (int i = 0; i < num_iterations; ++i) {
  1320. int op_choice = choice_dist(rng) % 6;
  1321. std::string op;
  1322. switch (op_choice) {
  1323. case 0: op = "+"; break;
  1324. case 1: op = "-"; break;
  1325. case 2: op = "*"; break;
  1326. case 3: op = "/"; break;
  1327. case 4: op = "=="; break;
  1328. case 5: op = "~"; break; // string concat
  1329. }
  1330. std::string left_var = var_names[choice_dist(rng) % var_names.size()];
  1331. std::string right_var = var_names[choice_dist(rng) % var_names.size()];
  1332. std::string tmpl = "{{ " + left_var + " " + op + " " + right_var + " }}";
  1333. json vars = {
  1334. {"x", 42},
  1335. {"y", "hello"},
  1336. {"z", 3.14},
  1337. {"arr", json::array({1, 2, 3})},
  1338. {"obj", {{"a", 1}}},
  1339. {"items", json::array()},
  1340. {"foo", nullptr},
  1341. {"bar", true}
  1342. };
  1343. t.assert_true("type coercion: " + tmpl, fuzz_test_template(tmpl, vars));
  1344. }
  1345. });
  1346. t.test("fuzz builtin functions", [&](testing & t) {
  1347. // pair of (type_name, builtin_name)
  1348. std::vector<std::pair<std::string, std::string>> builtins;
  1349. auto add_fns = [&](std::string type_name, const jinja::func_builtins & added) {
  1350. for (const auto & it : added) {
  1351. builtins.push_back({type_name, it.first});
  1352. }
  1353. };
  1354. add_fns("global", jinja::global_builtins());
  1355. add_fns("int", jinja::value_int_t(0).get_builtins());
  1356. add_fns("float", jinja::value_float_t(0.0f).get_builtins());
  1357. add_fns("string", jinja::value_string_t().get_builtins());
  1358. add_fns("array", jinja::value_array_t().get_builtins());
  1359. add_fns("object", jinja::value_object_t().get_builtins());
  1360. const int max_args = 5;
  1361. const std::vector<std::string> kwarg_names = {
  1362. "base", "attribute", "default", "reverse", "case_sensitive", "by", "safe", "chars", "separators", "sort_keys", "indent", "ensure_ascii",
  1363. };
  1364. // Generate random argument values
  1365. auto gen_random_arg = [&]() -> std::string {
  1366. int type = choice_dist(rng) % 8;
  1367. switch (type) {
  1368. case 0: return std::to_string(int_dist(rng)); // int
  1369. case 1: return std::to_string(int_dist(rng)) + ".5"; // float
  1370. case 2: return "\"" + random_string(rng, 10) + "\""; // string
  1371. case 3: return "true"; // bool true
  1372. case 4: return "false"; // bool false
  1373. case 5: return "none"; // none
  1374. case 6: return "[1, 2, 3]"; // array
  1375. case 7: return "{\"a\": 1}"; // object
  1376. default: return "0";
  1377. }
  1378. };
  1379. for (int i = 0; i < num_iterations; ++i) {
  1380. // Pick a random builtin
  1381. auto & [type_name, fn_name] = builtins[choice_dist(rng) % builtins.size()];
  1382. // Generate random number of args
  1383. int num_args = choice_dist(rng) % (max_args + 1);
  1384. std::string args_str;
  1385. for (int a = 0; a < num_args; ++a) {
  1386. if (a > 0) args_str += ", ";
  1387. // Sometimes use keyword args
  1388. if (choice_dist(rng) % 3 == 0 && !kwarg_names.empty()) {
  1389. std::string kwarg = kwarg_names[choice_dist(rng) % kwarg_names.size()];
  1390. args_str += kwarg + "=" + gen_random_arg();
  1391. } else {
  1392. args_str += gen_random_arg();
  1393. }
  1394. }
  1395. std::string tmpl;
  1396. if (type_name == "global") {
  1397. // Global function call
  1398. tmpl = "{{ " + fn_name + "(" + args_str + ") }}";
  1399. } else {
  1400. // Method call on a value
  1401. std::string base_val;
  1402. if (type_name == "int") {
  1403. base_val = std::to_string(int_dist(rng));
  1404. } else if (type_name == "float") {
  1405. base_val = std::to_string(int_dist(rng)) + ".5";
  1406. } else if (type_name == "string") {
  1407. base_val = "\"test_string\"";
  1408. } else if (type_name == "array") {
  1409. base_val = "[1, 2, 3, \"a\", \"b\"]";
  1410. } else if (type_name == "object") {
  1411. base_val = "{\"x\": 1, \"y\": 2}";
  1412. } else {
  1413. base_val = "x";
  1414. }
  1415. tmpl = "{{ " + base_val + "." + fn_name + "(" + args_str + ") }}";
  1416. }
  1417. json vars = {
  1418. {"x", 42},
  1419. {"y", "hello"},
  1420. {"arr", json::array({1, 2, 3})},
  1421. {"obj", {{"a", 1}, {"b", 2}}}
  1422. };
  1423. t.assert_true("builtin " + type_name + "." + fn_name + " #" + std::to_string(i), fuzz_test_template(tmpl, vars));
  1424. }
  1425. });
  1426. }