1 // Copyright 2012 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "utils/text/templates.hpp"
30 
31 #include <fstream>
32 #include <sstream>
33 
34 #include <atf-c++.hpp>
35 
36 #include "utils/fs/operations.hpp"
37 #include "utils/fs/path.hpp"
38 #include "utils/text/exceptions.hpp"
39 
40 namespace fs = utils::fs;
41 namespace text = utils::text;
42 
43 
44 namespace {
45 
46 
47 /// Applies a set of templates to an input string and validates the output.
48 ///
49 /// This fails the test case if exp_output does not match the document generated
50 /// by the application of the templates.
51 ///
52 /// \param templates The templates to apply.
53 /// \param input_str The input document to which to apply the templates.
54 /// \param exp_output The expected output document.
55 static void
do_test_ok(const text::templates_def & templates,const std::string & input_str,const std::string & exp_output)56 do_test_ok(const text::templates_def& templates, const std::string& input_str,
57            const std::string& exp_output)
58 {
59     std::istringstream input(input_str);
60     std::ostringstream output;
61 
62     text::instantiate(templates, input, output);
63     ATF_REQUIRE_EQ(exp_output, output.str());
64 }
65 
66 
67 /// Applies a set of templates to an input string and checks for an error.
68 ///
69 /// This fails the test case if the exception raised by the template processing
70 /// does not match the expected message.
71 ///
72 /// \param templates The templates to apply.
73 /// \param input_str The input document to which to apply the templates.
74 /// \param exp_message The expected error message in the raised exception.
75 static void
do_test_fail(const text::templates_def & templates,const std::string & input_str,const std::string & exp_message)76 do_test_fail(const text::templates_def& templates, const std::string& input_str,
77              const std::string& exp_message)
78 {
79     std::istringstream input(input_str);
80     std::ostringstream output;
81 
82     ATF_REQUIRE_THROW_RE(text::syntax_error, exp_message,
83                          text::instantiate(templates, input, output));
84 }
85 
86 
87 }  // anonymous namespace
88 
89 
90 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_variable__first);
ATF_TEST_CASE_BODY(templates_def__add_variable__first)91 ATF_TEST_CASE_BODY(templates_def__add_variable__first)
92 {
93     text::templates_def templates;
94     templates.add_variable("the-name", "first-value");
95     ATF_REQUIRE_EQ("first-value", templates.get_variable("the-name"));
96 }
97 
98 
99 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_variable__replace);
ATF_TEST_CASE_BODY(templates_def__add_variable__replace)100 ATF_TEST_CASE_BODY(templates_def__add_variable__replace)
101 {
102     text::templates_def templates;
103     templates.add_variable("the-name", "first-value");
104     templates.add_variable("the-name", "second-value");
105     ATF_REQUIRE_EQ("second-value", templates.get_variable("the-name"));
106 }
107 
108 
109 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__remove_variable);
ATF_TEST_CASE_BODY(templates_def__remove_variable)110 ATF_TEST_CASE_BODY(templates_def__remove_variable)
111 {
112     text::templates_def templates;
113     templates.add_variable("the-name", "the-value");
114     templates.get_variable("the-name");  // Should not throw.
115     templates.remove_variable("the-name");
116     ATF_REQUIRE_THROW(text::syntax_error, templates.get_variable("the-name"));
117 }
118 
119 
120 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_vector__first);
ATF_TEST_CASE_BODY(templates_def__add_vector__first)121 ATF_TEST_CASE_BODY(templates_def__add_vector__first)
122 {
123     text::templates_def templates;
124     templates.add_vector("the-name");
125     ATF_REQUIRE(templates.get_vector("the-name").empty());
126 }
127 
128 
129 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_vector__replace);
ATF_TEST_CASE_BODY(templates_def__add_vector__replace)130 ATF_TEST_CASE_BODY(templates_def__add_vector__replace)
131 {
132     text::templates_def templates;
133     templates.add_vector("the-name");
134     templates.add_to_vector("the-name", "foo");
135     ATF_REQUIRE(!templates.get_vector("the-name").empty());
136     templates.add_vector("the-name");
137     ATF_REQUIRE(templates.get_vector("the-name").empty());
138 }
139 
140 
141 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_to_vector);
ATF_TEST_CASE_BODY(templates_def__add_to_vector)142 ATF_TEST_CASE_BODY(templates_def__add_to_vector)
143 {
144     text::templates_def templates;
145     templates.add_vector("the-name");
146     ATF_REQUIRE_EQ(0, templates.get_vector("the-name").size());
147     templates.add_to_vector("the-name", "first");
148     ATF_REQUIRE_EQ(1, templates.get_vector("the-name").size());
149     templates.add_to_vector("the-name", "second");
150     ATF_REQUIRE_EQ(2, templates.get_vector("the-name").size());
151     templates.add_to_vector("the-name", "third");
152     ATF_REQUIRE_EQ(3, templates.get_vector("the-name").size());
153 
154     std::vector< std::string > expected;
155     expected.push_back("first");
156     expected.push_back("second");
157     expected.push_back("third");
158     ATF_REQUIRE(expected == templates.get_vector("the-name"));
159 }
160 
161 
162 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__exists__variable);
ATF_TEST_CASE_BODY(templates_def__exists__variable)163 ATF_TEST_CASE_BODY(templates_def__exists__variable)
164 {
165     text::templates_def templates;
166     ATF_REQUIRE(!templates.exists("some-name"));
167     templates.add_variable("some-name ", "foo");
168     ATF_REQUIRE(!templates.exists("some-name"));
169     templates.add_variable("some-name", "foo");
170     ATF_REQUIRE(templates.exists("some-name"));
171 }
172 
173 
174 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__exists__vector);
ATF_TEST_CASE_BODY(templates_def__exists__vector)175 ATF_TEST_CASE_BODY(templates_def__exists__vector)
176 {
177     text::templates_def templates;
178     ATF_REQUIRE(!templates.exists("some-name"));
179     templates.add_vector("some-name ");
180     ATF_REQUIRE(!templates.exists("some-name"));
181     templates.add_vector("some-name");
182     ATF_REQUIRE(templates.exists("some-name"));
183 }
184 
185 
186 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_variable__ok);
ATF_TEST_CASE_BODY(templates_def__get_variable__ok)187 ATF_TEST_CASE_BODY(templates_def__get_variable__ok)
188 {
189     text::templates_def templates;
190     templates.add_variable("foo", "");
191     templates.add_variable("bar", "    baz  ");
192     ATF_REQUIRE_EQ("", templates.get_variable("foo"));
193     ATF_REQUIRE_EQ("    baz  ", templates.get_variable("bar"));
194 }
195 
196 
197 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_variable__unknown);
ATF_TEST_CASE_BODY(templates_def__get_variable__unknown)198 ATF_TEST_CASE_BODY(templates_def__get_variable__unknown)
199 {
200     text::templates_def templates;
201     templates.add_variable("foo", "");
202     ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'foo '",
203                          templates.get_variable("foo "));
204 }
205 
206 
207 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_vector__ok);
ATF_TEST_CASE_BODY(templates_def__get_vector__ok)208 ATF_TEST_CASE_BODY(templates_def__get_vector__ok)
209 {
210     text::templates_def templates;
211     templates.add_vector("foo");
212     templates.add_vector("bar");
213     templates.add_to_vector("bar", "baz");
214     ATF_REQUIRE_EQ(0, templates.get_vector("foo").size());
215     ATF_REQUIRE_EQ(1, templates.get_vector("bar").size());
216 }
217 
218 
219 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_vector__unknown);
ATF_TEST_CASE_BODY(templates_def__get_vector__unknown)220 ATF_TEST_CASE_BODY(templates_def__get_vector__unknown)
221 {
222     text::templates_def templates;
223     templates.add_vector("foo");
224     ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'foo '",
225                          templates.get_vector("foo "));
226 }
227 
228 
229 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__variable__ok);
ATF_TEST_CASE_BODY(templates_def__evaluate__variable__ok)230 ATF_TEST_CASE_BODY(templates_def__evaluate__variable__ok)
231 {
232     text::templates_def templates;
233     templates.add_variable("foo", "");
234     templates.add_variable("bar", "    baz  ");
235     ATF_REQUIRE_EQ("", templates.evaluate("foo"));
236     ATF_REQUIRE_EQ("    baz  ", templates.evaluate("bar"));
237 }
238 
239 
240 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__variable__unknown);
ATF_TEST_CASE_BODY(templates_def__evaluate__variable__unknown)241 ATF_TEST_CASE_BODY(templates_def__evaluate__variable__unknown)
242 {
243     text::templates_def templates;
244     templates.add_variable("foo", "");
245     ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'foo1'",
246                          templates.evaluate("foo1"));
247 }
248 
249 
250 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__ok);
ATF_TEST_CASE_BODY(templates_def__evaluate__vector__ok)251 ATF_TEST_CASE_BODY(templates_def__evaluate__vector__ok)
252 {
253     text::templates_def templates;
254     templates.add_vector("v");
255     templates.add_to_vector("v", "foo");
256     templates.add_to_vector("v", "bar");
257     templates.add_to_vector("v", "baz");
258 
259     templates.add_variable("index", "0");
260     ATF_REQUIRE_EQ("foo", templates.evaluate("v(index)"));
261     templates.add_variable("index", "1");
262     ATF_REQUIRE_EQ("bar", templates.evaluate("v(index)"));
263     templates.add_variable("index", "2");
264     ATF_REQUIRE_EQ("baz", templates.evaluate("v(index)"));
265 }
266 
267 
268 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__unknown_vector);
ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_vector)269 ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_vector)
270 {
271     text::templates_def templates;
272     templates.add_vector("v");
273     templates.add_to_vector("v", "foo");
274     templates.add_variable("index", "0");
275     ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'fooz'",
276                          templates.evaluate("fooz(index)"));
277 }
278 
279 
280 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__unknown_index);
ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_index)281 ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_index)
282 {
283     text::templates_def templates;
284     templates.add_vector("v");
285     templates.add_to_vector("v", "foo");
286     templates.add_variable("index", "0");
287     ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'indexz'",
288                          templates.evaluate("v(indexz)"));
289 }
290 
291 
292 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__out_of_range);
ATF_TEST_CASE_BODY(templates_def__evaluate__vector__out_of_range)293 ATF_TEST_CASE_BODY(templates_def__evaluate__vector__out_of_range)
294 {
295     text::templates_def templates;
296     templates.add_vector("v");
297     templates.add_to_vector("v", "foo");
298     templates.add_variable("index", "1");
299     ATF_REQUIRE_THROW_RE(text::syntax_error, "Index 'index' out of range "
300                          "at position '1'", templates.evaluate("v(index)"));
301 }
302 
303 
304 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__defined);
ATF_TEST_CASE_BODY(templates_def__evaluate__defined)305 ATF_TEST_CASE_BODY(templates_def__evaluate__defined)
306 {
307     text::templates_def templates;
308     templates.add_vector("the-variable");
309     templates.add_vector("the-vector");
310     ATF_REQUIRE_EQ("false", templates.evaluate("defined(the-variabl)"));
311     ATF_REQUIRE_EQ("false", templates.evaluate("defined(the-vecto)"));
312     ATF_REQUIRE_EQ("true", templates.evaluate("defined(the-variable)"));
313     ATF_REQUIRE_EQ("true", templates.evaluate("defined(the-vector)"));
314 }
315 
316 
317 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__length__ok);
ATF_TEST_CASE_BODY(templates_def__evaluate__length__ok)318 ATF_TEST_CASE_BODY(templates_def__evaluate__length__ok)
319 {
320     text::templates_def templates;
321     templates.add_vector("v");
322     ATF_REQUIRE_EQ("0", templates.evaluate("length(v)"));
323     templates.add_to_vector("v", "foo");
324     ATF_REQUIRE_EQ("1", templates.evaluate("length(v)"));
325     templates.add_to_vector("v", "bar");
326     ATF_REQUIRE_EQ("2", templates.evaluate("length(v)"));
327     templates.add_to_vector("v", "baz");
328     ATF_REQUIRE_EQ("3", templates.evaluate("length(v)"));
329 }
330 
331 
332 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__length__unknown_vector);
ATF_TEST_CASE_BODY(templates_def__evaluate__length__unknown_vector)333 ATF_TEST_CASE_BODY(templates_def__evaluate__length__unknown_vector)
334 {
335     text::templates_def templates;
336     templates.add_vector("foo1");
337     ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'foo'",
338                          templates.evaluate("length(foo)"));
339 }
340 
341 
342 ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__parenthesis_error);
ATF_TEST_CASE_BODY(templates_def__evaluate__parenthesis_error)343 ATF_TEST_CASE_BODY(templates_def__evaluate__parenthesis_error)
344 {
345     text::templates_def templates;
346     ATF_REQUIRE_THROW_RE(text::syntax_error,
347                          "Expected '\\)' in.*'foo\\(abc'",
348                          templates.evaluate("foo(abc"));
349     ATF_REQUIRE_THROW_RE(text::syntax_error,
350                          "Unexpected text.*'\\)' in.*'a\\(b\\)c'",
351                          templates.evaluate("a(b)c"));
352 }
353 
354 
355 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__empty_input);
ATF_TEST_CASE_BODY(instantiate__empty_input)356 ATF_TEST_CASE_BODY(instantiate__empty_input)
357 {
358     const text::templates_def templates;
359     do_test_ok(templates, "", "");
360 }
361 
362 
363 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__value__ok);
ATF_TEST_CASE_BODY(instantiate__value__ok)364 ATF_TEST_CASE_BODY(instantiate__value__ok)
365 {
366     const std::string input =
367         "first line\n"
368         "%%testvar1%%\n"
369         "third line\n"
370         "%%testvar2%% %%testvar3%%%%testvar4%%\n"
371         "fifth line\n";
372 
373     const std::string exp_output =
374         "first line\n"
375         "second line\n"
376         "third line\n"
377         "fourth line.\n"
378         "fifth line\n";
379 
380     text::templates_def templates;
381     templates.add_variable("testvar1", "second line");
382     templates.add_variable("testvar2", "fourth");
383     templates.add_variable("testvar3", "line");
384     templates.add_variable("testvar4", ".");
385 
386     do_test_ok(templates, input, exp_output);
387 }
388 
389 
390 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__value__unknown_variable);
ATF_TEST_CASE_BODY(instantiate__value__unknown_variable)391 ATF_TEST_CASE_BODY(instantiate__value__unknown_variable)
392 {
393     const std::string input =
394         "%%testvar1%%\n";
395 
396     text::templates_def templates;
397     templates.add_variable("testvar2", "fourth line");
398 
399     do_test_fail(templates, input, "Unknown variable 'testvar1'");
400 }
401 
402 
403 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_length__ok);
ATF_TEST_CASE_BODY(instantiate__vector_length__ok)404 ATF_TEST_CASE_BODY(instantiate__vector_length__ok)
405 {
406     const std::string input =
407         "%%length(testvector1)%%\n"
408         "%%length(testvector2)%% - %%length(testvector3)%%\n";
409 
410     const std::string exp_output =
411         "4\n"
412         "0 - 1\n";
413 
414     text::templates_def templates;
415     templates.add_vector("testvector1");
416     templates.add_to_vector("testvector1", "000");
417     templates.add_to_vector("testvector1", "111");
418     templates.add_to_vector("testvector1", "543");
419     templates.add_to_vector("testvector1", "999");
420     templates.add_vector("testvector2");
421     templates.add_vector("testvector3");
422     templates.add_to_vector("testvector3", "123");
423 
424     do_test_ok(templates, input, exp_output);
425 }
426 
427 
428 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_length__unknown_vector);
ATF_TEST_CASE_BODY(instantiate__vector_length__unknown_vector)429 ATF_TEST_CASE_BODY(instantiate__vector_length__unknown_vector)
430 {
431     const std::string input =
432         "%%length(testvector)%%\n";
433 
434     text::templates_def templates;
435     templates.add_vector("testvector2");
436 
437     do_test_fail(templates, input, "Unknown vector 'testvector'");
438 }
439 
440 
441 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__ok);
ATF_TEST_CASE_BODY(instantiate__vector_value__ok)442 ATF_TEST_CASE_BODY(instantiate__vector_value__ok)
443 {
444     const std::string input =
445         "first line\n"
446         "%%testvector1(i)%%\n"
447         "third line\n"
448         "%%testvector2(j)%%\n"
449         "fifth line\n";
450 
451     const std::string exp_output =
452         "first line\n"
453         "543\n"
454         "third line\n"
455         "123\n"
456         "fifth line\n";
457 
458     text::templates_def templates;
459     templates.add_variable("i", "2");
460     templates.add_variable("j", "0");
461     templates.add_vector("testvector1");
462     templates.add_to_vector("testvector1", "000");
463     templates.add_to_vector("testvector1", "111");
464     templates.add_to_vector("testvector1", "543");
465     templates.add_to_vector("testvector1", "999");
466     templates.add_vector("testvector2");
467     templates.add_to_vector("testvector2", "123");
468 
469     do_test_ok(templates, input, exp_output);
470 }
471 
472 
473 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__unknown_vector);
ATF_TEST_CASE_BODY(instantiate__vector_value__unknown_vector)474 ATF_TEST_CASE_BODY(instantiate__vector_value__unknown_vector)
475 {
476     const std::string input =
477         "%%testvector(j)%%\n";
478 
479     text::templates_def templates;
480     templates.add_vector("testvector2");
481 
482     do_test_fail(templates, input, "Unknown vector 'testvector'");
483 }
484 
485 
486 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__out_of_range__empty);
ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__empty)487 ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__empty)
488 {
489     const std::string input =
490         "%%testvector(j)%%\n";
491 
492     text::templates_def templates;
493     templates.add_vector("testvector");
494     templates.add_variable("j", "0");
495 
496     do_test_fail(templates, input, "Index 'j' out of range at position '0'");
497 }
498 
499 
500 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__out_of_range__not_empty);
ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__not_empty)501 ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__not_empty)
502 {
503     const std::string input =
504         "%%testvector(j)%%\n";
505 
506     text::templates_def templates;
507     templates.add_vector("testvector");
508     templates.add_to_vector("testvector", "a");
509     templates.add_to_vector("testvector", "b");
510     templates.add_variable("j", "2");
511 
512     do_test_fail(templates, input, "Index 'j' out of range at position '2'");
513 }
514 
515 
516 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__one_level__taken);
ATF_TEST_CASE_BODY(instantiate__if__one_level__taken)517 ATF_TEST_CASE_BODY(instantiate__if__one_level__taken)
518 {
519     const std::string input =
520         "first line\n"
521         "%if defined(some_var)\n"
522         "hello from within the variable conditional\n"
523         "%endif\n"
524         "%if defined(some_vector)\n"
525         "hello from within the vector conditional\n"
526         "%else\n"
527         "bye from within the vector conditional\n"
528         "%endif\n"
529         "some more\n";
530 
531     const std::string exp_output =
532         "first line\n"
533         "hello from within the variable conditional\n"
534         "hello from within the vector conditional\n"
535         "some more\n";
536 
537     text::templates_def templates;
538     templates.add_variable("some_var", "zzz");
539     templates.add_vector("some_vector");
540 
541     do_test_ok(templates, input, exp_output);
542 }
543 
544 
545 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__one_level__not_taken);
ATF_TEST_CASE_BODY(instantiate__if__one_level__not_taken)546 ATF_TEST_CASE_BODY(instantiate__if__one_level__not_taken)
547 {
548     const std::string input =
549         "first line\n"
550         "%if defined(some_var)\n"
551         "hello from within the variable conditional\n"
552         "%endif\n"
553         "%if defined(some_vector)\n"
554         "hello from within the vector conditional\n"
555         "%else\n"
556         "bye from within the vector conditional\n"
557         "%endif\n"
558         "some more\n";
559 
560     const std::string exp_output =
561         "first line\n"
562         "bye from within the vector conditional\n"
563         "some more\n";
564 
565     text::templates_def templates;
566 
567     do_test_ok(templates, input, exp_output);
568 }
569 
570 
571 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__multiple_levels__taken);
ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__taken)572 ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__taken)
573 {
574     const std::string input =
575         "first line\n"
576         "%if defined(var1)\n"
577         "first before\n"
578         "%if length(var2)\n"
579         "second before\n"
580         "%if defined(var3)\n"
581         "third before\n"
582         "hello from within the conditional\n"
583         "third after\n"
584         "%endif\n"
585         "second after\n"
586         "%else\n"
587         "second after not shown\n"
588         "%endif\n"
589         "first after\n"
590         "%endif\n"
591         "some more\n";
592 
593     const std::string exp_output =
594         "first line\n"
595         "first before\n"
596         "second before\n"
597         "third before\n"
598         "hello from within the conditional\n"
599         "third after\n"
600         "second after\n"
601         "first after\n"
602         "some more\n";
603 
604     text::templates_def templates;
605     templates.add_variable("var1", "false");
606     templates.add_vector("var2");
607     templates.add_to_vector("var2", "not-empty");
608     templates.add_variable("var3", "foobar");
609 
610     do_test_ok(templates, input, exp_output);
611 }
612 
613 
614 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__multiple_levels__not_taken);
ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__not_taken)615 ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__not_taken)
616 {
617     const std::string input =
618         "first line\n"
619         "%if defined(var1)\n"
620         "first before\n"
621         "%if length(var2)\n"
622         "second before\n"
623         "%if defined(var3)\n"
624         "third before\n"
625         "hello from within the conditional\n"
626         "third after\n"
627         "%else\n"
628         "will not be shown either\n"
629         "%endif\n"
630         "second after\n"
631         "%else\n"
632         "second after shown\n"
633         "%endif\n"
634         "first after\n"
635         "%endif\n"
636         "some more\n";
637 
638     const std::string exp_output =
639         "first line\n"
640         "first before\n"
641         "second after shown\n"
642         "first after\n"
643         "some more\n";
644 
645     text::templates_def templates;
646     templates.add_variable("var1", "false");
647     templates.add_vector("var2");
648     templates.add_vector("var3");
649 
650     do_test_ok(templates, input, exp_output);
651 }
652 
653 
654 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__no_iterations);
ATF_TEST_CASE_BODY(instantiate__loop__no_iterations)655 ATF_TEST_CASE_BODY(instantiate__loop__no_iterations)
656 {
657     const std::string input =
658         "first line\n"
659         "%loop table1 i\n"
660         "hello\n"
661         "value in vector: %%table1(i)%%\n"
662         "%if defined(var1)\n" "some other text\n" "%endif\n"
663         "%endloop\n"
664         "some more\n";
665 
666     const std::string exp_output =
667         "first line\n"
668         "some more\n";
669 
670     text::templates_def templates;
671     templates.add_variable("var1", "defined");
672     templates.add_vector("table1");
673 
674     do_test_ok(templates, input, exp_output);
675 }
676 
677 
678 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__multiple_iterations);
ATF_TEST_CASE_BODY(instantiate__loop__multiple_iterations)679 ATF_TEST_CASE_BODY(instantiate__loop__multiple_iterations)
680 {
681     const std::string input =
682         "first line\n"
683         "%loop table1 i\n"
684         "hello %%table1(i)%% %%table2(i)%%\n"
685         "%endloop\n"
686         "some more\n";
687 
688     const std::string exp_output =
689         "first line\n"
690         "hello foo1 foo2\n"
691         "hello bar1 bar2\n"
692         "some more\n";
693 
694     text::templates_def templates;
695     templates.add_vector("table1");
696     templates.add_to_vector("table1", "foo1");
697     templates.add_to_vector("table1", "bar1");
698     templates.add_vector("table2");
699     templates.add_to_vector("table2", "foo2");
700     templates.add_to_vector("table2", "bar2");
701 
702     do_test_ok(templates, input, exp_output);
703 }
704 
705 
706 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__nested__no_iterations);
ATF_TEST_CASE_BODY(instantiate__loop__nested__no_iterations)707 ATF_TEST_CASE_BODY(instantiate__loop__nested__no_iterations)
708 {
709     const std::string input =
710         "first line\n"
711         "%loop table1 i\n"
712         "before: %%table1(i)%%\n"
713         "%loop table2 j\n"
714         "before: %%table2(j)%%\n"
715         "%loop table3 k\n"
716         "%%table3(k)%%\n"
717         "%endloop\n"
718         "after: %%table2(i)%%\n"
719         "%endloop\n"
720         "after: %%table1(i)%%\n"
721         "%endloop\n"
722         "some more\n";
723 
724     const std::string exp_output =
725         "first line\n"
726         "before: a\n"
727         "after: a\n"
728         "before: b\n"
729         "after: b\n"
730         "some more\n";
731 
732     text::templates_def templates;
733     templates.add_vector("table1");
734     templates.add_to_vector("table1", "a");
735     templates.add_to_vector("table1", "b");
736     templates.add_vector("table2");
737     templates.add_vector("table3");
738     templates.add_to_vector("table3", "1");
739 
740     do_test_ok(templates, input, exp_output);
741 }
742 
743 
744 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__nested__multiple_iterations);
ATF_TEST_CASE_BODY(instantiate__loop__nested__multiple_iterations)745 ATF_TEST_CASE_BODY(instantiate__loop__nested__multiple_iterations)
746 {
747     const std::string input =
748         "first line\n"
749         "%loop table1 i\n"
750         "%loop table2 j\n"
751         "%%table1(i)%% %%table2(j)%%\n"
752         "%endloop\n"
753         "%endloop\n"
754         "some more\n";
755 
756     const std::string exp_output =
757         "first line\n"
758         "a 1\n"
759         "a 2\n"
760         "a 3\n"
761         "b 1\n"
762         "b 2\n"
763         "b 3\n"
764         "some more\n";
765 
766     text::templates_def templates;
767     templates.add_vector("table1");
768     templates.add_to_vector("table1", "a");
769     templates.add_to_vector("table1", "b");
770     templates.add_vector("table2");
771     templates.add_to_vector("table2", "1");
772     templates.add_to_vector("table2", "2");
773     templates.add_to_vector("table2", "3");
774 
775     do_test_ok(templates, input, exp_output);
776 }
777 
778 
779 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__sequential);
ATF_TEST_CASE_BODY(instantiate__loop__sequential)780 ATF_TEST_CASE_BODY(instantiate__loop__sequential)
781 {
782     const std::string input =
783         "first line\n"
784         "%loop table1 iter\n"
785         "1: %%table1(iter)%%\n"
786         "%endloop\n"
787         "divider\n"
788         "%loop table2 iter\n"
789         "2: %%table2(iter)%%\n"
790         "%endloop\n"
791         "divider\n"
792         "%loop table3 iter\n"
793         "3: %%table3(iter)%%\n"
794         "%endloop\n"
795         "divider\n"
796         "%loop table4 iter\n"
797         "4: %%table4(iter)%%\n"
798         "%endloop\n"
799         "some more\n";
800 
801     const std::string exp_output =
802         "first line\n"
803         "1: a\n"
804         "1: b\n"
805         "divider\n"
806         "divider\n"
807         "divider\n"
808         "4: 1\n"
809         "4: 2\n"
810         "4: 3\n"
811         "some more\n";
812 
813     text::templates_def templates;
814     templates.add_vector("table1");
815     templates.add_to_vector("table1", "a");
816     templates.add_to_vector("table1", "b");
817     templates.add_vector("table2");
818     templates.add_vector("table3");
819     templates.add_vector("table4");
820     templates.add_to_vector("table4", "1");
821     templates.add_to_vector("table4", "2");
822     templates.add_to_vector("table4", "3");
823 
824     do_test_ok(templates, input, exp_output);
825 }
826 
827 
828 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__scoping);
ATF_TEST_CASE_BODY(instantiate__loop__scoping)829 ATF_TEST_CASE_BODY(instantiate__loop__scoping)
830 {
831     const std::string input =
832         "%loop table1 i\n"
833         "%if defined(i)\n" "i defined inside scope 1\n" "%endif\n"
834         "%loop table2 j\n"
835         "%if defined(i)\n" "i defined inside scope 2\n" "%endif\n"
836         "%if defined(j)\n" "j defined inside scope 2\n" "%endif\n"
837         "%endloop\n"
838         "%if defined(j)\n" "j defined inside scope 1\n" "%endif\n"
839         "%endloop\n"
840         "%if defined(i)\n" "i defined outside\n" "%endif\n"
841         "%if defined(j)\n" "j defined outside\n" "%endif\n";
842 
843     const std::string exp_output =
844         "i defined inside scope 1\n"
845         "i defined inside scope 2\n"
846         "j defined inside scope 2\n"
847         "i defined inside scope 1\n"
848         "i defined inside scope 2\n"
849         "j defined inside scope 2\n";
850 
851     text::templates_def templates;
852     templates.add_vector("table1");
853     templates.add_to_vector("table1", "first");
854     templates.add_to_vector("table1", "second");
855     templates.add_vector("table2");
856     templates.add_to_vector("table2", "first");
857 
858     do_test_ok(templates, input, exp_output);
859 }
860 
861 
862 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__mismatched_delimiters);
ATF_TEST_CASE_BODY(instantiate__mismatched_delimiters)863 ATF_TEST_CASE_BODY(instantiate__mismatched_delimiters)
864 {
865     const std::string input =
866         "this is some %% text\n"
867         "and this is %%var%% text%%\n";
868 
869     const std::string exp_output =
870         "this is some %% text\n"
871         "and this is some more text%%\n";
872 
873     text::templates_def templates;
874     templates.add_variable("var", "some more");
875 
876     do_test_ok(templates, input, exp_output);
877 }
878 
879 
880 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__empty_statement);
ATF_TEST_CASE_BODY(instantiate__empty_statement)881 ATF_TEST_CASE_BODY(instantiate__empty_statement)
882 {
883     do_test_fail(text::templates_def(), "%\n", "Empty statement");
884 }
885 
886 
887 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__unknown_statement);
ATF_TEST_CASE_BODY(instantiate__unknown_statement)888 ATF_TEST_CASE_BODY(instantiate__unknown_statement)
889 {
890     do_test_fail(text::templates_def(), "%if2\n", "Unknown statement 'if2'");
891 }
892 
893 
894 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__invalid_narguments);
ATF_TEST_CASE_BODY(instantiate__invalid_narguments)895 ATF_TEST_CASE_BODY(instantiate__invalid_narguments)
896 {
897     do_test_fail(text::templates_def(), "%if a b\n",
898                  "Invalid number of arguments for statement 'if'");
899 }
900 
901 
902 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__files__ok);
ATF_TEST_CASE_BODY(instantiate__files__ok)903 ATF_TEST_CASE_BODY(instantiate__files__ok)
904 {
905     text::templates_def templates;
906     templates.add_variable("string", "Hello, world!");
907 
908     atf::utils::create_file("input.txt", "The string is: %%string%%\n");
909 
910     text::instantiate(templates, fs::path("input.txt"), fs::path("output.txt"));
911 
912     std::ifstream output("output.txt");
913     std::string line;
914     ATF_REQUIRE(std::getline(output, line).good());
915     ATF_REQUIRE_EQ(line, "The string is: Hello, world!");
916     ATF_REQUIRE(std::getline(output, line).eof());
917 }
918 
919 
920 ATF_TEST_CASE_WITHOUT_HEAD(instantiate__files__input_error);
ATF_TEST_CASE_BODY(instantiate__files__input_error)921 ATF_TEST_CASE_BODY(instantiate__files__input_error)
922 {
923     text::templates_def templates;
924     ATF_REQUIRE_THROW_RE(text::error, "Failed to open input.txt for read",
925                          text::instantiate(templates, fs::path("input.txt"),
926                                            fs::path("output.txt")));
927 }
928 
929 
930 ATF_TEST_CASE(instantiate__files__output_error);
ATF_TEST_CASE_HEAD(instantiate__files__output_error)931 ATF_TEST_CASE_HEAD(instantiate__files__output_error)
932 {
933     set_md_var("require.user", "unprivileged");
934 }
ATF_TEST_CASE_BODY(instantiate__files__output_error)935 ATF_TEST_CASE_BODY(instantiate__files__output_error)
936 {
937     text::templates_def templates;
938 
939     atf::utils::create_file("input.txt", "");
940 
941     fs::mkdir(fs::path("dir"), 0444);
942 
943     ATF_REQUIRE_THROW_RE(text::error, "Failed to open dir/output.txt for write",
944                          text::instantiate(templates, fs::path("input.txt"),
945                                            fs::path("dir/output.txt")));
946 }
947 
948 
ATF_INIT_TEST_CASES(tcs)949 ATF_INIT_TEST_CASES(tcs)
950 {
951     ATF_ADD_TEST_CASE(tcs, templates_def__add_variable__first);
952     ATF_ADD_TEST_CASE(tcs, templates_def__add_variable__replace);
953     ATF_ADD_TEST_CASE(tcs, templates_def__remove_variable);
954     ATF_ADD_TEST_CASE(tcs, templates_def__add_vector__first);
955     ATF_ADD_TEST_CASE(tcs, templates_def__add_vector__replace);
956     ATF_ADD_TEST_CASE(tcs, templates_def__add_to_vector);
957     ATF_ADD_TEST_CASE(tcs, templates_def__exists__variable);
958     ATF_ADD_TEST_CASE(tcs, templates_def__exists__vector);
959     ATF_ADD_TEST_CASE(tcs, templates_def__get_variable__ok);
960     ATF_ADD_TEST_CASE(tcs, templates_def__get_variable__unknown);
961     ATF_ADD_TEST_CASE(tcs, templates_def__get_vector__ok);
962     ATF_ADD_TEST_CASE(tcs, templates_def__get_vector__unknown);
963     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__variable__ok);
964     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__variable__unknown);
965     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__ok);
966     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__unknown_vector);
967     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__unknown_index);
968     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__out_of_range);
969     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__defined);
970     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__length__ok);
971     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__length__unknown_vector);
972     ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__parenthesis_error);
973 
974     ATF_ADD_TEST_CASE(tcs, instantiate__empty_input);
975     ATF_ADD_TEST_CASE(tcs, instantiate__value__ok);
976     ATF_ADD_TEST_CASE(tcs, instantiate__value__unknown_variable);
977     ATF_ADD_TEST_CASE(tcs, instantiate__vector_length__ok);
978     ATF_ADD_TEST_CASE(tcs, instantiate__vector_length__unknown_vector);
979     ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__ok);
980     ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__unknown_vector);
981     ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__out_of_range__empty);
982     ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__out_of_range__not_empty);
983     ATF_ADD_TEST_CASE(tcs, instantiate__if__one_level__taken);
984     ATF_ADD_TEST_CASE(tcs, instantiate__if__one_level__not_taken);
985     ATF_ADD_TEST_CASE(tcs, instantiate__if__multiple_levels__taken);
986     ATF_ADD_TEST_CASE(tcs, instantiate__if__multiple_levels__not_taken);
987     ATF_ADD_TEST_CASE(tcs, instantiate__loop__no_iterations);
988     ATF_ADD_TEST_CASE(tcs, instantiate__loop__multiple_iterations);
989     ATF_ADD_TEST_CASE(tcs, instantiate__loop__nested__no_iterations);
990     ATF_ADD_TEST_CASE(tcs, instantiate__loop__nested__multiple_iterations);
991     ATF_ADD_TEST_CASE(tcs, instantiate__loop__sequential);
992     ATF_ADD_TEST_CASE(tcs, instantiate__loop__scoping);
993     ATF_ADD_TEST_CASE(tcs, instantiate__mismatched_delimiters);
994     ATF_ADD_TEST_CASE(tcs, instantiate__empty_statement);
995     ATF_ADD_TEST_CASE(tcs, instantiate__unknown_statement);
996     ATF_ADD_TEST_CASE(tcs, instantiate__invalid_narguments);
997 
998     ATF_ADD_TEST_CASE(tcs, instantiate__files__ok);
999     ATF_ADD_TEST_CASE(tcs, instantiate__files__input_error);
1000     ATF_ADD_TEST_CASE(tcs, instantiate__files__output_error);
1001 }
1002