blob: 5a237162e8860dc7a5578a38c8418adcc7e495cf [file] [log] [blame]
Brian Silverman70325d62015-09-20 17:00:43 -04001// Copyright (c) 2009, 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
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// ---
31// Author: csilvers@google.com (Craig Silverstein)
32//
33
34#include "config_for_unittests.h"
35#include <ctemplate/template_cache.h>
36#include <assert.h> // for assert()
37#include <stdio.h> // for printf()
38#include <stdlib.h> // for exit()
39#include <string.h> // for strcmp()
40#include <sys/types.h> // for mode_t
41#ifdef HAVE_UNISTD_H
42# include <unistd.h>
43#endif // for unlink()
44#include <ctemplate/template.h> // for Template
45#include <ctemplate/template_dictionary.h> // for TemplateDictionary
46#include <ctemplate/template_enums.h> // for DO_NOT_STRIP, etc
47#include <ctemplate/template_pathops.h> // for PathJoin(), kCWD
48#include <ctemplate/template_string.h> // for TemplateString
49#include "tests/template_test_util.h" // for AssertExpandIs(), etc
50using std::string;
51using GOOGLE_NAMESPACE::FLAGS_test_tmpdir;
52using GOOGLE_NAMESPACE::AssertExpandIs;
53using GOOGLE_NAMESPACE::CreateOrCleanTestDir;
54using GOOGLE_NAMESPACE::CreateOrCleanTestDirAndSetAsTmpdir;
55using GOOGLE_NAMESPACE::DO_NOT_STRIP;
56using GOOGLE_NAMESPACE::PathJoin;
57using GOOGLE_NAMESPACE::STRIP_BLANK_LINES;
58using GOOGLE_NAMESPACE::STRIP_WHITESPACE;
59using GOOGLE_NAMESPACE::StaticTemplateString;
60using GOOGLE_NAMESPACE::StringToFile;
61using GOOGLE_NAMESPACE::StringToTemplateCache;
62using GOOGLE_NAMESPACE::StringToTemplateFile;
63using GOOGLE_NAMESPACE::Template;
64using GOOGLE_NAMESPACE::TemplateCache;
65using GOOGLE_NAMESPACE::TemplateCachePeer;
66using GOOGLE_NAMESPACE::TemplateDictionary;
67using GOOGLE_NAMESPACE::kCWD;
68
69#define ASSERT(cond) do { \
70 if (!(cond)) { \
71 printf("ASSERT FAILED, line %d: %s\n", __LINE__, #cond); \
72 assert(cond); \
73 exit(1); \
74 } \
75} while (0)
76
77#define ASSERT_STREQ(a, b) ASSERT(strcmp(a, b) == 0)
78
79static const StaticTemplateString kKey = STS_INIT(kKey, "MY_KEY");
80static const StaticTemplateString kContent = STS_INIT(kContent, "content");
81
82// It would be nice to use the TEST framework, but it makes friendship
83// more difficult. (TemplateCache befriends TemplateCacheUnittest.)
84class TemplateCacheUnittest {
85 public:
86 static void TestGetTemplate() {
87 // Tests the cache
88 TemplateCache cache1;
89 const char* text = "{This is perfectly valid} yay!";
90 TemplateDictionary empty_dict("dict");
91
92 string filename = StringToTemplateFile(text);
93 const Template* tpl1 = cache1.GetTemplate(filename, DO_NOT_STRIP);
94 const Template* tpl2 = cache1.GetTemplate(filename.c_str(), DO_NOT_STRIP);
95 const Template* tpl3 = cache1.GetTemplate(filename, STRIP_WHITESPACE);
96 ASSERT(tpl1 && tpl2 && tpl3);
97 ASSERT(tpl1 == tpl2);
98 ASSERT(tpl1 != tpl3);
99 AssertExpandIs(tpl1, &empty_dict, text, true);
100 AssertExpandIs(tpl2, &empty_dict, text, true);
101 AssertExpandIs(tpl3, &empty_dict, text, true);
102
103 // Tests that a nonexistent template returns NULL
104 const Template* tpl4 = cache1.GetTemplate("/yakakak", STRIP_WHITESPACE);
105 ASSERT(!tpl4);
106
107 // Make sure we get different results if we use a different cache.
108 TemplateCache cache2;
109 const Template* tpl5 = cache2.GetTemplate(filename, DO_NOT_STRIP);
110 ASSERT(tpl5);
111 ASSERT(tpl5 != tpl1);
112 AssertExpandIs(tpl5, &empty_dict, text, true);
113
114 // And different results yet if we use the default cache.
115 const Template* tpl6 = Template::GetTemplate(filename, DO_NOT_STRIP);
116 ASSERT(tpl6);
117 ASSERT(tpl6 != tpl1);
118 AssertExpandIs(tpl6, &empty_dict, text, true);
119 }
120
121 static void TestLoadTemplate() {
122 // Tests the cache
123 TemplateCache cache1;
124 const char* text = "{This is perfectly valid} yay!";
125 TemplateDictionary empty_dict("dict");
126 string filename = StringToTemplateFile(text);
127
128 ASSERT(cache1.LoadTemplate(filename, DO_NOT_STRIP));
129
130 // Tests that a nonexistent template returns false
131 ASSERT(!cache1.LoadTemplate("/yakakak", STRIP_WHITESPACE));
132 }
133
134 static void TestStringGetTemplate() {
135 // If you use these same cache keys somewhere else,
136 // call Template::ClearCache first.
137 const string cache_key_a = "cache key a";
138 const string text = "Test template 1";
139 TemplateDictionary empty_dict("dict");
140
141 TemplateCache cache1;
142 const Template *tpl1;
143 ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
144 tpl1 = cache1.GetTemplate(cache_key_a, DO_NOT_STRIP);
145 AssertExpandIs(tpl1, &empty_dict, text, true);
146
147 // A different cache should give different templates.
148 TemplateCache cache2;
149 const Template *tpl3;
150 ASSERT(cache2.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
151 tpl3 = cache2.GetTemplate(cache_key_a, DO_NOT_STRIP);
152 ASSERT(tpl3 != tpl1);
153 AssertExpandIs(tpl3, &empty_dict, text, true);
154
155 // And the main cache different still
156 const Template *tpl4;
157 ASSERT(StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
158 tpl4 = Template::GetTemplate(cache_key_a, DO_NOT_STRIP);
159 ASSERT(tpl4 != tpl1);
160 AssertExpandIs(tpl4, &empty_dict, text, true);
161
162 // If we register a new string with the same text, it should be ignored.
163 ASSERT(!cache1.StringToTemplateCache(cache_key_a, "new text",
164 DO_NOT_STRIP));
165
166 Template::ClearCache();
167 }
168
169 static void TestStringToTemplateCacheWithStrip() {
170 const string cache_key_a = "cache key a";
171 const string text = "Test template 1";
172 TemplateDictionary empty_dict("dict");
173
174 TemplateCache cache;
175 ASSERT(cache.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
176
177 TemplateCachePeer cache_peer(&cache);
178 TemplateCachePeer::TemplateCacheKey cache_key1(cache_key_a, DO_NOT_STRIP);
179 ASSERT(cache_peer.TemplateIsCached(cache_key1));
180 const Template* tpl1 = cache_peer.GetTemplate(cache_key_a, DO_NOT_STRIP);
181 ASSERT(tpl1);
182 AssertExpandIs(tpl1, &empty_dict, text, true);
183
184 // Different strip: when a string template is registered via
185 // StringToTemplateCache with a strip, we cannot use a different
186 // strip later to fetch the template.
187 TemplateCachePeer::TemplateCacheKey cache_key2(cache_key_a,
188 STRIP_WHITESPACE);
189 ASSERT(!cache_peer.TemplateIsCached(cache_key2));
190 }
191
192 static void TestExpandNoLoad() {
193 TemplateCache cache;
194 string filename = StringToTemplateFile("alone");
195 string top_filename = StringToTemplateFile("Hello, {{>WORLD}}");
196 string inc_filename = StringToTemplateFile("world");
197
198 TemplateDictionary dict("ExpandNoLoad");
199 dict.AddIncludeDictionary("WORLD")->SetFilename(inc_filename);
200 string out;
201
202 // This should fail because the cache is empty.
203 cache.Freeze();
204 ASSERT(!cache.ExpandNoLoad(filename, DO_NOT_STRIP, &dict, NULL, &out));
205
206 cache.ClearCache(); // also clears the "frozen" state
207 // This should succeed -- it loads inc_filename from disk.
208 ASSERT(cache.ExpandWithData(filename, DO_NOT_STRIP, &dict, NULL, &out));
209 ASSERT(out == "alone");
210 out.clear();
211 // Now this should succeed -- it's in the cache.
212 cache.Freeze();
213 ASSERT(cache.ExpandNoLoad(filename, DO_NOT_STRIP, &dict, NULL, &out));
214 ASSERT(out == "alone");
215 out.clear();
216
217 // This should fail because neither top nor inc are in the cache.
218 cache.ClearCache();
219 cache.Freeze();
220 ASSERT(!cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
221 cache.ClearCache();
222 ASSERT(cache.LoadTemplate(top_filename, DO_NOT_STRIP));
223 // This *should* fail, but because inc_filename isn't in the cache.
224 cache.Freeze();
225 ASSERT(!cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
226 // TODO(csilvers): this should not be necessary. But expand writes
227 // to its output even before it fails.
228 out.clear();
229 cache.ClearCache();
230 ASSERT(cache.LoadTemplate(top_filename, DO_NOT_STRIP));
231 ASSERT(cache.LoadTemplate(inc_filename, DO_NOT_STRIP));
232 cache.Freeze();
233 // *Now* it should succeed, with everything it needs loaded.
234 ASSERT(cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
235 ASSERT(out == "Hello, world");
236 out.clear();
237 // This should succeed too, of course.
238 ASSERT(cache.ExpandWithData(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
239 ASSERT(out == "Hello, world");
240 out.clear();
241
242 cache.ClearCache();
243 ASSERT(cache.ExpandWithData(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
244 ASSERT(out == "Hello, world");
245 out.clear();
246 // Now everything NoLoad needs should be in the cache again.
247 cache.Freeze();
248 ASSERT(cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
249 ASSERT(out == "Hello, world");
250 out.clear();
251
252 cache.ClearCache();
253 ASSERT(cache.LoadTemplate(top_filename, DO_NOT_STRIP));
254 cache.Freeze();
255 // This fails, of course, because we're frozen.
256 ASSERT(!cache.LoadTemplate(inc_filename, DO_NOT_STRIP));
257 // And thus, this fails too.
258 ASSERT(!cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
259 }
260
261 static void TestTemplateSearchPath() {
262 TemplateCache cache1;
263
264 const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
265 const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
266 CreateOrCleanTestDir(pathA);
267 CreateOrCleanTestDir(pathB);
268
269 TemplateDictionary dict("");
270 cache1.SetTemplateRootDirectory(pathA);
271 cache1.AddAlternateTemplateRootDirectory(pathB);
272 ASSERT(cache1.template_root_directory() == pathA);
273
274 // 1. Show that a template in the secondary path can be found.
275 const string path_b_bar = PathJoin(pathB, "template_bar");
276 StringToFile("b/template_bar", path_b_bar);
277 ASSERT_STREQ(path_b_bar.c_str(),
278 cache1.FindTemplateFilename("template_bar").c_str());
279 const Template* b_bar = cache1.GetTemplate("template_bar", DO_NOT_STRIP);
280 ASSERT(b_bar);
281 AssertExpandIs(b_bar, &dict, "b/template_bar", true);
282
283 // 2. Show that the search stops once the first match is found.
284 // Create two templates in separate directories with the same name.
285 const string path_a_foo = PathJoin(pathA, "template_foo");
286 const string path_b_foo = PathJoin(pathB, "template_foo");
287 StringToFile("a/template_foo", path_a_foo);
288 StringToFile("b/template_foo", path_b_foo);
289 ASSERT_STREQ(path_a_foo.c_str(),
290 cache1.FindTemplateFilename("template_foo").c_str());
291 const Template* a_foo = cache1.GetTemplate("template_foo", DO_NOT_STRIP);
292 ASSERT(a_foo);
293 AssertExpandIs(a_foo, &dict, "a/template_foo", true);
294
295 // 3. Show that attempting to find a non-existent template gives an
296 // empty path.
297 ASSERT(cache1.FindTemplateFilename("baz").empty());
298
299 // 4. If we make a new cache, its path will be followed.
300 TemplateCache cache2;
301 cache2.SetTemplateRootDirectory(pathB);
302 ASSERT_STREQ(path_b_foo.c_str(),
303 cache2.FindTemplateFilename("template_foo").c_str());
304 const Template* b_foo = cache2.GetTemplate("template_foo", DO_NOT_STRIP);
305 ASSERT(b_foo);
306 AssertExpandIs(b_foo, &dict, "b/template_foo", true);
307
308 // 5. Neither path will work for the default cache, which has no path.
309 ASSERT(Template::template_root_directory() == kCWD);
310 ASSERT(Template::FindTemplateFilename("template_foo").empty());
311 ASSERT(!Template::GetTemplate("template_foo", DO_NOT_STRIP));
312
313 CreateOrCleanTestDir(pathA);
314 CreateOrCleanTestDir(pathB);
315 }
316
317 static void TestDelete() {
318 Template::ClearCache(); // just for exercise.
319 const string cache_key = "TestRemoveStringFromTemplateCache";
320 const string text = "<html>here today...</html>";
321 const string text2 = "<html>on disk tomorrow</html>";
322
323 TemplateDictionary dict("test");
324 TemplateCache cache1;
325
326 ASSERT(cache1.StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
327 const Template* tpl = cache1.GetTemplate(cache_key, DO_NOT_STRIP);
328 ASSERT(tpl);
329 AssertExpandIs(tpl, &dict, text, true);
330
331 cache1.Delete(cache_key);
332 tpl = cache1.GetTemplate(cache_key, DO_NOT_STRIP);
333 ASSERT(!tpl);
334 tpl = cache1.GetTemplate(cache_key, STRIP_WHITESPACE);
335 ASSERT(!tpl);
336 tpl = cache1.GetTemplate(cache_key, STRIP_BLANK_LINES);
337 ASSERT(!tpl);
338
339 // Try delete on a file-based template as well.
340 string filename = StringToTemplateFile(text2);
341 tpl = cache1.GetTemplate(filename, DO_NOT_STRIP);
342 ASSERT(tpl);
343 AssertExpandIs(tpl, &dict, text2, true);
344 cache1.Delete(filename);
345 tpl = cache1.GetTemplate(filename, DO_NOT_STRIP);
346 ASSERT(tpl);
347 AssertExpandIs(tpl, &dict, text2, true);
348
349 // Try re-adding a cache key after deleting it.
350 ASSERT(cache1.StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
351 tpl = cache1.GetTemplate(cache_key, DO_NOT_STRIP);
352 ASSERT(tpl);
353 AssertExpandIs(tpl, &dict, text, true);
354
355 // Try ClearCache while we're at it.
356 cache1.ClearCache();
357 tpl = cache1.GetTemplate(cache_key, STRIP_BLANK_LINES);
358 ASSERT(!tpl);
359
360 // Test on the Template class, which has a different function name.
361 ASSERT(StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
362 tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
363 ASSERT(tpl);
364 AssertExpandIs(tpl, &dict, text, true);
365
366 Template::RemoveStringFromTemplateCache(cache_key);
367 tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
368 ASSERT(!tpl);
369 tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE);
370 ASSERT(!tpl);
371 tpl = Template::GetTemplate(cache_key, STRIP_BLANK_LINES);
372 ASSERT(!tpl);
373 }
374
375 static void TestTemplateCache() {
376 const string filename_a = StringToTemplateFile("Test template 1");
377 const string filename_b = StringToTemplateFile("Test template 2.");
378
379 TemplateCache cache1;
380 const Template *tpl, *tpl2;
381 ASSERT(tpl = cache1.GetTemplate(filename_a, DO_NOT_STRIP));
382
383 ASSERT(tpl2 = cache1.GetTemplate(filename_b, DO_NOT_STRIP));
384 ASSERT(tpl2 != tpl); // different filenames.
385 ASSERT(tpl2 = cache1.GetTemplate(filename_a, STRIP_BLANK_LINES));
386 ASSERT(tpl2 != tpl); // different strip.
387 ASSERT(tpl2 = cache1.GetTemplate(filename_b, STRIP_BLANK_LINES));
388 ASSERT(tpl2 != tpl); // different filenames and strip.
389 ASSERT(tpl2 = cache1.GetTemplate(filename_a, DO_NOT_STRIP));
390 ASSERT(tpl2 == tpl); // same filename and strip.
391 }
392
393 static void TestReloadAllIfChangedLazyLoad() {
394 TemplateDictionary dict("empty");
395 TemplateCache cache1;
396
397 string filename = StringToTemplateFile("{valid template}");
398 string nonexistent = StringToTemplateFile("dummy");
399 unlink(nonexistent.c_str());
400
401 const Template* tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE);
402 assert(tpl);
403 const Template* tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
404 assert(!tpl2);
405
406 StringToFile("exists now!", nonexistent);
407 tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
408 ASSERT(!tpl2);
409 cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
410 tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE); // force the reload
411 tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
412 ASSERT(tpl2); // file exists now
413
414 unlink(nonexistent.c_str()); // here today...
415 cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
416 ASSERT(cache1.GetTemplate(filename, STRIP_WHITESPACE));
417 ASSERT(!cache1.GetTemplate(nonexistent, STRIP_WHITESPACE));
418
419 StringToFile("lazarus", nonexistent);
420 StringToFile("{new template}", filename);
421 tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE);
422 AssertExpandIs(tpl, &dict, "{valid template}", true); // haven't reloaded
423 // But a different cache (say, the default) should load the new content.
424 const Template* tpl3 = Template::GetTemplate(filename, STRIP_WHITESPACE);
425 AssertExpandIs(tpl3, &dict, "{new template}", true);
426
427 cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
428 tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE); // needed
429 AssertExpandIs(tpl, &dict, "{new template}", true);
430 tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
431 ASSERT(tpl2);
432 AssertExpandIs(tpl2, &dict, "lazarus", true);
433
434 // Ensure that string templates don't reload
435 const string cache_key_a = "cache key a";
436 const string text = "Test template 1";
437 const Template *str_tpl;
438 ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
439 str_tpl = cache1.GetTemplate(cache_key_a, DO_NOT_STRIP);
440 AssertExpandIs(str_tpl, &dict, text, true);
441 cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
442 ASSERT(cache1.GetTemplate(cache_key_a, DO_NOT_STRIP) == str_tpl);
443
444 cache1.ClearCache();
445 }
446
447 static void TestReloadAllIfChangedImmediateLoad() {
448 TemplateDictionary dict("empty");
449 TemplateCache cache1;
450 TemplateCachePeer cache_peer(&cache1);
451
452 // Add templates
453 string filename1 = StringToTemplateFile("{valid template}");
454 string filename2 = StringToTemplateFile("{another valid template}");
455
456 const Template* tpl1 = cache1.GetTemplate(filename1,
457 STRIP_WHITESPACE);
458 assert(tpl1);
459 const Template* tpl2 = cache1.GetTemplate(filename2,
460 STRIP_WHITESPACE);
461 assert(tpl2);
462
463 StringToFile("{file1 contents changed}", filename1);
464 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
465
466 TemplateCachePeer::TemplateCacheKey cache_key1(filename1, STRIP_WHITESPACE);
467 ASSERT(cache_peer.TemplateIsCached(cache_key1));
468 const Template* tpl1_post_reload = cache_peer.GetTemplate(filename1,
469 STRIP_WHITESPACE);
470 ASSERT(tpl1_post_reload != tpl1);
471 // Check that cache1's tpl1 has the new contents
472 AssertExpandIs(tpl1_post_reload, &dict, "{file1 contents changed}",
473 true);
474
475 // Ensure tpl2 is unchanged
476 TemplateCachePeer::TemplateCacheKey cache_key2(filename2, STRIP_WHITESPACE);
477 ASSERT(cache_peer.TemplateIsCached(cache_key2));
478 const Template* tpl2_post_reload = cache_peer.GetTemplate(filename2,
479 STRIP_WHITESPACE);
480 ASSERT(tpl2_post_reload == tpl2);
481
482 // Test delete & re-add: delete tpl2, and reload.
483 unlink(filename2.c_str());
484 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
485 ASSERT(!cache_peer.GetTemplate(filename2, STRIP_WHITESPACE));
486 // Re-add tpl2 and ensure it reloads.
487 StringToFile("{re-add valid template contents}", filename2);
488 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
489 ASSERT(cache_peer.GetTemplate(filename2, STRIP_WHITESPACE));
490
491 // Ensure that string templates don't reload
492 const string cache_key_a = "cache key a";
493 const string text = "Test template 1";
494 const Template *str_tpl;
495 ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
496 str_tpl = cache1.GetTemplate(cache_key_a, DO_NOT_STRIP);
497 AssertExpandIs(str_tpl, &dict, text, true);
498 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
499 ASSERT(cache1.GetTemplate(cache_key_a, DO_NOT_STRIP) == str_tpl);
500
501 cache1.ClearCache();
502 }
503
504 static void TestReloadImmediateWithDifferentSearchPaths() {
505 TemplateDictionary dict("empty");
506 TemplateCache cache1;
507 TemplateCachePeer cache_peer(&cache1);
508
509 const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
510 const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
511 CreateOrCleanTestDir(pathA);
512 CreateOrCleanTestDir(pathB);
513
514 cache1.SetTemplateRootDirectory(pathA);
515 cache1.AddAlternateTemplateRootDirectory(pathB);
516 ASSERT(cache1.template_root_directory() == pathA);
517
518 // Add b/foo
519 const string path_b_foo = PathJoin(pathB, "template_foo");
520 StringToFile("b/template_foo", path_b_foo);
521 ASSERT_STREQ(path_b_foo.c_str(),
522 cache1.FindTemplateFilename("template_foo").c_str());
523 // Add b/foo to the template cache.
524 cache1.GetTemplate("template_foo", DO_NOT_STRIP);
525
526 // Add a/foo
527 const string path_a_foo = PathJoin(pathA, "template_foo");
528 StringToFile("a/template_foo", path_a_foo);
529 ASSERT_STREQ(path_a_foo.c_str(),
530 cache1.FindTemplateFilename("template_foo").c_str());
531
532 // Now, on reload we pick up foo from the earlier search path: a/foo
533 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
534 const Template* foo_post_reload = cache_peer.GetTemplate("template_foo",
535 STRIP_WHITESPACE);
536 AssertExpandIs(foo_post_reload, &dict, "a/template_foo",
537 true);
538
539 // Delete a/foo and reload. Now we pick up the next available foo: b/foo
540 unlink(path_a_foo.c_str());
541 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
542 foo_post_reload = cache_peer.GetTemplate("template_foo",
543 STRIP_WHITESPACE);
544 AssertExpandIs(foo_post_reload, &dict, "b/template_foo",
545 true);
546 }
547
548 static void TestReloadLazyWithDifferentSearchPaths() {
549 // Identical test as above with but with LAZY_RELOAD
550 TemplateDictionary dict("empty");
551 TemplateCache cache1;
552 TemplateCachePeer cache_peer(&cache1);
553
554 const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
555 const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
556 CreateOrCleanTestDir(pathA);
557 CreateOrCleanTestDir(pathB);
558
559 cache1.SetTemplateRootDirectory(pathA);
560 cache1.AddAlternateTemplateRootDirectory(pathB);
561 ASSERT(cache1.template_root_directory() == pathA);
562
563 // Add b/foo
564 const string path_b_foo = PathJoin(pathB, "template_foo");
565 StringToFile("b/template_foo", path_b_foo);
566 ASSERT_STREQ(path_b_foo.c_str(),
567 cache1.FindTemplateFilename("template_foo").c_str());
568 // Add b/foo to the template cache.
569 cache1.GetTemplate("template_foo", DO_NOT_STRIP);
570
571 // Add a/foo
572 const string path_a_foo = PathJoin(pathA, "template_foo");
573 StringToFile("a/template_foo", path_a_foo);
574 ASSERT_STREQ(path_a_foo.c_str(),
575 cache1.FindTemplateFilename("template_foo").c_str());
576
577 // Now, on reload we pick up foo from the earlier search path: a/foo
578 cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
579 const Template* foo_post_reload = cache_peer.GetTemplate("template_foo",
580 STRIP_WHITESPACE);
581 AssertExpandIs(foo_post_reload, &dict, "a/template_foo",
582 true);
583
584 // Delete a/foo and reload. Now we pick up the next available foo: b/foo
585 unlink(path_a_foo.c_str());
586 cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
587 foo_post_reload = cache_peer.GetTemplate("template_foo",
588 STRIP_WHITESPACE);
589 AssertExpandIs(foo_post_reload, &dict, "b/template_foo",
590 true);
591 }
592
593 static void TestRefcounting() {
594 TemplateCache cache1;
595 TemplateCachePeer cache_peer(&cache1);
596 TemplateDictionary dict("dict");
597
598 // Add templates
599 string filename1 = StringToTemplateFile("{valid template}");
600 string filename2 = StringToTemplateFile("{another valid template}");
601
602 const Template* cache1_tpl1 = cache1.GetTemplate(filename1,
603 STRIP_WHITESPACE);
604 assert(cache1_tpl1);
605 const Template* cache1_tpl2 = cache1.GetTemplate(filename2,
606 STRIP_WHITESPACE);
607 assert(cache1_tpl2);
608
609 // Check refcount. It should be 2 -- one for the originalvalue
610 // when it's constructed, and one for the call to GetTemplate.
611 TemplateCachePeer::TemplateCacheKey cache_key1(filename1, STRIP_WHITESPACE);
612 ASSERT(cache_peer.Refcount(cache_key1) == 2);
613 TemplateCachePeer::TemplateCacheKey cache_key2(filename2, STRIP_WHITESPACE);
614 ASSERT(cache_peer.Refcount(cache_key2) == 2);
615
616 // Clone cache2 from cache1
617 TemplateCache* cache2 = cache1.Clone();
618 TemplateCachePeer cache_peer2(cache2);
619
620 // Check refcount was incremented. It should be the same for both caches.
621 ASSERT(cache_peer.Refcount(cache_key1) == 3);
622 ASSERT(cache_peer2.Refcount(cache_key1) == 3);
623 ASSERT(cache_peer.Refcount(cache_key2) == 3);
624 ASSERT(cache_peer2.Refcount(cache_key2) == 3);
625
626 // Check that the template ptrs in both caches are the same.
627 const Template* cache2_tpl1 = cache2->GetTemplate(filename1,
628 STRIP_WHITESPACE);
629 const Template* cache2_tpl2 = cache2->GetTemplate(filename2,
630 STRIP_WHITESPACE);
631 ASSERT(cache2_tpl1 == cache1_tpl1);
632 ASSERT(cache2_tpl2 == cache1_tpl2);
633
634 // GetTemplate should have augmented the refcount.
635 ASSERT(cache_peer.Refcount(cache_key1) == 4);
636 ASSERT(cache_peer2.Refcount(cache_key1) == 4);
637 ASSERT(cache_peer.Refcount(cache_key2) == 4);
638 ASSERT(cache_peer2.Refcount(cache_key2) == 4);
639
640 // Change tpl1 file contents and reload.
641 StringToFile("{file1 contents changed}", filename1);
642 cache2->ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
643 // Since the template will be reloaded into a new instance,
644 // GetTemplate will return new pointers. The older template
645 // pointer was moved to the freelist.
646 const Template* cache2_tpl1_post_reload = cache2->GetTemplate(
647 filename1, STRIP_WHITESPACE);
648 ASSERT(cache2_tpl1_post_reload != cache2_tpl1);
649 // Check that cache1's tpl1 has the new contents
650 AssertExpandIs(cache2_tpl1_post_reload, &dict, "{file1 contents changed}",
651 true);
652
653 // Ensure tpl2 is unchanged
654 const Template* cache2_tpl2_post_reload = cache2->GetTemplate(
655 filename2, STRIP_WHITESPACE);
656 ASSERT(cache2_tpl2_post_reload == cache2_tpl2);
657
658 // Now key1 points to different templates in cache1 and cache2.
659 // cache1's version should have a refcount of 3 (was 4, went down
660 // by 1 when cache2 dropped its reference to it). cache2's
661 // version should be 2 (one for the new file, 1 for the call to
662 // GetTemplate() that followed it), while key2 should have a
663 // refcount of 5 in both caches (due to the new call, above, to
664 // GetTemplate()).
665 ASSERT(cache_peer.Refcount(cache_key1) == 3);
666 ASSERT(cache_peer2.Refcount(cache_key1) == 2);
667 ASSERT(cache_peer.Refcount(cache_key2) == 5);
668 ASSERT(cache_peer2.Refcount(cache_key2) == 5);
669
670 const int old_delete_count = cache_peer.NumTotalTemplateDeletes();
671
672 // Clear up the cache2's freelist, this should drop all refcounts,
673 // due to the calls cache_peer2 made to
674 // GetTemplate(the-old-filename1), GetTemplate(the-new-filename1),
675 // and GetTemplate(filename2) (twice!)
676 cache_peer2.DoneWithGetTemplatePtrs();
677 ASSERT(cache_peer.Refcount(cache_key1) == 2);
678 ASSERT(cache_peer2.Refcount(cache_key1) == 1);
679 ASSERT(cache_peer.Refcount(cache_key2) == 3);
680 ASSERT(cache_peer2.Refcount(cache_key2) == 3);
681
682 // Make sure that deleting from the cache causes deletion.
683 // ClearCache() on peer1 should finally get rid of the old filename1.
684 cache_peer.ClearCache();
685 ASSERT(cache_peer.NumTotalTemplateDeletes() == old_delete_count + 1);
686 cache_peer2.ClearCache();
687 // Delete-count should go up by 2 as both the new tpl1, and tpl2, go away.
688 ASSERT(cache_peer.NumTotalTemplateDeletes() == old_delete_count + 3);
689
690 delete cache2;
691 }
692
693 static void TestDoneWithGetTemplatePtrs() {
694 TemplateCache cache1;
695 TemplateCachePeer cache_peer1(&cache1);
696 TemplateDictionary dict("dict");
697
698 // Add templates
699 string fname = StringToTemplateFile("{valid template}");
700 TemplateCachePeer::TemplateCacheKey cache_key(fname, STRIP_WHITESPACE);
701 string out;
702
703 int old_delete_count = cache_peer1.NumTotalTemplateDeletes();
704
705 // OK, let's get the templates in the cache.
706 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
707 // This should not have changed the delete-count.
708 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
709 // And the refcount should be 1.
710 ASSERT(cache_peer1.Refcount(cache_key) == 1);
711 // Same holds if we expand again.
712 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
713 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
714 ASSERT(cache_peer1.Refcount(cache_key) == 1);
715
716 // Now we delete from the cache. Should up the delete_count.
717 ASSERT(cache1.Delete(fname));
718 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
719
720 // Calling DoneWithGetTemplatePtrs() should be a noop -- we
721 // haven't called GetTemplate() yet.
722 cache1.DoneWithGetTemplatePtrs();
723 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
724
725 // Now do the same thing, but throw in a GetTemplate(). Now
726 // DoneWithGetTemplatePtrs() should still cause a delete, but only
727 // after a call to Delete() deletes the cache's refcount too.
728 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
729 cache1.GetTemplate(fname, STRIP_WHITESPACE);
730 cache1.DoneWithGetTemplatePtrs();
731 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
732 ASSERT(cache1.Delete(fname));
733 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
734 cache1.ClearCache();
735 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
736
737 // Now load in a replacement. The loading itself should cause a
738 // delete (no GetTemplate calls, so no need to involve the freelist).
739 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
740 StringToFile("{file1 contents changed}", fname);
741 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
742 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
743 // DoneWithGetTemplatePtrs() should just be a noop.
744 cache1.DoneWithGetTemplatePtrs();
745 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
746 // Delete the new version of fname too!
747 cache1.Delete(fname);
748 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
749
750 // Now load in a replacement, but having done a GetTemplate() first.
751 // We need DoneWithGetTemplatePtrs() to delete, in this case.
752 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
753 cache1.GetTemplate(fname, STRIP_WHITESPACE);
754 ASSERT(cache_peer1.Refcount(cache_key) == 2);
755 StringToFile("{file1 contents changed}", fname);
756 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
757 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
758 cache1.DoneWithGetTemplatePtrs();
759 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
760 // Delete the new version of fname too!
761 cache1.Delete(fname);
762 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
763
764 // Add a Clone() into the mix. Now Delete() calls, even from both
765 // caches, won't up the delete-count until we DoneWithGetTemplatePtrs()
766 // -- but only from the cache that called GetTemplate().
767 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
768 cache1.GetTemplate(fname, STRIP_WHITESPACE);
769 ASSERT(cache_peer1.Refcount(cache_key) == 2);
770 {
771 TemplateCache* cache2 = cache1.Clone();
772 TemplateCachePeer cache_peer2(cache2);
773 ASSERT(cache_peer1.Refcount(cache_key) == 3);
774 ASSERT(cache_peer2.Refcount(cache_key) == 3);
775 // Do all sorts of Delete()s.
776 StringToFile("{file1 contents changed}", fname);
777 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
778 ASSERT(cache_peer1.Refcount(cache_key) == 1); // the new file
779 ASSERT(cache_peer2.Refcount(cache_key) == 2); // the old file
780 cache2->ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
781 // Each cache has a different copy of the new file.
782 ASSERT(cache_peer1.Refcount(cache_key) == 1); // the new file
783 ASSERT(cache_peer2.Refcount(cache_key) == 1); // the new file
784 ASSERT(cache1.Delete(fname)); // should delete the new file
785 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
786 ASSERT(cache2->Delete(fname));
787 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
788 cache2->DoneWithGetTemplatePtrs();
789 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
790 cache1.DoneWithGetTemplatePtrs();
791 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
792 cache1.ClearCache();
793 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
794 delete cache2;
795 }
796
797 // If we call DoneWithGetTemplatePtrs() while a clone points to the
798 // template, it won't delete the template yet.
799 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
800 {
801 TemplateCache* cache2 = cache1.Clone();
802 TemplateCachePeer cache_peer2(cache2);
803 StringToFile("{file1 contents changed}", fname);
804 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
805 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
806 delete cache2;
807 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
808 }
809 cache1.ClearCache();
810 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
811
812 // If we throw an explicit GetTemplate() in, we still need
813 // DoneWithGetTemplatePtrs().
814 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
815 cache1.GetTemplate(fname, STRIP_WHITESPACE);
816 {
817 TemplateCache* cache2 = cache1.Clone();
818 TemplateCachePeer cache_peer2(cache2);
819 StringToFile("{file1 contents changed}", fname);
820 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
821 cache1.DoneWithGetTemplatePtrs();
822 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
823 delete cache2;
824 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
825 }
826 cache1.ClearCache();
827 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
828
829 // Multiple GetTemplate()s should still all be cleared by
830 // DoneWithGetTemplatePtrs().
831 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
832 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
833 cache1.GetTemplate(fname, STRIP_WHITESPACE);
834 cache1.GetTemplate(fname, STRIP_WHITESPACE);
835 ASSERT(cache_peer1.Refcount(cache_key) == 3);
836 StringToFile("{file1 contents changed}", fname);
837 cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
838 cache1.DoneWithGetTemplatePtrs();
839 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
840 cache1.ClearCache();
841 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
842
843 // Calling ClearCache() deletes old templates too -- we don't even
844 // need to change the content.
845 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
846 cache1.GetTemplate(fname, STRIP_WHITESPACE);
847 cache1.GetTemplate(fname, STRIP_WHITESPACE);
848 cache1.ClearCache();
849 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
850
851 // So does deleting the cache object.
852 ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
853 {
854 TemplateCache* cache2 = cache1.Clone();
855 TemplateCachePeer cache_peer2(cache2);
856 ASSERT(cache_peer1.Refcount(cache_key) == 2);
857 cache2->GetTemplate(fname, STRIP_WHITESPACE);
858 ASSERT(cache_peer1.Refcount(cache_key) == 3);
859 ASSERT(cache_peer2.Refcount(cache_key) == 3);
860 ASSERT(cache1.Delete(fname));
861 ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
862 ASSERT(cache_peer2.Refcount(cache_key) == 2);
863 delete cache2;
864 }
865 ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
866 }
867
868 static void TestCloneStringTemplates() {
869 TemplateCache cache1;
870
871 // Create & insert a string template
872 const string cache_key_a = "cache key a";
873 const string text = "Test template 1";
874 TemplateDictionary empty_dict("dict");
875
876 ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
877
878 // Clone cache2 from cache1
879 TemplateCache* cache2 = cache1.Clone();
880
881 // Check that the string template was copied into cache2
882 const Template* cache2_tpl = cache2->GetTemplate(cache_key_a,
883 DO_NOT_STRIP);
884 ASSERT(cache2_tpl);
885 AssertExpandIs(cache2_tpl, &empty_dict, text, true);
886
887 delete cache2;
888 }
889
890 static void TestInclude() {
891 TemplateCache cache;
892 string incname = StringToTemplateFile("include & print file\n");
893 string tpl_file = StringToTemplateFile("hi {{>INC:h}} bar\n");
894 const Template* tpl = cache.GetTemplate(tpl_file, DO_NOT_STRIP);
895 ASSERT(tpl);
896
897 TemplateDictionary dict("dict");
898 AssertExpandWithCacheIs(&cache, tpl_file, DO_NOT_STRIP, &dict, NULL,
899 "hi bar\n", true);
900 dict.AddIncludeDictionary("INC")->SetFilename(incname);
901 AssertExpandWithCacheIs(&cache, tpl_file, DO_NOT_STRIP, &dict, NULL,
902 "hi include &amp; print file bar\n",
903 true);
904 }
905
906 // Make sure we don't deadlock when a template includes itself.
907 // This also tests we handle recursive indentation properly.
908 static void TestRecursiveInclude() {
909 TemplateCache cache;
910 string incname = StringToTemplateFile("hi {{>INC}} bar\n {{>INC}}!");
911 const Template* tpl = cache.GetTemplate(incname, DO_NOT_STRIP);
912 ASSERT(tpl);
913 TemplateDictionary dict("dict");
914 dict.AddIncludeDictionary("INC")->SetFilename(incname);
915 // Note the last line is indented 4 spaces instead of 2. This is
916 // because the last sub-include is indented.
917 AssertExpandWithCacheIs(&cache, incname, DO_NOT_STRIP, &dict, NULL,
918 "hi hi bar\n ! bar\n hi bar\n !!",
919 true);
920 }
921
922 static void TestStringTemplateInclude() {
923 const string cache_key = "TestStringTemplateInclude";
924 const string cache_key_inc = "TestStringTemplateInclude-inc";
925 const string text = "<html>{{>INC}}</html>";
926 const string text_inc = "<div>\n<p>\nUser {{USER}}\n</div>";
927
928 TemplateCache cache;
929 ASSERT(cache.StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
930 ASSERT(cache.StringToTemplateCache(cache_key_inc, text_inc, DO_NOT_STRIP));
931
932 const Template *tpl = cache.GetTemplate(cache_key, DO_NOT_STRIP);
933 ASSERT(tpl);
934
935 TemplateDictionary dict("dict");
936 TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC");
937 sub_dict->SetFilename(cache_key_inc);
938
939 sub_dict->SetValue("USER", "John<>Doe");
940 string expected = "<html><div>\n<p>\nUser John<>Doe\n</div></html>";
941 AssertExpandWithCacheIs(&cache, cache_key, DO_NOT_STRIP, &dict, NULL,
942 expected, true);
943 }
944
945 static void TestTemplateString() {
946 TemplateCache cache;
947 ASSERT(cache.StringToTemplateCache(kKey, kContent, DO_NOT_STRIP));
948 const Template *tpl = cache.GetTemplate(kKey, DO_NOT_STRIP);
949 ASSERT(tpl);
950
951 TemplateDictionary dict("dict");
952 AssertExpandWithCacheIs(&cache, "MY_KEY", DO_NOT_STRIP, &dict, NULL,
953 "content", true);
954
955 // Try retrieving with a char* rather than a TemplateString*.
956 tpl = cache.GetTemplate("MY_KEY", DO_NOT_STRIP);
957 ASSERT(tpl);
958 AssertExpandWithCacheIs(&cache, "MY_KEY", DO_NOT_STRIP, &dict, NULL,
959 "content", true);
960
961 // Delete with a char* rather than a TemplateString*.
962 cache.Delete("MY_KEY");
963 tpl = cache.GetTemplate("MY_KEY", DO_NOT_STRIP);
964 ASSERT(!tpl);
965
966 ASSERT(cache.StringToTemplateCache("MY_KEY", "content", DO_NOT_STRIP));
967 tpl = cache.GetTemplate(kKey, DO_NOT_STRIP);
968 ASSERT(tpl);
969 cache.Delete(kKey);
970 tpl = cache.GetTemplate("MY_KEY", DO_NOT_STRIP);
971 ASSERT(!tpl);
972 }
973
974 static void TestFreeze() {
975 TemplateCache cache;
976 TemplateDictionary dict("dict");
977
978 // Load some templates
979 string filename1 = StringToTemplateFile("{valid template}");
980 string filename2 = StringToTemplateFile("hi {{>INC:h}} bar\n");
981
982 const Template* cache_tpl1 = cache.GetTemplate(filename1, STRIP_WHITESPACE);
983 assert(cache_tpl1);
984 AssertExpandIs(cache_tpl1, &dict, "{valid template}", true);
985 const Template* cache_tpl2 = cache.GetTemplate(filename2, DO_NOT_STRIP);
986 assert(cache_tpl2);
987 static_cast<void>(cache_tpl2); // avoid unused var warning in opt mode
988 AssertExpandWithCacheIs(&cache, filename2, DO_NOT_STRIP, &dict, NULL,
989 "hi bar\n", true);
990
991 // Set the root directory
992 const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
993 CreateOrCleanTestDir(pathA);
994 cache.SetTemplateRootDirectory(pathA);
995 ASSERT(cache.template_root_directory() == pathA);
996
997 // Freeze the cache now, and test its impact.
998 cache.Freeze();
999
1000 // 1. Loading new templates fails.
1001 string filename3 = StringToTemplateFile("{yet another valid template}");
1002 const Template* cache_tpl3 = cache.GetTemplate(filename3, STRIP_WHITESPACE);
1003 assert(!cache_tpl3);
1004 static_cast<void>(cache_tpl3); // avoid unused var warning in opt mode
1005
1006 // 2. Reloading existing templates fails.
1007 StringToFile("{file1 contents changed}", filename1);
1008 cache.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
1009 const Template* cache_tpl1_post_reload = cache.GetTemplate(
1010 filename1, STRIP_WHITESPACE);
1011 ASSERT(cache_tpl1_post_reload == cache_tpl1);
1012 // Check that cache's tpl1 has the same old contents
1013 AssertExpandIs(cache_tpl1_post_reload, &dict, "{valid template}",
1014 true);
1015 // 3. Cannot delete from a frozen cache.
1016 cache.Delete(filename1);
1017 ASSERT(cache.GetTemplate(filename1, STRIP_WHITESPACE));
1018
1019 // 4. Expand won't load an included template on-demand.
1020 string incname = StringToTemplateFile("include & print file\n");
1021 dict.AddIncludeDictionary("INC")->SetFilename(incname);
1022 AssertExpandWithCacheIs(&cache, filename2, DO_NOT_STRIP, &dict, NULL,
1023 "hi bar\n", false);
1024
1025 // 5. Cannot change template root directory.
1026 const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
1027 CreateOrCleanTestDir(pathB);
1028 cache.SetTemplateRootDirectory(pathB);
1029 ASSERT(cache.template_root_directory() == pathA); // Still the old path
1030
1031 CreateOrCleanTestDir(pathA);
1032 CreateOrCleanTestDir(pathB);
1033 }
1034};
1035
1036
1037int main(int argc, char** argv) {
1038
1039 CreateOrCleanTestDirAndSetAsTmpdir(FLAGS_test_tmpdir);
1040
1041 TemplateCacheUnittest::TestGetTemplate();
1042 TemplateCacheUnittest::TestLoadTemplate();
1043 TemplateCacheUnittest::TestStringGetTemplate();
1044 TemplateCacheUnittest::TestStringToTemplateCacheWithStrip();
1045 TemplateCacheUnittest::TestExpandNoLoad();
1046 TemplateCacheUnittest::TestTemplateSearchPath();
1047 TemplateCacheUnittest::TestDelete();
1048 TemplateCacheUnittest::TestTemplateCache();
1049 TemplateCacheUnittest::TestReloadAllIfChangedLazyLoad();
1050 TemplateCacheUnittest::TestReloadAllIfChangedImmediateLoad();
1051 TemplateCacheUnittest::TestReloadImmediateWithDifferentSearchPaths();
1052 TemplateCacheUnittest::TestReloadLazyWithDifferentSearchPaths();
1053 TemplateCacheUnittest::TestRefcounting();
1054 TemplateCacheUnittest::TestDoneWithGetTemplatePtrs();
1055 TemplateCacheUnittest::TestCloneStringTemplates();
1056 TemplateCacheUnittest::TestInclude();
1057 TemplateCacheUnittest::TestRecursiveInclude();
1058 TemplateCacheUnittest::TestStringTemplateInclude();
1059 TemplateCacheUnittest::TestTemplateString();
1060 TemplateCacheUnittest::TestFreeze();
1061
1062 printf("DONE\n");
1063 return 0;
1064}