test-jinja.cpp 53 KB

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