integrationTest.php 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. // This function is defined to check that escaping strategies
  11. // like html works even if a function with the same name is defined.
  12. function html()
  13. {
  14. return 'foo';
  15. }
  16. class Twig_Tests_IntegrationTest extends PHPUnit_Framework_TestCase
  17. {
  18. public function getTests()
  19. {
  20. $fixturesDir = realpath(dirname(__FILE__).'/Fixtures/');
  21. $tests = array();
  22. foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
  23. if (!preg_match('/\.test$/', $file)) {
  24. continue;
  25. }
  26. $test = file_get_contents($file->getRealpath());
  27. if (preg_match('/
  28. --TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) {
  29. $message = $match[1];
  30. $condition = $match[2];
  31. $templates = $this->parseTemplates($match[3]);
  32. $exception = $match[5];
  33. $outputs = array(array(null, $match[4], null, ''));
  34. } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) {
  35. $message = $match[1];
  36. $condition = $match[2];
  37. $templates = $this->parseTemplates($match[3]);
  38. $exception = false;
  39. preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER);
  40. } else {
  41. throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file)));
  42. }
  43. $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs);
  44. }
  45. return $tests;
  46. }
  47. /**
  48. * @dataProvider getTests
  49. */
  50. public function testIntegration($file, $message, $condition, $templates, $exception, $outputs)
  51. {
  52. if ($condition) {
  53. eval('$ret = '.$condition.';');
  54. if (!$ret) {
  55. $this->markTestSkipped($condition);
  56. }
  57. }
  58. $loader = new Twig_Loader_Array($templates);
  59. foreach ($outputs as $match) {
  60. $config = array_merge(array(
  61. 'cache' => false,
  62. 'strict_variables' => true,
  63. ), $match[2] ? eval($match[2].';') : array());
  64. $twig = new Twig_Environment($loader, $config);
  65. $twig->addExtension(new TestExtension());
  66. $twig->addExtension(new Twig_Extension_Debug());
  67. $policy = new Twig_Sandbox_SecurityPolicy(array(), array(), array(), array(), array());
  68. $twig->addExtension(new Twig_Extension_Sandbox($policy, false));
  69. $twig->addGlobal('global', 'global');
  70. try {
  71. $template = $twig->loadTemplate('index.twig');
  72. } catch (Exception $e) {
  73. if (false !== $exception) {
  74. $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage())));
  75. return;
  76. }
  77. if ($e instanceof Twig_Error_Syntax) {
  78. $e->setTemplateFile($file);
  79. throw $e;
  80. }
  81. throw new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e);
  82. }
  83. try {
  84. $output = trim($template->render(eval($match[1].';')), "\n ");
  85. } catch (Exception $e) {
  86. if (false !== $exception) {
  87. $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage())));
  88. return;
  89. }
  90. if ($e instanceof Twig_Error_Syntax) {
  91. $e->setTemplateFile($file);
  92. } else {
  93. $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e);
  94. }
  95. $output = trim(sprintf('%s: %s', get_class($e), $e->getMessage()));
  96. }
  97. if (false !== $exception) {
  98. list($class, ) = explode(':', $exception);
  99. $this->assertThat(NULL, new PHPUnit_Framework_Constraint_Exception($class));
  100. }
  101. $expected = trim($match[3], "\n ");
  102. if ($expected != $output) {
  103. echo 'Compiled template that failed:';
  104. foreach (array_keys($templates) as $name) {
  105. echo "Template: $name\n";
  106. $source = $loader->getSource($name);
  107. echo $twig->compile($twig->parse($twig->tokenize($source, $name)));
  108. }
  109. }
  110. $this->assertEquals($expected, $output, $message.' (in '.$file.')');
  111. }
  112. }
  113. protected function parseTemplates($test)
  114. {
  115. $templates = array();
  116. preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER);
  117. foreach ($matches as $match) {
  118. $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2];
  119. }
  120. return $templates;
  121. }
  122. }
  123. function test_foo($value = 'foo')
  124. {
  125. return $value;
  126. }
  127. class Foo implements Iterator
  128. {
  129. const BAR_NAME = 'bar';
  130. public $position = 0;
  131. public $array = array(1, 2);
  132. public function bar($param1 = null, $param2 = null)
  133. {
  134. return 'bar'.($param1 ? '_'.$param1 : '').($param2 ? '-'.$param2 : '');
  135. }
  136. public function getFoo()
  137. {
  138. return 'foo';
  139. }
  140. public function getSelf()
  141. {
  142. return $this;
  143. }
  144. public function is()
  145. {
  146. return 'is';
  147. }
  148. public function in()
  149. {
  150. return 'in';
  151. }
  152. public function not()
  153. {
  154. return 'not';
  155. }
  156. public function strToLower($value)
  157. {
  158. return strtolower($value);
  159. }
  160. public function rewind()
  161. {
  162. $this->position = 0;
  163. }
  164. public function current()
  165. {
  166. return $this->array[$this->position];
  167. }
  168. public function key()
  169. {
  170. return 'a';
  171. }
  172. public function next()
  173. {
  174. ++$this->position;
  175. }
  176. public function valid()
  177. {
  178. return isset($this->array[$this->position]);
  179. }
  180. }
  181. class TestTokenParser_☃ extends Twig_TokenParser
  182. {
  183. public function parse(Twig_Token $token)
  184. {
  185. $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
  186. return new Twig_Node_Print(new Twig_Node_Expression_Constant('☃', -1), -1);
  187. }
  188. public function getTag()
  189. {
  190. return '☃';
  191. }
  192. }
  193. class TestExtension extends Twig_Extension
  194. {
  195. public function getTokenParsers()
  196. {
  197. return array(
  198. new TestTokenParser_☃(),
  199. );
  200. }
  201. public function getFilters()
  202. {
  203. return array(
  204. '☃' => new Twig_Filter_Method($this, '☃Filter'),
  205. 'escape_and_nl2br' => new Twig_Filter_Method($this, 'escape_and_nl2br', array('needs_environment' => true, 'is_safe' => array('html'))),
  206. 'nl2br' => new Twig_Filter_Method($this, 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
  207. 'escape_something' => new Twig_Filter_Method($this, 'escape_something', array('is_safe' => array('something'))),
  208. 'preserves_safety' => new Twig_Filter_Method($this, 'preserves_safety', array('preserves_safety' => array('html'))),
  209. '*_path' => new Twig_Filter_Method($this, 'dynamic_path'),
  210. '*_foo_*_bar' => new Twig_Filter_Method($this, 'dynamic_foo'),
  211. );
  212. }
  213. public function getFunctions()
  214. {
  215. return array(
  216. '☃' => new Twig_Function_Method($this, '☃Function'),
  217. 'safe_br' => new Twig_Function_Method($this, 'br', array('is_safe' => array('html'))),
  218. 'unsafe_br' => new Twig_Function_Method($this, 'br'),
  219. '*_path' => new Twig_Function_Method($this, 'dynamic_path'),
  220. '*_foo_*_bar' => new Twig_Function_Method($this, 'dynamic_foo'),
  221. );
  222. }
  223. public function ☃Filter($value)
  224. {
  225. return "☃{$value}☃";
  226. }
  227. public function ☃Function($value)
  228. {
  229. return "☃{$value}☃";
  230. }
  231. /**
  232. * nl2br which also escapes, for testing escaper filters
  233. */
  234. public function escape_and_nl2br($env, $value, $sep = '<br />')
  235. {
  236. return $this->nl2br(twig_escape_filter($env, $value, 'html'), $sep);
  237. }
  238. /**
  239. * nl2br only, for testing filters with pre_escape
  240. */
  241. public function nl2br($value, $sep = '<br />')
  242. {
  243. // not secure if $value contains html tags (not only entities)
  244. // don't use
  245. return str_replace("\n", "$sep\n", $value);
  246. }
  247. public function dynamic_path($element, $item)
  248. {
  249. return $element.'/'.$item;
  250. }
  251. public function dynamic_foo($foo, $bar, $item)
  252. {
  253. return $foo.'/'.$bar.'/'.$item;
  254. }
  255. public function escape_something($value)
  256. {
  257. return strtoupper($value);
  258. }
  259. public function preserves_safety($value)
  260. {
  261. return strtoupper($value);
  262. }
  263. public function br()
  264. {
  265. return '<br />';
  266. }
  267. public function getName()
  268. {
  269. return 'test';
  270. }
  271. }