1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12use Twig\Environment;
13use Twig\Error\Error;
14use Twig\Error\RuntimeError;
15use Twig\Loader\ArrayLoader;
16use Twig\Loader\FilesystemLoader;
17use Twig\Source;
18
19class Twig_Tests_ErrorTest extends \PHPUnit\Framework\TestCase
20{
21    public function testErrorWithObjectFilename()
22    {
23        $error = new Error('foo');
24        $error->setSourceContext(new Source('', new \SplFileInfo(__FILE__)));
25
26        $this->assertContains('test'.DIRECTORY_SEPARATOR.'Twig'.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR.'ErrorTest.php', $error->getMessage());
27    }
28
29    public function testErrorWithArrayFilename()
30    {
31        $error = new Error('foo');
32        $error->setSourceContext(new Source('', ['foo' => 'bar']));
33
34        $this->assertEquals('foo in {"foo":"bar"}', $error->getMessage());
35    }
36
37    public function testTwigExceptionGuessWithMissingVarAndArrayLoader()
38    {
39        $loader = new ArrayLoader([
40            'base.html' => '{% block content %}{% endblock %}',
41            'index.html' => <<<EOHTML
42{% extends 'base.html' %}
43{% block content %}
44    {{ foo.bar }}
45{% endblock %}
46{% block foo %}
47    {{ foo.bar }}
48{% endblock %}
49EOHTML
50        ]);
51        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
52
53        $template = $twig->load('index.html');
54        try {
55            $template->render([]);
56
57            $this->fail();
58        } catch (RuntimeError $e) {
59            $this->assertEquals('Variable "foo" does not exist in "index.html" at line 3.', $e->getMessage());
60            $this->assertEquals(3, $e->getTemplateLine());
61            $this->assertEquals('index.html', $e->getSourceContext()->getName());
62        }
63    }
64
65    public function testTwigExceptionGuessWithExceptionAndArrayLoader()
66    {
67        $loader = new ArrayLoader([
68            'base.html' => '{% block content %}{% endblock %}',
69            'index.html' => <<<EOHTML
70{% extends 'base.html' %}
71{% block content %}
72    {{ foo.bar }}
73{% endblock %}
74{% block foo %}
75    {{ foo.bar }}
76{% endblock %}
77EOHTML
78        ]);
79        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
80
81        $template = $twig->load('index.html');
82        try {
83            $template->render(['foo' => new Twig_Tests_ErrorTest_Foo()]);
84
85            $this->fail();
86        } catch (RuntimeError $e) {
87            $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index.html" at line 3.', $e->getMessage());
88            $this->assertEquals(3, $e->getTemplateLine());
89            $this->assertEquals('index.html', $e->getSourceContext()->getName());
90        }
91    }
92
93    public function testTwigExceptionGuessWithMissingVarAndFilesystemLoader()
94    {
95        $loader = new FilesystemLoader(__DIR__.'/Fixtures/errors');
96        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
97
98        $template = $twig->load('index.html');
99        try {
100            $template->render([]);
101
102            $this->fail();
103        } catch (RuntimeError $e) {
104            $this->assertEquals('Variable "foo" does not exist.', $e->getMessage());
105            $this->assertEquals(3, $e->getTemplateLine());
106            $this->assertEquals('index.html', $e->getSourceContext()->getName());
107            $this->assertEquals(3, $e->getLine());
108            $this->assertEquals(strtr(__DIR__.'/Fixtures/errors/index.html', '/', DIRECTORY_SEPARATOR), $e->getFile());
109        }
110    }
111
112    public function testTwigExceptionGuessWithExceptionAndFilesystemLoader()
113    {
114        $loader = new FilesystemLoader(__DIR__.'/Fixtures/errors');
115        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
116
117        $template = $twig->load('index.html');
118        try {
119            $template->render(['foo' => new Twig_Tests_ErrorTest_Foo()]);
120
121            $this->fail();
122        } catch (RuntimeError $e) {
123            $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...").', $e->getMessage());
124            $this->assertEquals(3, $e->getTemplateLine());
125            $this->assertEquals('index.html', $e->getSourceContext()->getName());
126            $this->assertEquals(3, $e->getLine());
127            $this->assertEquals(strtr(__DIR__.'/Fixtures/errors/index.html', '/', DIRECTORY_SEPARATOR), $e->getFile());
128        }
129    }
130
131    /**
132     * @dataProvider getErroredTemplates
133     */
134    public function testTwigExceptionAddsFileAndLine($templates, $name, $line)
135    {
136        $loader = new ArrayLoader($templates);
137        $twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
138
139        $template = $twig->load('index');
140
141        try {
142            $template->render([]);
143
144            $this->fail();
145        } catch (RuntimeError $e) {
146            $this->assertEquals(sprintf('Variable "foo" does not exist in "%s" at line %d.', $name, $line), $e->getMessage());
147            $this->assertEquals($line, $e->getTemplateLine());
148            $this->assertEquals($name, $e->getSourceContext()->getName());
149        }
150
151        try {
152            $template->render(['foo' => new Twig_Tests_ErrorTest_Foo()]);
153
154            $this->fail();
155        } catch (RuntimeError $e) {
156            $this->assertEquals(sprintf('An exception has been thrown during the rendering of a template ("Runtime error...") in "%s" at line %d.', $name, $line), $e->getMessage());
157            $this->assertEquals($line, $e->getTemplateLine());
158            $this->assertEquals($name, $e->getSourceContext()->getName());
159        }
160    }
161
162    public function getErroredTemplates()
163    {
164        return [
165            // error occurs in a template
166            [
167                [
168                    'index' => "\n\n{{ foo.bar }}\n\n\n{{ 'foo' }}",
169                ],
170                'index', 3,
171            ],
172
173            // error occurs in an included template
174            [
175                [
176                    'index' => "{% include 'partial' %}",
177                    'partial' => '{{ foo.bar }}',
178                ],
179                'partial', 1,
180            ],
181
182            // error occurs in a parent block when called via parent()
183            [
184                [
185                    'index' => "{% extends 'base' %}
186                    {% block content %}
187                        {{ parent() }}
188                    {% endblock %}",
189                    'base' => '{% block content %}{{ foo.bar }}{% endblock %}',
190                ],
191                'base', 1,
192            ],
193
194            // error occurs in a block from the child
195            [
196                [
197                    'index' => "{% extends 'base' %}
198                    {% block content %}
199                        {{ foo.bar }}
200                    {% endblock %}
201                    {% block foo %}
202                        {{ foo.bar }}
203                    {% endblock %}",
204                    'base' => '{% block content %}{% endblock %}',
205                ],
206                'index', 3,
207            ],
208        ];
209    }
210}
211
212class Twig_Tests_ErrorTest_Foo
213{
214    public function bar()
215    {
216        throw new \Exception('Runtime error...');
217    }
218}
219