Squashed 'third_party/ctemplate/' content from commit 6742f62

Change-Id: I828e4e4c906f13ba19944d78a8a78652b62949af
git-subtree-dir: third_party/ctemplate
git-subtree-split: 6742f6233db12f545e90baa8f34f5c29c4eb396a
diff --git a/src/tests/compile_test.cc b/src/tests/compile_test.cc
new file mode 100644
index 0000000..4951e56
--- /dev/null
+++ b/src/tests/compile_test.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: Craig Silverstein
+//
+// Most other tests use "config_for_unittests.h" to make testing easier.
+// This brings in some code most users won't use.  This test is meant
+// entirely to use ctemplate as users will, just #including the public
+// .h files directly.  It does hardly any work, and is mainly intended
+// as a compile check for the .h files.  It will not work if you use
+// a non-standard name for the package namespace (via
+//     ./configure --enable-namespace=foo
+// ), though you can fix that by changing the namespace alias below.
+
+// These are all the .h files that we export
+#include <ctemplate/per_expand_data.h>
+#include <ctemplate/template.h>
+#include <ctemplate/template_dictionary.h>
+#include <ctemplate/template_dictionary_interface.h>
+#include <ctemplate/template_emitter.h>
+#include <ctemplate/template_enums.h>
+#include <ctemplate/template_modifiers.h>
+#include <ctemplate/template_namelist.h>
+#include <ctemplate/template_pathops.h>
+#include <ctemplate/template_string.h>
+#include <stdio.h>
+#include <string>
+
+// If you used ./configure --enable-namespace=foo, replace 'ctemplate'
+// here with 'foo'.
+namespace template_ns = ctemplate;
+
+int main() {
+  template_ns::Template::StringToTemplateCache("key", "example");
+  template_ns::Template* tpl = template_ns::Template::GetTemplate(
+      "key", template_ns::DO_NOT_STRIP);
+  template_ns::TemplateDictionary dict("my dict");
+  std::string nothing_will_come_of_nothing;
+  tpl->Expand(&nothing_will_come_of_nothing, &dict);
+
+  // Try using a bit more functionality.
+  template_ns::PerExpandData data;
+  nothing_will_come_of_nothing.clear();
+  template_ns::ExpandWithData("key", template_ns::DO_NOT_STRIP, &dict, &data,
+                              &nothing_will_come_of_nothing);
+
+  printf("PASS.\n");
+  return 0;
+}
diff --git a/src/tests/config_for_unittests.h b/src/tests/config_for_unittests.h
new file mode 100644
index 0000000..aa20d9f
--- /dev/null
+++ b/src/tests/config_for_unittests.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+// 
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// All Rights Reserved.
+//
+//
+// This file is needed for windows -- unittests are not part of the
+// ctemplate dll, but still want to include config.h just like the
+// dll does, so they can use internal tools and APIs for testing.
+//
+// The problem is that config.h declares CTEMPLATE_DLL_DECL to be
+// for exporting symbols, but the unittest needs to *import* symbols
+// (since it's not the dll).
+//
+// The solution is to have this file, which is just like config.h but
+// sets CTEMPLATE_DLL_DECL to do a dllimport instead of a dllexport.
+//
+// The reason we need this extra CTEMPLATE_DLL_DECL_FOR_UNITTESTS
+// variable is in case people want to set CTEMPLATE_DLL_DECL explicitly
+// to something other than __declspec(dllexport).  In that case, they
+// may want to use something other than __declspec(dllimport) for the
+// unittest case.  For that, we allow folks to define both
+// CTEMPLATE_DLL_DECL and CTEMPLATE_DLL_DECL_FOR_UNITTESTS explicitly.
+//
+// NOTE: This file is equivalent to config.h on non-windows systems,
+// which never defined CTEMPLATE_DLL_DECL_FOR_UNITTESTS and always
+// define CTEMPLATE_DLL_DECL to the empty string.
+
+#include "config.h"
+
+#undef CTEMPLATE_DLL_DECL
+#ifdef CTEMPLATE_DLL_DECL_FOR_UNITTESTS
+# define CTEMPLATE_DLL_DECL  CTEMPLATE_DLL_DECL_FOR_UNITTESTS
+#else
+# define CTEMPLATE_DLL_DECL  // if DLL_DECL_FOR_UNITTESTS isn't defined, use ""
+#endif
diff --git a/src/tests/diff_tpl_auto_escape_unittest.sh b/src/tests/diff_tpl_auto_escape_unittest.sh
new file mode 100755
index 0000000..b74f112
--- /dev/null
+++ b/src/tests/diff_tpl_auto_escape_unittest.sh
@@ -0,0 +1,196 @@
+#!/bin/sh
+
+# Copyright (c) 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# ---
+# Author: jad@google.com (Jad Boutros)
+# Heavily inspired from make_tpl_varnames_h_unittest.sh
+#
+#
+# TODO(jad): Add better testing of Strip mode.
+# TODO(jad): Add testing for (lame) suppressed diffs.
+
+die() {
+    echo "Test failed: $@" 1>&2
+    exit 1
+}
+
+TEST_SRCDIR=${TEST_SRCDIR-"."}
+TEST_TMPDIR=${TMPDIR-"/tmp"}
+
+# Optional first argument is where the executable lives
+DIFFTPL=${1-"$TEST_SRCDIR/diff_tpl_auto_escape"}
+
+# Optional second argument is tmpdir to use
+TMPDIR=${2-"$TEST_TMPDIR/difftpl"}
+
+rm -rf $TMPDIR
+mkdir $TMPDIR || die "$LINENO: Can't make $TMPDIR"
+
+# Let's make some templates
+# ok1.tpl is valid HTML and has one correct modifier.
+echo '<a href="{{URL}}">{{USER:h}}</a>' > $TMPDIR/ok1.tpl
+
+# ok2.tpl is valid HTML and has one right and one wrong modifier.
+echo '<a href="{{URL:U=html}}">{{USER:j}}</a>' > $TMPDIR/ok2.tpl
+
+# ok3.tpl is valid HTML and has both wrong modifiers.
+echo '<a href="{{URL:h}}">{{USER:c}}</a>' > $TMPDIR/ok3.tpl
+
+# ok4.tpl is valid HTML and is auto-escaped.
+echo '{{%AUTOESCAPE context="HTML"}}' \
+     '<a href="{{URL}}">{{USER}}</a>' > $TMPDIR/ok4.tpl
+# bad1.tpl will fail auto-escape parsing.
+echo '{{%AUTOESCAPE context="HTML"}}<a href={{QC' > $TMPDIR/bad1.tpl
+
+# First, test commandline flags
+$DIFFTPL >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL with no args didn't give an error"
+$DIFFTPL --strip=STRIP_WHITESPACE >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL with no template didn't give an error"
+$DIFFTPL $TMPDIR/ok1.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL with only one template didn't give an error"
+$DIFFTPL -sFOO $TMPDIR/ok1.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL with bad strip didn't given an error"
+$DIFFTPL --strip=BLA $TMPDIR/ok1.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL with other bad strip didn't given an error"
+$DIFFTPL -h >/dev/null 2>&1 \
+   || die "$LINENO: $DIFFTPL -h failed"
+$DIFFTPL --help >/dev/null 2>&1 \
+   || die "$LINENO: $DIFFTPL --help failed"
+
+# Some weird (broken) shells leave the ending EOF in the here-document,
+# hence the grep.
+# Diff between ok1.tpl and ok4.tpl. No differences.
+expected_test1_verbose=`cat <<EOF | grep -v '^EOF$'
+[VERBOSE] ------ Diff of [$TMPDIR/ok1.tpl, $TMPDIR/ok4.tpl] ------
+[VERBOSE] Variables Found: Total=2; Diffs=0; NoMods=1
+EOF`
+
+expected_test1=`cat <<EOF | grep -v '^EOF$'
+EOF`
+
+# Diff between ok1.tpl and ok2.tpl. Expect one difference.
+expected_test2_verbose=`cat <<EOF | grep -v '^EOF$'
+[VERBOSE] ------ Diff of [$TMPDIR/ok1.tpl, $TMPDIR/ok2.tpl] ------
+Difference for variable USER -- :h vs. :j
+[VERBOSE] Variables Found: Total=2; Diffs=1; NoMods=1
+EOF`
+
+expected_test2=`cat <<EOF | grep -v '^EOF$'
+Difference for variable USER -- :h vs. :j
+EOF`
+
+# Diff between ok3.tpl and ok4.tpl. Expect two differences.
+expected_test3_verbose=`cat <<EOF |  grep -v '^EOF$'
+[VERBOSE] ------ Diff of [$TMPDIR/ok3.tpl, $TMPDIR/ok4.tpl] ------
+Difference for variable URL -- :h vs. :U=html
+Difference for variable USER -- :c vs. :h
+[VERBOSE] Variables Found: Total=2; Diffs=2; NoMods=0
+EOF`
+
+expected_test3=`cat <<EOF |  grep -v '^EOF$'
+Difference for variable URL -- :h vs. :U=html
+Difference for variable USER -- :c vs. :h
+EOF`
+
+# Diff between ok2.tpl and ok3.tpl. Expect two differences.
+expected_test4_verbose=`cat <<EOF |  grep -v '^EOF$'
+[VERBOSE] ------ Diff of [$TMPDIR/ok2.tpl, $TMPDIR/ok3.tpl] ------
+Difference for variable URL -- :U=html vs. :h
+Difference for variable USER -- :j vs. :c
+[VERBOSE] Variables Found: Total=2; Diffs=2; NoMods=0
+EOF`
+
+expected_test4=`cat <<EOF |  grep -v '^EOF$'
+Difference for variable URL -- :U=html vs. :h
+Difference for variable USER -- :j vs. :c
+EOF`
+
+
+# syntax-check these templates
+echo "TMPDIR is: $TMPDIR"
+$DIFFTPL $TMPDIR/ok1.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1 \
+   || die "$LINENO: $DIFFTPL gave error parsing identical templates"
+$DIFFTPL $TMPDIR/ok1.tpl $TMPDIR/bad1.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL gave no error parsing bad template"
+$DIFFTPL $TMPDIR/ok100.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $DIFFTPL gave no error parsing non-existent template"
+
+# Now try the same thing, but use template-root so we don't need absdirs
+$DIFFTPL --template_root=$TMPDIR ok2.tpl ok2.tpl >/dev/null 2>&1 \
+   || die "$LINENO: $DIFFTPL gave error parsing identical templates"
+
+# Diffing the same template produces exit code 0. Check with all Strip values.
+$DIFFTPL -sSTRIP_WHITESPACE $TMPDIR/ok1.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1
+[ $? = 0 ] || die "$LINENO: $DIFFTPL: wrong error-code on same template: $?"
+$DIFFTPL -sSTRIP_BLANK_LINES $TMPDIR/ok1.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1
+[ $? = 0 ] || die "$LINENO: $DIFFTPL: wrong error-code on same template: $?"
+$DIFFTPL -sDO_NOT_STRIP $TMPDIR/ok1.tpl $TMPDIR/ok1.tpl >/dev/null 2>&1
+[ $? = 0 ] || die "$LINENO: $DIFFTPL: wrong error-code on same template: $?"
+
+# Diffing templates with diff, should produce exit code 1.
+$DIFFTPL $TMPDIR/ok1.tpl $TMPDIR/ok2.tpl >/dev/null 2>&1
+[ $? = 1 ] || die "$LINENO: $DIFFTPL: wrong error-code on diff. templates: $?"
+
+# Diffing templates with failure, should produce exit code 1.
+$DIFFTPL $TMPDIR/ok1.tpl $TMPDIR/ok100.tpl >/dev/null 2>&1
+[ $? = 1 ] || die "$LINENO: $DIFFTPL: wrong error-code on failed template: $?"
+
+# If you use relative filenames, must first fix expected outputs.
+out=`$DIFFTPL -v $TMPDIR/ok1.tpl $TMPDIR/ok4.tpl 2>&1`
+[ "$out" != "$expected_test1_verbose" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test1_verbose: $out\n"
+out=`$DIFFTPL $TMPDIR/ok1.tpl $TMPDIR/ok4.tpl 2>&1`
+[ "$out" != "$expected_test1" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test1: $out\n"
+
+out=`$DIFFTPL -v $TMPDIR/ok1.tpl $TMPDIR/ok2.tpl 2>&1`
+[ "$out" != "$expected_test2_verbose" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test2_verbose: $out\n"
+out=`$DIFFTPL $TMPDIR/ok1.tpl $TMPDIR/ok2.tpl 2>&1`
+[ "$out" != "$expected_test2" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test2: $out\n"
+
+out=`$DIFFTPL -v $TMPDIR/ok3.tpl $TMPDIR/ok4.tpl 2>&1`
+[ "$out" != "$expected_test3_verbose" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test3_verbose: $out\n"
+out=`$DIFFTPL $TMPDIR/ok3.tpl $TMPDIR/ok4.tpl 2>&1`
+[ "$out" != "$expected_test3" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test3: $out\n"
+
+out=`$DIFFTPL -v $TMPDIR/ok2.tpl $TMPDIR/ok3.tpl 2>&1`
+[ "$out" != "$expected_test4_verbose" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test4_verbose: $out\n"
+out=`$DIFFTPL $TMPDIR/ok2.tpl $TMPDIR/ok3.tpl 2>&1`
+[ "$out" != "$expected_test4" ] &&\
+  die "$LINENO: $DIFFTPL: bad output for test4: $out\n"
+
+echo "PASSED"
diff --git a/src/tests/generate_fsm_c_test.c b/src/tests/generate_fsm_c_test.c
new file mode 100644
index 0000000..83ca730
--- /dev/null
+++ b/src/tests/generate_fsm_c_test.c
@@ -0,0 +1,53 @@
+/* Copyright (c) 2007, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ---
+ *
+ * Author: falmeida@google.com (Filipe Almeida)
+ *
+ * Validate that sample_fsm.c compiles.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "htmlparser/statemachine.h"
+
+enum states  {
+  STRINGPARSER_STATE_TEXT,
+  STRINGPARSER_STATE_STRING
+};
+
+#include "tests/htmlparser_testdata/sample_fsm.c"
+
+int main()
+{
+  (void)stringparser_states_internal_names;
+  (void)stringparser_state_transitions;
+  printf("DONE.\n");
+  exit(0);
+}
diff --git a/src/tests/generate_fsm_test.sh b/src/tests/generate_fsm_test.sh
new file mode 100755
index 0000000..6d26947
--- /dev/null
+++ b/src/tests/generate_fsm_test.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+#
+# Copyright (c) 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# ---
+#
+# Author: falmeida@google.com (Filipe Almeida)
+
+die() {
+  echo "Test failed: $@" 1>&2
+  exit 1
+}
+TEST_SRCDIR=${1:-$TEST_SRCDIR}
+TOOLS_DIR="$TEST_SRCDIR/src/htmlparser"
+TESTDATA_DIR="$TEST_SRCDIR/src/tests/htmlparser_testdata"
+
+# Find input files
+INPUT_FILE="$TESTDATA_DIR/sample_fsm.config"
+OUTPUT_FILE="$TESTDATA_DIR/sample_fsm.c"
+GENERATE_FSM="$TOOLS_DIR/generate_fsm.py"
+
+EXPECTED="`cat $OUTPUT_FILE`"
+if [ -z "$EXPECTED" ]; then die "Error reading $OUTPUT_FILE"; fi
+
+# Let's make sure the script works with python2.2 and above
+for PYTHON in "" "python2.2" "python2.3" "python2.4" "python2.5" "python2.6"; do
+  # Skip the versions of python that are not installed.
+  if [ -n "$PYTHON" ]; then
+    $PYTHON -h >/dev/null 2>/dev/null || continue
+  else    # use the python that's in the shebang line
+    SHEBANG_PYTHON=`head -n1 "$GENERATE_FSM" | tr -d '#!'`
+    # SHEBANG_PYTHON could be something like "env python" so don't quotify it
+    $SHEBANG_PYTHON -h >/dev/null 2>/dev/null || continue
+  fi
+  echo "-- Running $PYTHON $GENERATE_FSM $INPUT_FILE"
+  # The tr is to get rid of windows-style line endings (\r)
+  GENERATED="`$PYTHON $GENERATE_FSM $INPUT_FILE | tr -d '\015'`"
+  if [ -z "$GENERATED" ]; then die "Error running $GENERATE_FSM"; fi
+
+  if [ "$EXPECTED" != "$GENERATED" ]; then
+    echo "Test failed ($PYTHON $GENERATE_FSM $INPUT_FILE)" 1>&2
+    echo "-- EXPECTED --" 1>&2
+    echo "$EXPECTED" 1>&2
+    echo "-- GENERATED --"  1>&2
+    echo "$GENERATED" 1>&2
+    echo "--"
+    exit 1
+  fi
+done
+
+echo "PASS"
diff --git a/src/tests/htmlparser_cpp_test.cc b/src/tests/htmlparser_cpp_test.cc
new file mode 100644
index 0000000..a8bcf79
--- /dev/null
+++ b/src/tests/htmlparser_cpp_test.cc
@@ -0,0 +1,629 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// ---
+// Author: falmeida@google.com (Filipe Almeida)
+//
+// Verify at different points during HTML processing that the parser is in the
+// correct state.
+//
+// The annotated file consists of regular html blocks and html processing
+// instructions with a target name of "state" and a list of comma separated key
+// value pairs describing the expected state or invoking a parser method.
+// Example:
+//
+// <html><body><?state state=text, tag=body ?>
+//
+// For a more detailed explanation of the acceptable values please consult
+// htmlparser_cpp.h. Following is a list of the possible keys:
+//
+// state: Current parser state as returned by HtmlParser::state().
+//        Possible values: text, tag, attr, value, comment or error.
+// tag: Current tag name as returned by HtmlParser::tag()
+// attr: Current attribute name as returned by HtmlParser::attr()
+// attr_type: Current attribute type as returned by HtmlParser::attr_type()
+//            Possible values: none, regular, uri, js or style.
+// attr_quoted: True if the attribute is quoted, false if it's not.
+// in_js: True if currently processing javascript (either an attribute value
+//        that expects javascript, a script block or the parser being in
+//        MODE_JS)
+// js_quoted: True if inside a javascript string literal.
+// js_state: Current javascript state as returned by
+//           HtmlParser::javascript_state().
+//           Possible values: text, q, dq, regexp or comment.
+// in_css: True if currently inside a CSS section or attribute.
+// line_number: Integer value containing the current line count.
+// column_number: Integer value containing the current column count.
+// value_index: Integer value containing the current character index in the
+//              current value starting from 0.
+// is_url_start: True if if this is the first character of a url attribute.
+// reset: If true, resets the parser state to it's initial values.
+// reset_mode: Similar to reset but receives an argument that changes the
+//             parser mode into either mode html or mode js.
+// insert_text: Executes HtmlParser::InsertText() if the argument is true.
+
+#include "config_for_unittests.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <utility>
+#include <vector>
+#include <map>
+#include "htmlparser/htmlparser_cpp.h"
+#include "ctemplate/template_pathops.h"
+#include "base/util.h"
+
+#define FAIL()  EXPECT_TRUE(false)
+TEST_INIT  // Among other things, defines RUN_ALL_TESTS
+
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+using GOOGLE_NAMESPACE::PathJoin;
+
+namespace ctemplate_htmlparser {
+
+// Maximum file size limit.
+static const int kMaxFileSize = 1000000;
+
+static void ReadToString(const char* filename, string* s) {
+  const int bufsize = 8092;
+  char buffer[bufsize];
+  size_t n;
+  FILE* fp = fopen(filename, "rb");
+  if (!fp)  PFATAL(filename);
+  while ((n=fread(buffer, 1, bufsize, fp)) > 0) {
+    if (ferror(fp))  PFATAL(filename);
+    s->append(string(buffer, n));
+  }
+  fclose(fp);
+}
+
+class HtmlparserCppTest : public testing::Test {
+ protected:
+
+  typedef map<string, HtmlParser *> ContextMap;
+
+  // Structure that stores the mapping between an id and a name.
+  struct IdNameMap {
+    int id;
+    const char *name;
+  };
+
+  // Mapping between the enum and the string representation of the state.
+  static const struct IdNameMap kStateMap[];
+
+  // Mapping between the enum and the string representation of the javascript
+  // state.
+  static const struct IdNameMap kJavascriptStateMap[];
+
+  // Mapping between the enum and the string representation of the attribute
+  // type.
+  static const struct IdNameMap kAttributeTypeMap[];
+
+  // Mapping between the enum and the string representation of the reset mode.
+  static const struct IdNameMap kResetModeMap[];
+
+  // String that marks the start of an annotation.
+  static const char kDirectiveBegin[];
+
+  // String that marks the end of an annotation.
+  static const char kDirectiveEnd[];
+
+  // Count the number of lines in a string.
+  static int UpdateLines(const string &str, int line);
+
+  // Count the number of columns in a string.
+  static int UpdateColumns(const string &str, int column);
+
+  // Converts a string to a boolean.
+  static bool StringToBool(const string &value);
+
+  // Returns the name of the corresponding enum_id by consulting an array of
+  // type IdNameMap.
+  const char *IdToName(const struct IdNameMap *list, int enum_id);
+
+  // Returns the enum_id of the correspondent name by consulting an array of
+  // type IdNameMap.
+  int NameToId(const struct IdNameMap *list, const string &name);
+
+  // Reads the filename of an annotated html file and validates the
+  // annotations against the html parser state.
+  void ValidateFile(string filename);
+
+  // Validate an annotation string against the current parser state.
+  void ProcessAnnotation(const string &dir);
+
+  // Validate the parser state against the provided state.
+  void ValidateState(const string &tag);
+
+  // Validate the parser tag name against the provided tag name.
+  void ValidateTag(const string &tag);
+
+  // Validate the parser attribute name against the provided attribute name.
+  void ValidateAttribute(const string &attr);
+
+  // Validate the parser attribute value contents against the provided string.
+  void ValidateValue(const string &contents);
+
+  // Validate the parser attribute type against the provided attribute type.
+  void ValidateAttributeType(const string &attr);
+
+  // Validate the parser attribute quoted state against the provided
+  // boolean.
+  void ValidateAttributeQuoted(const string &quoted);
+
+  // Validates the parser in javascript state against the provided boolean.
+  void ValidateInJavascript(const string &quoted);
+
+  // Validate the current parser javascript quoted state against the provided
+  // boolean.
+  void ValidateJavascriptQuoted(const string &quoted);
+
+  // Validate the javascript parser state against the provided state.
+  void ValidateJavascriptState(const string &expected_state);
+
+  // Validates the parser css state against the provided boolean.
+  void ValidateInCss(const string &quoted);
+
+  // Validate the line count against the expected count.
+  void ValidateLine(const string &expected_line);
+
+  // Validate the line count against the expected count.
+  void ValidateColumn(const string &expected_column);
+
+  // Validate the current parser value index against the provided index.
+  void ValidateValueIndex(const string &value_index);
+
+  // Validate the parser is_url_start value against the provided one.
+  void ValidateIsUrlStart(const string &expected_is_url_start);
+
+  void SetUp() {
+    parser_.Reset();
+  }
+
+  void TearDown() {
+    // Delete all parser instances from the context map
+    for (ContextMap::iterator iter = contextMap.begin();
+        iter != contextMap.end(); ++iter) {
+      delete iter->second;
+    }
+    contextMap.clear();
+  }
+
+  // Map containing the registers where the parser context is saved.
+  ContextMap contextMap;
+
+  // Parser instance
+  HtmlParser parser_;
+
+  friend class Test_HtmlparserTest_TestFiles;
+};
+
+const char HtmlparserCppTest::kDirectiveBegin[] = "<?state";
+const char HtmlparserCppTest::kDirectiveEnd[] = "?>";
+
+const struct HtmlparserCppTest::IdNameMap
+             HtmlparserCppTest::kStateMap[] = {
+  { HtmlParser::STATE_TEXT,     "text" },
+  { HtmlParser::STATE_TAG,      "tag" },
+  { HtmlParser::STATE_ATTR,     "attr" },
+  { HtmlParser::STATE_VALUE,    "value" },
+  { HtmlParser::STATE_COMMENT,  "comment" },
+  { HtmlParser::STATE_JS_FILE,  "js_file" },
+  { HtmlParser::STATE_CSS_FILE, "css_file" },
+  { HtmlParser::STATE_ERROR,    "error" },
+  { 0, NULL }
+};
+
+const struct HtmlparserCppTest::IdNameMap
+             HtmlparserCppTest::kAttributeTypeMap[] = {
+  { HtmlParser::ATTR_NONE,    "none" },
+  { HtmlParser::ATTR_REGULAR, "regular" },
+  { HtmlParser::ATTR_URI,     "uri" },
+  { HtmlParser::ATTR_JS,      "js" },
+  { HtmlParser::ATTR_STYLE,   "style" },
+  { 0, NULL }
+};
+
+const struct HtmlparserCppTest::IdNameMap
+             HtmlparserCppTest::kJavascriptStateMap[] = {
+  { JavascriptParser::STATE_TEXT,    "text" },
+  { JavascriptParser::STATE_Q,       "q" },
+  { JavascriptParser::STATE_DQ,      "dq" },
+  { JavascriptParser::STATE_REGEXP,  "regexp" },
+  { JavascriptParser::STATE_COMMENT, "comment" },
+  { 0, NULL }
+};
+
+const struct HtmlparserCppTest::IdNameMap
+             HtmlparserCppTest::kResetModeMap[] = {
+  { HtmlParser::MODE_HTML,                "html" },
+  { HtmlParser::MODE_JS,                  "js" },
+  { HtmlParser::MODE_CSS,                 "css" },
+  { HtmlParser::MODE_HTML_IN_TAG,         "html_in_tag" },
+  { 0, NULL }
+};
+
+
+// Count the number of lines in a string.
+int HtmlparserCppTest::UpdateLines(const string &str, int line) {
+  int linecount = line;
+  for (string::size_type i = 0; i < str.length(); ++i) {
+    if (str[i] == '\n')
+      ++linecount;
+  }
+  return linecount;
+}
+
+// Count the number of columns in a string.
+int HtmlparserCppTest::UpdateColumns(const string &str, int column) {
+  // Number of bytes since the last newline.
+  size_t last_newline = str.rfind('\n');
+
+  // If no newline was found, we just sum up all the characters in the
+  // annotation.
+  if (last_newline == string::npos) {
+    return static_cast<int>(column + str.size() +
+                            strlen(kDirectiveBegin) + strlen(kDirectiveEnd));
+  // If a newline was found, the new column count becomes the number of
+  // characters after the last newline.
+  } else {
+    return static_cast<int>(str.size() + strlen(kDirectiveEnd) - last_newline);
+  }
+}
+
+
+// Converts a string to a boolean.
+bool HtmlparserCppTest::StringToBool(const string &value) {
+  if (strcasecmp(value.c_str(), "true") == 0) {
+    return true;
+  } else if (strcasecmp(value.c_str(), "false") == 0) {
+    return false;
+  } else {
+    LOG(FATAL) << "Unknown boolean value";
+  }
+}
+
+// Returns the name of the corresponding enum_id by consulting an array of
+// type IdNameMap.
+const char *HtmlparserCppTest::IdToName(const struct IdNameMap *list,
+                                        int enum_id) {
+  CHECK(list != NULL);
+  while (list->name) {
+    if (enum_id == list->id) {
+      return list->name;
+    }
+    list++;
+  }
+  LOG(FATAL) << "Unknown id";
+}
+
+// Returns the enum_id of the correspondent name by consulting an array of
+// type IdNameMap.
+int HtmlparserCppTest::NameToId(const struct IdNameMap *list,
+                                const string &name) {
+  CHECK(list != NULL);
+  while (list->name) {
+    if (name.compare(list->name) == 0) {
+      return list->id;
+    }
+    list++;
+  }
+  LOG(FATAL) << "Unknown name";
+}
+
+// Validate the parser state against the provided state.
+void HtmlparserCppTest::ValidateState(const string &expected_state) {
+  const char* parsed_state = IdToName(kStateMap, parser_.state());
+  EXPECT_TRUE(parsed_state != NULL);
+  EXPECT_TRUE(!expected_state.empty());
+  EXPECT_EQ(expected_state, string(parsed_state))
+      << "Unexpected state at line " << parser_.line_number();
+}
+
+// Validate the parser tag name against the provided tag name.
+void HtmlparserCppTest::ValidateTag(const string &expected_tag) {
+  EXPECT_TRUE(parser_.tag() != NULL);
+  EXPECT_TRUE(expected_tag == parser_.tag())
+      << "Unexpected attr tag name at line " << parser_.line_number();
+}
+
+// Validate the parser attribute name against the provided attribute name.
+void HtmlparserCppTest::ValidateAttribute(const string &expected_attr) {
+  EXPECT_TRUE(parser_.attribute() != NULL);
+  EXPECT_EQ(expected_attr, parser_.attribute())
+      << "Unexpected attr name value at line " << parser_.line_number();
+}
+
+// Validate the parser attribute value contents against the provided string.
+void HtmlparserCppTest::ValidateValue(const string &expected_value) {
+  EXPECT_TRUE(parser_.value() != NULL);
+  const string parsed_state(parser_.value());
+  EXPECT_EQ(expected_value, parsed_state)
+      << "Unexpected value at line " << parser_.line_number();
+}
+
+// Validate the parser attribute type against the provided attribute type.
+void HtmlparserCppTest::ValidateAttributeType(
+    const string &expected_attr_type) {
+  const char *parsed_attr_type = IdToName(kAttributeTypeMap,
+                                          parser_.AttributeType());
+  EXPECT_TRUE(parsed_attr_type != NULL);
+  EXPECT_TRUE(!expected_attr_type.empty());
+  EXPECT_EQ(expected_attr_type, string(parsed_attr_type))
+      << "Unexpected attr_type value at line " << parser_.line_number();
+}
+
+// Validate the parser attribute quoted state against the provided
+// boolean.
+void HtmlparserCppTest::ValidateAttributeQuoted(
+    const string &expected_attr_quoted) {
+  bool attr_quoted_bool = StringToBool(expected_attr_quoted);
+  EXPECT_EQ(attr_quoted_bool, parser_.IsAttributeQuoted())
+      << "Unexpected attr_quoted value at line " << parser_.line_number();
+}
+
+// Validates the parser in javascript state against the provided boolean.
+void HtmlparserCppTest::ValidateInJavascript(const string &expected_in_js) {
+  bool in_js_bool = StringToBool(expected_in_js);
+  EXPECT_EQ(in_js_bool, parser_.InJavascript())
+      << "Unexpected in_js value at line " << parser_.line_number();
+}
+
+// Validate the current parser javascript quoted state against the provided
+// boolean.
+void HtmlparserCppTest::ValidateJavascriptQuoted(
+    const string &expected_js_quoted) {
+  bool js_quoted_bool = StringToBool(expected_js_quoted);
+  EXPECT_EQ(js_quoted_bool, parser_.IsJavascriptQuoted())
+      << "Unexpected js_quoted value at line " << parser_.line_number();
+}
+
+// Validate the javascript parser state against the provided state.
+void HtmlparserCppTest::ValidateJavascriptState(const string &expected_state) {
+  const char* parsed_state = IdToName(kJavascriptStateMap,
+                                      parser_.javascript_state());
+  EXPECT_TRUE(parsed_state != NULL);
+  EXPECT_TRUE(!expected_state.empty());
+  EXPECT_EQ(expected_state, string(parsed_state))
+      << "Unexpected javascript state at line " << parser_.line_number();
+}
+
+// Validates the parser css state against the provided boolean.
+void HtmlparserCppTest::ValidateInCss(const string &expected_in_css) {
+  bool in_css_bool = StringToBool(expected_in_css);
+  EXPECT_EQ(in_css_bool, parser_.InCss())
+      << "Unexpected in_css value at line " << parser_.line_number();
+}
+
+// Validate the line count against the expected count.
+void HtmlparserCppTest::ValidateLine(const string &expected_line) {
+  int line;
+  CHECK(safe_strto32(expected_line, &line));
+  EXPECT_EQ(line, parser_.line_number())
+      << "Unexpected line count at line " << parser_.line_number();
+}
+
+// Validate the line count against the expected count.
+void HtmlparserCppTest::ValidateColumn(const string &expected_column) {
+  int column;
+  CHECK(safe_strto32(expected_column, &column));
+  EXPECT_EQ(column, parser_.column_number())
+      << "Unexpected column count at line " << parser_.line_number();
+}
+
+// Validate the current parser value index against the provided index.
+void HtmlparserCppTest::ValidateValueIndex(const string &expected_value_index) {
+  int index;
+  CHECK(safe_strto32(expected_value_index, &index));
+  EXPECT_EQ(index, parser_.ValueIndex())
+      << "Unexpected value_index value at line " << parser_.line_number();
+}
+
+// Validate the parser is_url_start value against the provided one.
+void HtmlparserCppTest::ValidateIsUrlStart(
+    const string &expected_is_url_start) {
+  bool is_url_start_bool = StringToBool(expected_is_url_start);
+  EXPECT_EQ(is_url_start_bool, parser_.IsUrlStart())
+      << "Unexpected is_url_start value at line " << parser_.line_number();
+}
+
+// Validate an annotation string against the current parser state.
+//
+// Split the annotation into a list of key value pairs and call the appropriate
+// handler for each pair.
+void HtmlparserCppTest::ProcessAnnotation(const string &annotation) {
+  vector< pair< string, string > > pairs;
+  SplitStringIntoKeyValuePairs(annotation, "=", ",", &pairs);
+
+  vector< pair< string, string > >::iterator iter;
+
+  iter = pairs.begin();
+  for (iter = pairs.begin(); iter != pairs.end(); ++iter) {
+    StripWhiteSpace(&iter->first);
+    StripWhiteSpace(&iter->second);
+
+    if (iter->first.compare("state") == 0) {
+      ValidateState(iter->second);
+    } else if (iter->first.compare("tag") == 0) {
+      ValidateTag(iter->second);
+    } else if (iter->first.compare("attr") == 0) {
+      ValidateAttribute(iter->second);
+    } else if (iter->first.compare("value") == 0) {
+      ValidateValue(iter->second);
+    } else if (iter->first.compare("attr_type") == 0) {
+      ValidateAttributeType(iter->second);
+    } else if (iter->first.compare("attr_quoted") == 0) {
+      ValidateAttributeQuoted(iter->second);
+    } else if (iter->first.compare("in_js") == 0) {
+      ValidateInJavascript(iter->second);
+    } else if (iter->first.compare("js_quoted") == 0) {
+      ValidateJavascriptQuoted(iter->second);
+    } else if (iter->first.compare("js_state") == 0) {
+      ValidateJavascriptState(iter->second);
+    } else if (iter->first.compare("in_css") == 0) {
+      ValidateInCss(iter->second);
+    } else if (iter->first.compare("line_number") == 0) {
+      ValidateLine(iter->second);
+    } else if (iter->first.compare("column_number") == 0) {
+      ValidateColumn(iter->second);
+    } else if (iter->first.compare("value_index") == 0) {
+      ValidateValueIndex(iter->second);
+    } else if (iter->first.compare("is_url_start") == 0) {
+      ValidateIsUrlStart(iter->second);
+    } else if (iter->first.compare("save_context") == 0) {
+      if (!contextMap.count(iter->second)) {
+        contextMap[iter->second] = new HtmlParser();
+      }
+      contextMap[iter->second]->CopyFrom(&parser_);
+    } else if (iter->first.compare("load_context") == 0) {
+      CHECK(contextMap.count(iter->second));
+      parser_.CopyFrom(contextMap[iter->second]);
+    } else if (iter->first.compare("reset") == 0) {
+      if (StringToBool(iter->second)) {
+        parser_.Reset();
+      }
+    } else if (iter->first.compare("reset_mode") == 0) {
+      HtmlParser::Mode mode =
+           static_cast<HtmlParser::Mode>(NameToId(kResetModeMap, iter->second));
+      parser_.ResetMode(mode);
+    } else if (iter->first.compare("insert_text") == 0) {
+      if (StringToBool(iter->second)) {
+        parser_.InsertText();
+      }
+    } else {
+      FAIL() << "Unknown test directive: " << iter->first;
+    }
+  }
+}
+
+// Validates an html annotated file against the parser state.
+//
+// It iterates over the html file splitting it into html blocks and annotation
+// blocks. It sends the html block to the parser and uses the annotation block
+// to validate the parser state.
+void HtmlparserCppTest::ValidateFile(string filename) {
+  // If TEMPLATE_ROOTDIR is set in the environment, it overrides the
+  // default of ".".  We use an env-var rather than argv because
+  // that's what automake supports most easily.
+  const char* template_rootdir = getenv("TEMPLATE_ROOTDIR");
+  if (template_rootdir == NULL)
+    template_rootdir = DEFAULT_TEMPLATE_ROOTDIR;   // probably "."
+  string dir = PathJoin(template_rootdir, "src");
+  dir = PathJoin(dir, "tests");
+  dir = PathJoin(dir, "htmlparser_testdata");
+  const string fullpath = PathJoin(dir, filename);
+  fprintf(stderr, "Validating %s", fullpath.c_str());
+  string buffer;
+  ReadToString(fullpath.c_str(), &buffer);
+
+  // Start of the current html block.
+  size_t start_html = 0;
+
+  // Start of the next annotation.
+  size_t start_annotation = buffer.find(kDirectiveBegin, 0);
+
+  // Ending of the current annotation.
+  size_t end_annotation = buffer.find(kDirectiveEnd, start_annotation);
+
+  while (start_annotation != string::npos) {
+    string html_block(buffer, start_html, start_annotation - start_html);
+    parser_.Parse(html_block);
+
+    start_annotation += strlen(kDirectiveBegin);
+
+    string annotation_block(buffer, start_annotation,
+                            end_annotation - start_annotation);
+    ProcessAnnotation(annotation_block);
+
+    // Update line and column count.
+    parser_.set_line_number(UpdateLines(annotation_block,
+                                        parser_.line_number()));
+    parser_.set_column_number(UpdateColumns(annotation_block,
+                                            parser_.column_number()));
+
+    start_html = end_annotation + strlen(kDirectiveEnd);
+    start_annotation = buffer.find(kDirectiveBegin, start_html);
+    end_annotation = buffer.find(kDirectiveEnd, start_annotation);
+
+    // Check for unclosed annotation.
+    CHECK(!(start_annotation != string::npos &&
+            end_annotation == string::npos));
+  }
+}
+
+static vector<string> g_filenames;
+#define TEST_FILE(testname, filename)                           \
+  struct Register_##testname {                                  \
+    Register_##testname() { g_filenames.push_back(filename); }  \
+  };                                                            \
+  static Register_##testname g_register_##testname
+
+TEST(HtmlparserTest, TestFiles) {
+  HtmlparserCppTest tester;
+  for (vector<string>::const_iterator it = g_filenames.begin();
+       it != g_filenames.end(); ++it) {
+    tester.SetUp();
+    tester.ValidateFile(*it);
+    tester.TearDown();
+  }
+}
+
+TEST_FILE(SimpleHtml, "simple.html");
+TEST_FILE(Comments, "comments.html");
+TEST_FILE(JavascriptBlock, "javascript_block.html");
+TEST_FILE(JavascriptAttribute, "javascript_attribute.html");
+TEST_FILE(JavascriptRegExp, "javascript_regexp.html");
+TEST_FILE(Tags, "tags.html");
+TEST_FILE(Context, "context.html");
+TEST_FILE(Reset, "reset.html");
+TEST_FILE(CData, "cdata.html");
+TEST_FILE(LineCount, "position.html");
+
+TEST(Htmlparser, Error) {
+  HtmlParser html;
+
+  EXPECT_EQ(html.GetErrorMessage(), (const char *)NULL);
+  EXPECT_EQ(html.Parse("<a href='http://www.google.com' ''>\n"),
+            HtmlParser::STATE_ERROR);
+
+  EXPECT_STREQ(html.GetErrorMessage(),
+               "Unexpected character '\\'' in state 'tag_space'");
+  html.Reset();
+  EXPECT_EQ(html.GetErrorMessage(), (const char *)NULL);
+}
+
+}  // namespace security_streamhtmlparser
+
+int main(int argc, char **argv) {
+
+  return RUN_ALL_TESTS();
+}
diff --git a/src/tests/htmlparser_testdata/cdata.html b/src/tests/htmlparser_testdata/cdata.html
new file mode 100644
index 0000000..817938b
--- /dev/null
+++ b/src/tests/htmlparser_testdata/cdata.html
@@ -0,0 +1,112 @@
+<html>
+<?state state=text, tag=html ?>
+
+  <head>
+    <?state state=text, tag=head ?>
+    <!-- Title element with markup -->
+    <title>
+      <?state state=text, tag=title ?>
+      <h1>
+        <?state state=text, tag=title ?>
+      </h1>
+      <!--
+        <?state state=text, tag=title ?>
+        </title>
+        <?state state=text, tag=title ?>
+      -->
+      <?state state=text, tag=title ?>
+    </title>
+    <?state state=text ?>
+
+    <!-- Style element with attributes -->
+    <style a=b>
+      <b><?state state=text, tag=style, in_js=false, in_css=true?></b>
+    </style>
+    <?state in_css=false?>
+  </head>
+<body>
+<?state state=text, in_js=false ?>
+  <!-- PCDATA nested block -->
+  <b>
+    <?state state=text, tag=b ?>
+    <i>
+      <?state state=text, tag=i ?>
+    </i>
+    <?state state=text ?>
+  </b>
+  <?state state=text ?>
+
+  <!-- Textarea element with space at the end of the closing tag -->
+  <textarea>
+  <?state state=text, tag=textarea ?>
+    <b>
+    <?state state=text, tag=textarea ?>
+      <i>
+      <?state state=text, tag=textarea, in_css=false ?>
+      <!--
+        <?state state=text, tag=textarea ?>
+        </textarea>
+        <?state state=text, tag=textarea ?>
+      -->
+      </i>
+      <?state state=text, tag=textarea ?>
+    </b>
+    <?state state=text, tag=textarea ?>
+  </textarea >
+
+<?state state=text ?>
+
+  <!-- script tag with other tags inside -->
+  <script>
+    document.write("
+      <?state in_js=true, js_quoted=true, tag=script ?>
+      <style>
+        .none { display:none }
+      </style>
+      <?state in_js=true, js_quoted=true ?>
+    ");
+    <?state in_js=true, js_quoted=false ?>
+  </script>
+
+  <?state in_js=false ?>
+
+  <!-- script tag with a backslash quoted script tag -->
+  <script>
+    <?state in_js=true, js_quoted=false ?>
+    document.body.innerHTML = '<script><\/script>'
+    <?state in_js=true, js_quoted=false ?>
+  </script>
+
+  <?state in_js=false ?>
+
+  <!-- </script> appearing between javascript comments -->
+  <script>
+  <!--
+    <?state in_js=true, js_quoted=false ?>
+    document.body.innerHTML = '<script></script>'
+    <?state in_js=true, js_quoted=false ?>
+  -->
+  </script>
+
+  <?state in_js=false ?>
+
+  <!-- Closing script with an extra space at the end of the tag. Some browsers
+  ignore this tag and some browsers honour it. We honour it. -->
+  <script>
+    <?state in_js=true, js_quoted=false ?>
+    document.body.innerHTML = '<script><\/script>'
+    <?state in_js=true, js_quoted=false ?>
+  </script >
+
+  <script>
+    <?state in_js=true, js_quoted=false ?>
+    </script%>
+    <?state in_js=true, js_quoted=false ?>
+  </script >
+
+  <?state in_js=false ?>
+
+</body>
+<?state in_js=false ?>
+</html>
+
diff --git a/src/tests/htmlparser_testdata/comments.html b/src/tests/htmlparser_testdata/comments.html
new file mode 100644
index 0000000..391f3f0
--- /dev/null
+++ b/src/tests/htmlparser_testdata/comments.html
@@ -0,0 +1,61 @@
+<!-- Tests for HTML comments and cdata escaping text spans. -->
+<html>
+
+<body>
+
+<?state state=text, tag=body ?>
+
+<!-- HTML doctype declaration -->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+<?state state=text, tag=body?>
+"http://www.w3.org/TR/html4/strict.dtd">
+<?state state=text, tag=body ?>
+
+<!-- Regular HTML comment -->
+<!-- <?state state=comment, tag=body ?> -->
+<?state state=text, tag=body ?>
+
+<!-- HTML comment with tags -->
+<!-- > -> </b> <a href="<?state state=comment, tag=body ?>"></a>-->
+<?state state=text, tag=body ?>
+
+<!-- Should not be interpreted as an SGML comment -->
+<?state state=text, tag=body ?>
+<!-- -- -->
+<?state state=text, tag=body ?>
+
+<!-- -- Sync back the SGML comment for editors who parse SGML comments
+(ie: vim) -->
+<?state state=text, tag=body ?>
+
+<!-- Multiple dashes at the end. -->
+<!----- <?state state=comment, tag=body ?> --><?state state=text, tag=body ?>
+<!----- <?state state=comment, tag=body ?> ---><?state state=text, tag=body ?>
+<!----- <?state state=comment, tag=body ?> ----><?state state=text, tag=body ?>
+<!----- <?state state=comment, tag=body ?> -----><?state state=text, tag=body ?>
+
+<!-- Some more misc tests -->
+<!-- test <?state state=comment?> --><?state state=text?> test test --><?state state=text?>
+<!-- test -> test test --><?state state=text?>
+<!-- test test='--><?state state=text?>'
+<!----><?state state=text?>
+<!-----><?state state=text?>
+
+<!-- Make sure the double dash sequence is not interpreted as an SGML comment
+by introducing a legit postfix decrement operator -->
+<?state state=text, in_js=false ?>
+<script>
+<!--
+<?state state=text, in_js=true ?>
+
+var x = 1;
+x--;
+<?state state=text, in_js=true ?>
+-->
+</script>
+<?state state=text, in_js=false ?>
+
+</body>
+
+</html>
+<?state state=text ?>
diff --git a/src/tests/htmlparser_testdata/context.html b/src/tests/htmlparser_testdata/context.html
new file mode 100644
index 0000000..aaaaa46
--- /dev/null
+++ b/src/tests/htmlparser_testdata/context.html
@@ -0,0 +1,79 @@
+<!-- Tests for CopyFrom() -->
+<html>
+  <body>
+    <?state save_context=body?>
+    <?state tag=body?>
+    <h1>
+      <?state save_context=h1?>
+      <?state tag=h1?>
+      <?state load_context=body?>
+    <?state tag=body?>
+
+    <a href="http://www.google.com<?state save_context=href?>"></a>
+
+    <script>
+      <?state save_context=js?>
+      var x ='<?state save_context=js_str_literal?>
+      <?state load_context=href?><?state state=value,
+                                         tag=a,
+                                         attr=href,
+                                         in_js=false,
+                                         value=http://www.google.com?>
+    <?state load_context=js_str_literal?>
+    <?state state=text,
+            tag=script,
+            in_js=true,
+            js_quoted=true?>';
+
+    // Regexp handling
+    var expression = 10 / <?state save_context=js_expression?> / <?state save_context=js_regexp?> /;
+
+    <?state load_context=js_expression?><?state js_state=text?>
+    <?state load_context=js_regexp?><?state js_state=regexp?> /;
+    <?state js_state=text?>
+
+  </script>
+  <?state in_js=false?>
+  <?state load_context=js?>
+  <?state tag=script, js_state=text, in_js=true?>
+  </script>
+
+  <!-- html encoded script attribute -->
+  <a onclick="alert(&#39;<?state save_context=onclick_str_literal?>'"></a>
+  <?state in_js=false?>
+  <?state load_context=onclick_str_literal?><?state state=value,
+                                                    tag=a,
+                                                    attr=onclick,
+                                                    attr_type=js,
+                                                    in_js=true,
+                                                    js_quoted=true?>'">
+    <?state state=text, tag=a?>
+  </a>
+
+<!-- ResetMode() tests -->
+
+<?state reset_mode=css?>
+<?state in_css=true?>
+<?state state=css_file?>
+<?state save_context=mode_css?>
+
+<?state reset_mode=html?>
+<?state state=text?>
+<?state in_css=false?>
+<?state load_context=mode_css?>
+<?state in_css=true?>
+<?state state=css_file?>
+
+<?state reset_mode=html_in_tag?>blah=<?state save_context=in_tag?>
+<?state load_context=onclick_str_literal?><?state state=value,
+                                                  tag=a,
+                                                  attr=onclick,
+                                                  attr_type=js,
+                                                  in_js=true,
+                                                  js_quoted=true?>'">
+<?state load_context=in_tag?>
+<?state attr=blah?>xpto<?state value=xpto?>
+
+
+  </body>
+</html>
diff --git a/src/tests/htmlparser_testdata/google.html b/src/tests/htmlparser_testdata/google.html
new file mode 100644
index 0000000..45dddd8
--- /dev/null
+++ b/src/tests/htmlparser_testdata/google.html
@@ -0,0 +1,3 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><style>body,td,a,p,.h{font-family:arial,sans-serif}.h{font-size:20px}.h{color:#3366cc}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}#gbar{height:22px;padding-left:2px}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}#gbi,#gbs{background:#fff;left:0;position:absolute;top:24px;visibility:hidden;z-index:1000}#gbi{border:1px solid;border-color:#c9d7f1 #36c #36c #a2bae7;z-index:1001}#guser{padding-bottom:7px !important}#gbar,#guser{font-size:13px;padding-top:1px !important}@media all{.gb1,.gb3{height:22px;margin-right:.73em;vertical-align:top}#gbar{float:left}}.gb2{display:block;padding:.2em .5em}a.gb1,a.gb2,a.gb3{color:#00c !important}.gb2,.gb3{text-decoration:none}a.gb2:hover{background:#36c;color:#fff !important}</style><script>window.google={kEI:"jigHScf6BKDwswP7-eSsAw",kEXPI:"17259,19016",kHL:"en"};
+function sf(){document.f.q.focus()}
+window.gbar={};(function(){var b=window.gbar,f,h;b.qs=function(a){var c=window.encodeURIComponent&&(document.forms[0].q||"").value;if(c)a.href=a.href.replace(/([?&])q=[^&]*|$/,function(i,g){return(g||"&")+"q="+encodeURIComponent(c)})};function j(a,c){a.visibility=h?"hidden":"visible";a.left=c+"px"}b.tg=function(a){a=a||window.event;var c=0,i,g=window.navExtra,d=document.getElementById("gbi"),e=a.target||a.srcElement;a.cancelBubble=true;if(!f){f=document.createElement(Array.every||window.createPopup?"iframe":"div");f.frameBorder="0";f.src="#";d.parentNode.appendChild(f).id="gbs";if(g)for(i in g)d.insertBefore(g[i],d.firstChild).className="gb2";document.onclick=b.close}if(e.className!="gb3")e=e.parentNode;do c+=e.offsetLeft;while(e=e.offsetParent);j(d.style,c);f.style.width=d.offsetWidth+"px";f.style.height=d.offsetHeight+"px";j(f.style,c);h=!h};b.close=function(a){h&&b.tg(a)}})();</script></head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload="sf();if(document.images){new Image().src='/images/nav_logo3.png'}" topmargin=3 marginheight=3><div id=gbar><nobr><b class=gb1>Web</b> <a href="http://images.google.com/imghp?hl=en&tab=wi" onclick=gbar.qs(this) class=gb1>Images</a> <a href="http://maps.google.com/maps?hl=en&tab=wl" onclick=gbar.qs(this) class=gb1>Maps</a> <a href="http://news.google.com/nwshp?hl=en&tab=wn" onclick=gbar.qs(this) class=gb1>News</a> <a href="http://www.google.com/prdhp?hl=en&tab=wf" onclick=gbar.qs(this) class=gb1>Shopping</a> <a href="http://mail.google.com/mail/?hl=en&tab=wm" class=gb1>Gmail</a> <a href="http://www.google.com/intl/en/options/" onclick="this.blur();gbar.tg(event);return !1" class=gb3><u>more</u> <small>&#9660;</small></a><div id=gbi> <a href="http://video.google.com/?hl=en&tab=wv" onclick=gbar.qs(this) class=gb2>Video</a> <a href="http://groups.google.com/grphp?hl=en&tab=wg" onclick=gbar.qs(this) class=gb2>Groups</a> <a href="http://books.google.com/bkshp?hl=en&tab=wp" onclick=gbar.qs(this) class=gb2>Books</a> <a href="http://scholar.google.com/schhp?hl=en&tab=ws" onclick=gbar.qs(this) class=gb2>Scholar</a> <a href="http://finance.google.com/finance?hl=en&tab=we" onclick=gbar.qs(this) class=gb2>Finance</a> <a href="http://blogsearch.google.com/?hl=en&tab=wb" onclick=gbar.qs(this) class=gb2>Blogs</a> <div class=gb2><div class=gbd></div></div> <a href="http://www.youtube.com/?hl=en&tab=w1" onclick=gbar.qs(this) class=gb2>YouTube</a> <a href="http://www.google.com/calendar/render?hl=en&tab=wc" class=gb2>Calendar</a> <a href="http://picasaweb.google.com/home?hl=en&tab=wq" onclick=gbar.qs(this) class=gb2>Photos</a> <a href="http://docs.google.com/?hl=en&tab=wo" class=gb2>Documents</a> <a href="http://www.google.com/reader/view/?hl=en&tab=wy" class=gb2>Reader</a> <a href="http://sites.google.com/?hl=en&tab=w3" class=gb2>Sites</a> <div class=gb2><div class=gbd></div></div> <a href="http://www.google.com/intl/en/options/" class=gb2>even more &raquo;</a></div> </nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div><div align=right id=guser style="font-size:84%;padding:0 0 4px" width=100%><nobr><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Den%26source%3Diglk&usg=AFQjCNFA18XPfgb7dKnXfKz7x7g1GDH1tg">iGoogle</a> | <a href="https://www.google.com/accounts/Login?continue=http://www.google.com/&hl=en">Sign in</a></nobr></div><center><br clear=all id=lgpd><img alt="Google" height=110 src="/intl/en_ALL/images/logo.gif" width=276><br><br><form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top><td width=25%>&nbsp;</td><td align=center nowrap><input name=hl type=hidden value=en><input type=hidden name=ie value="ISO-8859-1"><input autocomplete="off" maxlength=2048 name=q size=55 title="Google Search" value=""><br><input name=btnG type=submit value="Google Search"><input name=btnI type=submit value="I'm Feeling Lucky"></td><td nowrap width=25%><font size=-2>&nbsp;&nbsp;<a href=/advanced_search?hl=en>Advanced Search</a><br>&nbsp;&nbsp;<a href=/preferences?hl=en>Preferences</a><br>&nbsp;&nbsp;<a href=/language_tools?hl=en>Language Tools</a></font></td></tr></table></form><br><font size=-1><font color=red>New!</font> The G1 phone is on sale now. <a href="/aclk?sa=L&ai=BuJQcgigHSbvbCqDUsAPGm6X7DvPUz3en34zVCcHZnNkT0IYDEAEYASDBVDgAUJL0-Mb8_____wFgyQY&num=1&sig=AGiWqtxZNijZyCsNtIwkfSx_S1WSW0Uh8A&q=http://www.google.com/intl/en_us/mobile/android/hpp.html">Learn more</a>.</font><br><br><br><font size=-1><a href="/intl/en/ads/">Advertising&nbsp;Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/about.html">About Google</a></font><p><font size=-2>&copy;2008 - <a href="/intl/en/privacy.html">Privacy</a></font></p></center></body><script>google.y={first:[]};window.setTimeout(function(){var xjs=document.createElement('script');xjs.src='/extern_js/f/CgJlbhICdXMgACswCjgILCswGDgDLA/Vh5nhw3Xn6A.js';document.getElementsByTagName('head')[0].appendChild(xjs)},0);google.y.first.push(function(){google.ac.i(document.f,document.f.q,'','')})</script></html>
\ No newline at end of file
diff --git a/src/tests/htmlparser_testdata/javascript_attribute.html b/src/tests/htmlparser_testdata/javascript_attribute.html
new file mode 100644
index 0000000..db096f0
--- /dev/null
+++ b/src/tests/htmlparser_testdata/javascript_attribute.html
@@ -0,0 +1,27 @@
+<html>
+<body>
+
+<a onclick="alert(&#39;<?state state=value, tag=a, attr=onclick, attr_type=js,
+in_js=true, js_quoted=true?> x&#x27;) &; &a; &x;/*blah <?state state=value,
+tag=a, attr=onclick, attr_type=js, in_js=true ?> */ "></a>
+
+<?state state=text, in_js=false ?>
+
+<a onmouseover='alert(document.domain<?state state=value, tag=a,
+attr=onmouseover, attr_type=js, in_js=true ?>)'>test</a>
+
+<?state state=text, in_js=false ?>
+
+<a onmouseover="">test</a>
+
+<?state state=text, in_js=false ?>
+
+<a onclick="<?state in_js=true, js_quoted=false?>">test</a>
+<?state state=text, in_js=false ?>
+
+<a onclick="'<?state in_js=true, js_quoted=true?>">test</a>
+<?state state=text, in_js=false ?>
+
+</body>
+</html>
+<?state state=text ?><?state state=text ?>
diff --git a/src/tests/htmlparser_testdata/javascript_block.html b/src/tests/htmlparser_testdata/javascript_block.html
new file mode 100644
index 0000000..539c1a6
--- /dev/null
+++ b/src/tests/htmlparser_testdata/javascript_block.html
@@ -0,0 +1,50 @@
+<html>
+<body>
+
+<script>
+
+x < 1;
+
+<?state state=text, tag=script, in_js=true ?>
+
+</script>
+
+<?state state=text?>
+
+<script>
+//<!--
+var x = 1;
+<?state state=text, tag=script, in_js=true ?>
+//--> </script>
+
+<?state state=text?>
+
+<script> //<!--
+var x = 1;
+<?state state=text, tag=script, in_js=true ?>
+</script>
+<?state state=text, tag=script, in_js=true ?>
+//--> </script>
+
+<?state state=text?>
+
+<script>
+<!--
+var x = 1;
+<?state state=text, tag=script, in_js=true ?>
+</script>
+<?state state=text, tag=script, in_js=true ?>
+-->
+</script>
+
+<?state state=text?>
+
+<script><?state tag=script, in_js=true?> </script><?state in_js=false?>
+<script><?state tag=script, in_js=true, js_quoted=false?></script><?state in_js=false?>
+<script>'<?state tag=script, in_js=true, js_quoted=true?></script><?state in_js=false?>
+<script>"<?state tag=script, in_js=true, js_quoted=true?></script><?state in_js=false?>
+
+</body>
+</html>
+<?state state=text ?>
+<?state state=text ?>
diff --git a/src/tests/htmlparser_testdata/javascript_regexp.html b/src/tests/htmlparser_testdata/javascript_regexp.html
new file mode 100644
index 0000000..7c1f88d
--- /dev/null
+++ b/src/tests/htmlparser_testdata/javascript_regexp.html
@@ -0,0 +1,171 @@
+<html>
+<body>
+
+
+<script>
+
+// General regular expression literal synching tests.
+
+var regexp = /x'/;
+<?state state=text, in_js=true, js_quoted=false?>
+
+var string = '<?state state=text, in_js=true, js_quoted=true?>';
+<?state state=text, in_js=true, js_quoted=false?>
+
+var op = 1 / 2;
+var string2 = '<?state state=text, in_js=true, js_quoted=true?>';
+<?state state=text, in_js=true, js_quoted=false?>
+
+return /x'/;
+<?state state=text, in_js=true, js_quoted=false?>
+
+
+// General regular expression state tests
+
+var regexp = / <?state js_state=regexp?> /; <?state js_state=text?>
+
+var a = /"hello/.exec("<?state state=text, in_js=true, js_quoted=true ?>");
+var a = /"hello"/.exec("<?state state=text, in_js=true, js_quoted=true ?>");
+
+var expression = 10 / <?state js_state=text?> / <?state js_state=regexp?> /;
+
+<?state js_state=text?>
+
+var expression2 = / <?state js_state=regexp?> /;
+
+if (window.frames.length < /\d+<?state js_state=regexp?>/.exec(<?state js_state=text?>)[0]) {
+  alert(/ '" <?state js_state=regexp?>/.exec(<?state js_state=text?>)/);
+  var quoted_string = "<?state js_state=dq?>" <?state js_state=text?>;
+}
+
+switch(/ <?state js_state=regexp?> /) { <?state js_state=text?>
+  case / <?state js_state=regexp?> /: <?state js_state=text?>
+    break;
+  case / \/<?state js_state=regexp?> /: <?state js_state=text?>
+    break;
+}
+
+delete / <?state js_state=regexp?> x / <?state js_state=text?>;
+id / <?state js_state=text?> x / <?state js_state=text?>;
+
+function test(/ <?state js_state=regexp?> /) {
+  return / <?state js_state=regexp?> /.exec(<?state js_state=text?>);
+}
+
+function test2(/ <?state js_state=regexp?> /, <?state js_state=text?>) {
+  return / '"<?state js_state=regexp?> /.exec(<?state js_state=text?>);
+}
+
+var a = "/<?state js_state=dq?>"/<?state js_state=text?>;
+
+test in / <?state js_state=regexp?>/;
+min / <?state js_state=text?>;
+IN / <?state js_state=text?>;
+
+3.. /<?state js_state=text?>/;
+0x3./<?state js_state=text?>/;
+
+// Escaping in regular expressions
+
+var a = / blah\/<?state js_state=regexp?>/<?state js_state=text?>,
+/\//<?state js_state=text?>,
+/\/*/<?state js_state=text?> /**/ <?state js_state=text?>,
+
+// Bracket expressions
+var a = [/[/] <?state js_state=regexp?> / <?state js_state=text?>,
+var a = /[/\]/ <?state js_state=regexp?> ]/ <?state js_state=text?>,
+var a = /[/\\]/ <?state js_state=text?>];
+
+/* Unary incremented/decremented variable, followed by a division. */
+
+var w = w++ / 1 <?state js_state=text?>;
+var w = w-- / 1 <?state js_state=text?>;
+
+/* Division after array acessor. */
+var test = xpto[2] / <?state js_state=text?>;
+
+/* Division after parenthesis expression. */
+var test = (2 + 2) / <?state js_state=text?>;
+
+/* Division with comments before the the previous token. */
+var test = x/* test *// <?state js_state=text?>;
+var test = x /* test *// <?state js_state=text?>;
+var test = x/* test */ / <?state js_state=text?>;
+var test = x /* test */ / <?state js_state=text?>;
+var test = x /* test */
+/ <?state js_state=text?>;
+
+var test = x // test
+/ <?state js_state=text?>;
+
+var test = x // test
+ / <?state js_state=text?>;
+
+var test = x // test
+
+/ <?state js_state=text?>;
+
+/* Regexp with multi line comment before the the previous token. */
+var test =/* test *// <?state js_state=regexp?> /;
+var test = /* test *// <?state js_state=regexp?> /;
+var test = /* test *// <?state js_state=regexp?> /;
+var test = /* test */ / <?state js_state=regexp?> /;
+var test = /* test */
+/ <?state js_state=regexp?> /;
+
+var test = // test
+/ <?state js_state=regexp?> /;
+
+var test = // test
+ / <?state js_state=regexp?> /;
+
+var test = // test
+
+/ <?state js_state=regexp?> /;
+
+
+/* Semicolon insertion after a code block */
+function() {} / <?state js_state=regexp?>/
+
+/****************************************************************************
+  Tests that won't pass right now due to design or implementation choices.
+*/
+
+/* Division after a regular expression.
+
+var test = <?nopstate js_state=text?>
+/ <?nopstate js_state=regexp?>
+/ <?nopstate js_state=text?>
+/ <?nopstate js_state=text?>
+/ <?nopstate js_state=regexp?>
+/ <?nopstate js_state=text?>;
+
+*/
+
+/* Division of an object literal
+
+{
+ a: 1,
+ b : 2
+} / <?nopstate js_state=text?>/
+
+*/
+
+/* Unary increment and decrement of regular expressions.
+
+var w = ++/ <?nopstate js_state=regexp?>/i;
+var x = --/ <?nopstate js_state=regexp?>/i
+
+*/
+
+
+</script>
+
+<script>
+
+/ <?state js_state=regexp?> /;
+
+</script>
+
+</body>
+</html>
diff --git a/src/tests/htmlparser_testdata/position.html b/src/tests/htmlparser_testdata/position.html
new file mode 100644
index 0000000..120ca4e
--- /dev/null
+++ b/src/tests/htmlparser_testdata/position.html
@@ -0,0 +1,33 @@
+<?state line_number=1?>
+<?state line_number=2?>
+<html>
+<?state column_number=1?>
+  <body><?state column_number=9?>
+    <?state line_number=6?><?state column_number=28?>
+    <?state
+
+      line_number=7
+    ?><?state column_number=7?>
+    <?state line_number=11?><?state column_number=29?>
+  </body>
+  <?state line_number=13?>
+
+
+
+<?state column_number=1?>
+ <?state column_number=2?>
+  <?state column_number=3?>
+
+
+
+
+
+<a href="http://ww.google.com" onclick="var x=<?state column_number=47?>">
+</a>
+
+<img src="http://www.google.com" onerror="var w = &qu<?state column_number=54?>ot;test&quot;">
+
+
+
+  <?state line_number=32?>
+</html>
diff --git a/src/tests/htmlparser_testdata/reset.html b/src/tests/htmlparser_testdata/reset.html
new file mode 100644
index 0000000..cd0d070
--- /dev/null
+++ b/src/tests/htmlparser_testdata/reset.html
@@ -0,0 +1,31 @@
+<html>
+  <body>
+    <?state state=text, attr_type=none?>
+    <b font="<?state state=value, tag=b, attr=font, attr_quoted=true,
+      in_js=false, attr_type=regular ?>
+<?state reset=true ?>
+<?state state=text, attr_type=none ?>
+<b <?state state=tag, tag=b ?>
+<?state reset_mode=js ?>
+<?state state=js_file?>
+var unquoted =<?state js_quoted=false, in_js=true ?>;
+var single_quoted ='<?state js_quoted=true, in_js=true ?>';
+var unquoted =<?state js_quoted=false, in_js=true ?>;
+<?state reset_mode=html_in_tag?>blah=<?state attr=blah?>xpto<?state value=xpto?>
+test<?state state=attr?>
+<?state reset_mode=html_in_tag?>
+test="test123<?state attr=test, value=test123?>">
+<?state state=text?>
+<?state reset_mode=css?>
+<?state in_css=true?>
+<?state state=css_file?>
+
+<a href="<?state in_css=true?>"></style><?state in_css=true?>
+
+<123 <script><?state in_css=true?>
+
+<?state reset_mode=html?>
+<?state in_css=false?>
+    <?state state=text, attr_type=none?>
+    <b font="<?state state=value, tag=b, attr=font, attr_quoted=true,
+      in_js=false, attr_type=regular ?>
diff --git a/src/tests/htmlparser_testdata/sample_fsm.c b/src/tests/htmlparser_testdata/sample_fsm.c
new file mode 100644
index 0000000..ed85c5f
--- /dev/null
+++ b/src/tests/htmlparser_testdata/sample_fsm.c
@@ -0,0 +1,802 @@
+/* Parses C style strings
+ * Auto generated by generate_fsm.py. Please do not edit.
+ */
+#define STRINGPARSER_NUM_STATES 4
+enum stringparser_state_internal_enum {
+  STRINGPARSER_STATE_INT_TEXT,
+  STRINGPARSER_STATE_INT_STRING,
+  STRINGPARSER_STATE_INT_STRING_ESCAPE
+};
+
+static const int stringparser_states_external[] = {
+  STRINGPARSER_STATE_TEXT,
+  STRINGPARSER_STATE_STRING,
+  STRINGPARSER_STATE_STRING
+};
+
+static const char * stringparser_states_internal_names[] = {
+  "text",
+  "string",
+  "string_escape"
+};
+
+static const int stringparser_transition_row_text[] = {
+      /* '\x00' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x01' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x02' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x03' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x04' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x05' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x06' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x07' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x08' */ STRINGPARSER_STATE_INT_TEXT,
+      /*   '\t' */ STRINGPARSER_STATE_INT_TEXT,
+      /*   '\n' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x0b' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x0c' */ STRINGPARSER_STATE_INT_TEXT,
+      /*   '\r' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x0e' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x0f' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x10' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x11' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x12' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x13' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x14' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x15' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x16' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x17' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x18' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x19' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x1a' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x1b' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x1c' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x1d' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x1e' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x1f' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    ' ' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '!' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '"' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '#' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '$' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '%' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '&' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    "'" */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '(' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    ')' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '*' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '+' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    ',' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '-' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '.' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '/' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '0' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '1' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '2' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '3' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '4' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '5' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '6' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '7' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '8' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '9' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    ':' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    ';' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '<' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '=' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '>' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '?' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '@' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'A' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'B' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'C' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'D' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'E' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'F' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'G' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'H' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'I' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'J' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'K' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'L' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'M' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'N' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'O' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'P' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'Q' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'R' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'S' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'T' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'U' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'V' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'W' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'X' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'Y' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'Z' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '[' */ STRINGPARSER_STATE_INT_TEXT,
+      /*   '\\' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ']' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '^' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '_' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '`' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'a' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'b' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'c' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'd' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'e' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'f' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'g' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'h' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'i' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'j' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'k' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'l' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'm' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'n' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'o' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'p' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'q' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'r' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    's' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    't' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'u' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'v' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'w' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'x' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'y' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    'z' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '{' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '|' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '}' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '~' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x7f' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x80' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x81' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x82' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x83' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x84' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x85' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x86' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x87' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x88' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x89' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x8a' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x8b' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x8c' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x8d' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x8e' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x8f' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x90' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x91' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x92' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x93' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x94' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x95' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x96' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x97' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x98' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x99' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x9a' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x9b' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x9c' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x9d' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x9e' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\x9f' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa0' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa1' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa2' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa3' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa4' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa5' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa6' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa7' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa8' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xa9' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xaa' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xab' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xac' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xad' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xae' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xaf' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb0' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb1' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb2' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb3' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb4' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb5' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb6' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb7' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb8' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xb9' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xba' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xbb' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xbc' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xbd' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xbe' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xbf' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc0' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc1' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc2' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc3' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc4' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc5' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc6' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc7' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc8' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xc9' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xca' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xcb' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xcc' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xcd' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xce' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xcf' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd0' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd1' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd2' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd3' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd4' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd5' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd6' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd7' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd8' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xd9' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xda' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xdb' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xdc' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xdd' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xde' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xdf' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe0' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe1' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe2' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe3' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe4' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe5' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe6' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe7' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe8' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xe9' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xea' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xeb' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xec' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xed' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xee' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xef' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf0' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf1' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf2' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf3' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf4' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf5' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf6' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf7' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf8' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xf9' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xfa' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xfb' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xfc' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xfd' */ STRINGPARSER_STATE_INT_TEXT,
+      /* '\xfe' */ STRINGPARSER_STATE_INT_TEXT
+};
+
+static const int stringparser_transition_row_string[] = {
+      /* '\x00' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x01' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x02' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x03' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x04' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x05' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x06' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x07' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x08' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\t' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\n' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0c' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\r' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x10' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x11' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x12' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x13' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x14' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x15' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x16' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x17' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x18' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x19' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1a' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1c' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1d' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1f' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ' ' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '!' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '"' */ STRINGPARSER_STATE_INT_TEXT,
+      /*    '#' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '$' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '%' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '&' */ STRINGPARSER_STATE_INT_STRING,
+      /*    "'" */ STRINGPARSER_STATE_INT_STRING,
+      /*    '(' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ')' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '*' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '+' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ',' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '-' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '.' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '/' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '0' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '1' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '2' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '3' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '4' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '5' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '6' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '7' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '8' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '9' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ':' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ';' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '<' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '=' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '>' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '?' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '@' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'A' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'B' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'C' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'D' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'E' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'F' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'G' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'H' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'I' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'J' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'K' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'L' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'M' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'N' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'O' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'P' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'Q' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'R' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'S' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'T' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'U' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'V' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'W' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'X' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'Y' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'Z' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '[' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\\' */ STRINGPARSER_STATE_INT_STRING_ESCAPE,
+      /*    ']' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '^' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '_' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '`' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'a' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'b' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'c' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'd' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'e' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'f' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'g' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'h' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'i' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'j' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'k' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'l' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'm' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'n' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'o' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'p' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'q' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'r' */ STRINGPARSER_STATE_INT_STRING,
+      /*    's' */ STRINGPARSER_STATE_INT_STRING,
+      /*    't' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'u' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'v' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'w' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'x' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'y' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'z' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '{' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '|' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '}' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '~' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x7f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x80' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x81' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x82' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x83' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x84' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x85' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x86' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x87' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x88' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x89' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8a' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8c' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8d' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x90' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x91' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x92' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x93' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x94' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x95' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x96' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x97' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x98' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x99' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9a' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9c' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9d' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xaa' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xab' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xac' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xad' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xae' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xaf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xba' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbe' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xca' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xce' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xda' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xde' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xea' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xeb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xec' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xed' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xee' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xef' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfa' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfe' */ STRINGPARSER_STATE_INT_STRING
+};
+
+static const int stringparser_transition_row_string_escape[] = {
+      /* '\x00' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x01' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x02' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x03' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x04' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x05' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x06' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x07' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x08' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\t' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\n' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0c' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\r' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x0f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x10' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x11' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x12' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x13' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x14' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x15' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x16' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x17' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x18' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x19' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1a' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1c' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1d' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x1f' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ' ' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '!' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '"' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '#' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '$' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '%' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '&' */ STRINGPARSER_STATE_INT_STRING,
+      /*    "'" */ STRINGPARSER_STATE_INT_STRING,
+      /*    '(' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ')' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '*' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '+' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ',' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '-' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '.' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '/' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '0' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '1' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '2' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '3' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '4' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '5' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '6' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '7' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '8' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '9' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ':' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ';' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '<' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '=' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '>' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '?' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '@' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'A' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'B' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'C' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'D' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'E' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'F' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'G' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'H' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'I' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'J' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'K' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'L' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'M' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'N' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'O' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'P' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'Q' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'R' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'S' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'T' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'U' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'V' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'W' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'X' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'Y' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'Z' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '[' */ STRINGPARSER_STATE_INT_STRING,
+      /*   '\\' */ STRINGPARSER_STATE_INT_STRING,
+      /*    ']' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '^' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '_' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '`' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'a' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'b' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'c' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'd' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'e' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'f' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'g' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'h' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'i' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'j' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'k' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'l' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'm' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'n' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'o' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'p' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'q' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'r' */ STRINGPARSER_STATE_INT_STRING,
+      /*    's' */ STRINGPARSER_STATE_INT_STRING,
+      /*    't' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'u' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'v' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'w' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'x' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'y' */ STRINGPARSER_STATE_INT_STRING,
+      /*    'z' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '{' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '|' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '}' */ STRINGPARSER_STATE_INT_STRING,
+      /*    '~' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x7f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x80' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x81' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x82' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x83' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x84' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x85' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x86' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x87' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x88' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x89' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8a' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8c' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8d' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x8f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x90' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x91' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x92' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x93' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x94' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x95' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x96' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x97' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x98' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x99' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9a' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9b' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9c' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9d' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9e' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\x9f' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xa9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xaa' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xab' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xac' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xad' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xae' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xaf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xb9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xba' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbe' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xbf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xc9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xca' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xce' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xcf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xd9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xda' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xde' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xdf' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xe9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xea' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xeb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xec' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xed' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xee' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xef' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf0' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf1' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf2' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf3' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf4' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf5' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf6' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf7' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf8' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xf9' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfa' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfb' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfc' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfd' */ STRINGPARSER_STATE_INT_STRING,
+      /* '\xfe' */ STRINGPARSER_STATE_INT_STRING
+};
+
+static const int * stringparser_state_transitions[] = {
+  stringparser_transition_row_text,
+  stringparser_transition_row_string,
+  stringparser_transition_row_string_escape
+};
+
diff --git a/src/tests/htmlparser_testdata/sample_fsm.config b/src/tests/htmlparser_testdata/sample_fsm.config
new file mode 100644
index 0000000..df66e69
--- /dev/null
+++ b/src/tests/htmlparser_testdata/sample_fsm.config
@@ -0,0 +1,64 @@
+# Copyright (c) 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# ---
+#
+# Author: falmeida@google.com (Filipe Almeida)
+
+name = 'stringparser'
+
+comment = 'Parses C style strings'
+
+condition('dq', '\\"'),
+condition('backslash', '\\\\'),
+condition('default', '[:default:]')
+
+# Outside a string
+state(name = 'text',
+      external = 'text',
+      transitions = [
+        ['dq', 'string'],
+        ['default', 'text']
+      ])
+
+# String literal
+state(name = 'string',
+      external = 'string',
+      transitions = [
+        ['backslash', 'string_escape'],
+        ['dq', 'text'],
+        ['default', 'string']
+      ])
+
+# Escaped character in a string literal. Ignore the next character
+state(name = 'string_escape',
+      external = 'string',
+      transitions = [
+        ['default', 'string']
+      ])
+
diff --git a/src/tests/htmlparser_testdata/simple.html b/src/tests/htmlparser_testdata/simple.html
new file mode 100644
index 0000000..555928f
--- /dev/null
+++ b/src/tests/htmlparser_testdata/simple.html
@@ -0,0 +1,26 @@
+<html>
+  <body>
+    <?state state=text,tag=body?>
+    <a href="<?state state=value,tag=a?>">test</a>
+
+    <test test123=<?state state=value, tag=test, attr=test123,
+    attr_type=regular ?>>
+
+    <?state state=text?>
+
+    <body blah='<?state state=value, tag=body, attr=blah, attr_type=regular
+      ?>'>
+
+      <style>
+        <?state in_css=true?>
+      </style>
+      <?state in_css=false?>
+
+      <h1 onclick="<?state state=value, tag=h1, attr=onclick, attr_type=js,
+        in_js=true ?>" style="<?state in_css=true?>" <?state in_css=false?>>
+        <?state state=text, tag=h1?>
+      </h1>
+
+
+  </body>
+</html>
diff --git a/src/tests/htmlparser_testdata/tags.html b/src/tests/htmlparser_testdata/tags.html
new file mode 100644
index 0000000..1caf68d
--- /dev/null
+++ b/src/tests/htmlparser_testdata/tags.html
@@ -0,0 +1,214 @@
+<html>
+
+<body blah='<?state state=value, tag=body, attr=blah, attr_type=regular,
+attr_quoted=true ?>'>
+
+<?state state=text, tag=body ?>
+<a href=<?state state=value, tag=a, attr=href, attr_type=uri ?>><?state state=text, tag=a ?></a>
+<a href=
+  "<?state state=value, tag=a, attr=href, attr_type=uri, attr_quoted=true ?>"></a>
+
+<a href=<?state state=value, tag=a, attr=href, attr_type=uri ?> blah=x></a>
+<a href=
+  "<?state state=value, tag=a, attr=href, attr_type=uri ?>" blah=x></a>
+
+<a href=
+  <?state state=value, tag=a, attr=href, attr_type=uri, attr_quoted=false ?> blah=x></a>
+
+<a href><?state state=text, tag=a ?></a>
+
+<a href=x<?state state=value, tag=a, attr=href, attr_type=uri ?> <?state state=tag, tag=a ?>></a>
+
+<a href =<?state state=value, tag=a, attr=href, attr_type=uri ?>></a>
+<a href
+=<?state state=value, tag=a, attr=href, attr_type=uri ?>></a>
+<a href
+  =<?state state=value, tag=a, attr=href, attr_type=uri ?>></a>
+
+<?state state=text?>
+
+<b font=<?state state=value, value_index=0?>></b>
+<b font=x<?state state=value, value_index=1?>></b>
+<b font='<?state state=value, value_index=0?>'></b>
+<b font='x<?state state=value, value_index=1?>'></b>
+
+<!-- XML Processing instruction -->
+
+<?example <?state state=text?> <a href=<?state state=text?>></a
+  <script>
+  <?state state=text, in_js=false?>
+  </script>
+?>
+
+<a href=http://www.google.com/<?state state=value, tag=a, attr=href, attr_type=uri ?>?q=tt<?state state=value, tag=a, attr=href, attr_type=uri ?>>test</a>
+
+<!-- Test javascript url handling -->
+<a href="test<?state value=test, in_js=false ?>">test</a>
+<a href="javascript<?state value=javascript, in_js=false ?>">test</a>
+<a href="javascript:<?state value=javascript:, in_js=false ?>">test</a>
+<a href="javascript:alert('<?state in_js=false ?>">test</a>
+<a href="http:<?state value=http:, in_js=false ?>">test</a>
+<a href="http://www.google.com"
+   alt="javascript:<?state value=javascript:, in_js=false ?>">test</a>
+
+<!-- Test calls to  TemplateDirective() -->
+<b font=<?state state=value?>
+   color<?state state=value?>></b>
+
+<b font=<?state state=value?><?state insert_text=true?>
+   color<?state state=attr?>></b>
+
+<b font="<?state state=value?><?state insert_text=true?>
+   color<?state state=value?>"></b>
+
+<a href=
+  <?state state=value?><?state insert_text=true?> alt<?state state=attr?>>
+  link
+</a>
+
+<b font=<?state state=value?>><?state state=text, tag=b?></b>
+
+<!-- Large invalid HTML entity -->
+<a onclick="&testtesttesttesttesttesttesttesttesttesttesttest;"
+   href="http://www.google.com/"></a>
+
+<!-- URI attributes.  The attribute list can be found in
+     htmlparser.c:is_uri_attribute() -->
+<a target="<?state attr_type=regular?>"></a>
+<!-- -->
+<form action="<?state attr_type=uri?>"></form>
+<applet archive="<?state attr_type=uri?>"></applet>
+<blockquote cite="<?state attr_type=uri?>"></blockquote>
+<object classid="<?state attr_type=uri?>"></object>
+<object codebase="<?state attr_type=uri?>"></object>
+<object data="<?state attr_type=uri?>"></object>
+<img dynsrc="<?state attr_type=uri?>"></img>
+<a href="<?state attr_type=uri?>"></a>
+<img longdesc="<?state attr_type=uri?>"></img>
+<img src="<?state attr_type=uri?>"></img>
+<img usemap="<?state attr_type=uri?>"></img>
+<!-- -->
+<form style="x" action="<?state attr_type=uri?>"></form>
+<applet style="x" archive="<?state attr_type=uri?>"></applet>
+<blockquote style="x" cite="<?state attr_type=uri?>"></blockquote>
+<object style="x" classid="<?state attr_type=uri?>"></object>
+<object style="x" codebase="<?state attr_type=uri?>"></object>
+<object style="x" data="<?state attr_type=uri?>"></object>
+<img style="x" dynsrc="<?state attr_type=uri?>"></img>
+<a style="x" href="<?state attr_type=uri?>"></a>
+<img style="x" longdesc="<?state attr_type=uri?>"></img>
+<img style="x" src="<?state attr_type=uri?>"></img>
+<img style="x" usemap="<?state attr_type=uri?>"></img>
+<!-- -->
+<img alt="<?state attr_type=regular?>"></a>
+
+
+<!-- Style attributes as returned by htmlparser.c:is_style_attribute() -->
+<a target="<?state attr_type=regular?>"></a>
+<!-- -->
+<b style="<?state attr_type=style?>"></b>
+<!-- -->
+<a target="<?state attr_type=regular?>"></a>
+
+<!-- Big attribute value. We can't do prefix checking right now so we can't
+     validate the contents of the value here, although statemachine_test.c has
+     a test for that. -->
+
+<a href="http://www.google.com/"
+   alt="01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        01234567890123456789012345678901234567890123456789
+        <?state state=value, attr_quoted=true, tag=a, attr=alt?>"></a>
+
+<?state state=text?>
+
+<!-- is_url_start tests -->
+
+<a href="<?state is_url_start=true?>"></a>
+<a href="http://<?state is_url_start=false?>"></a>
+<a href="http://www.google.com?q=<?state is_url_start=false?>"></a>
+<b font="<?state is_url_start=false?>"></b>
+<b font="http://www.google.com?q=<?state is_url_start=false?>"></b>
+<?state is_url_start=false?>
+
+<!-- <?state is_url_start=false?> -->
+
+<!-- Tag opening tests -->
+
+<a <?state state=tag?>></a><?state state=text?>
+<br <?state state=tag?>></br><?state state=text?>
+< br <?state state=text?>></br><?state state=text?>
+<< <?state state=text?>><?state state=text?>
+<  <?state state=text?> alt="<?state state=text?>">
+</blah <?state state=tag?>><?state state=text?>
+<<i<?state state=tag?>><?state state=text?></i>
+
+
+<!-- We do allow numbers to open html tags, which is not how most browsers
+behave. We still test this anyway. -->
+<0 <?state state=tag?>><?state state=text?>
+<1 <?state state=tag?>><?state state=text?>
+
+<!-- meta redirect url tests. -->
+<meta http-equiv="refresh" content="5;URL=<?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content="10;URL=<?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content="5 ;URL=<?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content=" 5 ;URL=<?state attr_type=uri, is_url_start=true?>">
+<?state attr_type=none, is_url_start=false?>
+<meta http-equiv="refresh" content=" 5 ;    url   =   <?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content="5;Url=<?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content="5;UrL=<?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content="5;uRL=<?state attr_type=uri, is_url_start=true?>">
+<?state attr_type=none, is_url_start=false?>
+<meta http-equiv="refresh" content="5;uRL=http://<?state attr_type=uri, is_url_start=false?>">
+<meta http-equiv="refresh" content="5 ; URL=http://www.google.com/<?state attr_type=uri, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;URL=/<?state attr_type=uri, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;URL=../<?state attr_type=uri, is_url_start=false?>">
+<meta http-equiv="refresh" content="             123456789    ;    url    =  ../<?state attr_type=uri, is_url_start=false?>">
+
+<!-- Quoted url's -->
+<meta http-equiv="refresh" content="5;URL = '<?state attr_type=uri, is_url_start=true?>">
+<meta http-equiv="refresh" content='5;URL = "<?state attr_type=uri, is_url_start=true?>"'>
+<meta http-equiv="refresh" content="5;URL = ' <?state attr_type=uri, is_url_start=false?>">
+<meta http-equiv="refresh" content='5;URL = " <?state attr_type=uri, is_url_start=false?>"'>
+
+<?state attr_type=none, is_url_start=false?>
+
+<meta http-equiv="refresh" content="5x;URL=<?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="<?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="5<?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;<?state attr_type=regular, is_url_start=false?>">
+<?state attr_type=none, is_url_start=false?>
+<meta http-equiv="refresh" content="5;U<?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;UR<?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;URL<?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;URL <?state attr_type=regular, is_url_start=false?>">
+<?state attr_type=none, is_url_start=false?>
+<meta http-equiv="refresh" content="5x;URL= <?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="5;UR L <?state attr_type=regular, is_url_start=false?>">
+<meta http-equiv="refresh" content="URL = <?state attr_type=regular, is_url_start=false?>">
+
+<meta http-equiv="refresh" content="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <?state attr_type=regular?>">
+
+<span a:type="<?state state=value, attr=a:type?>"
+  a:abc.abc="<?state state=value, attr=a:abc.abc?>"
+  b:a.b.c.d.e.f=<?state state=value, attr=b:a.b.c.d.e.f?>>
+
+<tag.test>
+<?state state=text, tag=tag.test?>
+</tag.test>
+
+<!-- Tests regarding our specific implementation -->
+<meta content="5;URL=<?state attr_type=uri, is_url_start=true?>">
+
+</body>
+
+</html>
+<?state state=text ?>
diff --git a/src/tests/make_tpl_varnames_h_unittest.sh b/src/tests/make_tpl_varnames_h_unittest.sh
new file mode 100755
index 0000000..5d541cf
--- /dev/null
+++ b/src/tests/make_tpl_varnames_h_unittest.sh
@@ -0,0 +1,239 @@
+#!/bin/sh
+
+# Copyright (c) 2006, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# ---
+# Author: csilvers@google.com (Craig Silverstein)
+#
+
+die() {
+    echo "Test failed: $@" 1>&2
+    exit 1
+}
+
+MAKETPL=${1-"$TEST_SRCDIR/make_tpl_varnames_h"}
+
+# Optional second argument is tmpdir to use
+TMPDIR=${2-"$TEST_TMPDIR/maketpl"}
+
+rm -rf $TMPDIR
+mkdir $TMPDIR || die "$LINENO: Can't make $TMPDIR"
+
+# Let's make some templates: three that are ok, and three that are not
+echo '<a href={{QCHAR}}{{HREF}}{{QCHAR}} {{PARAMS}}>' > $TMPDIR/ok1.tpl
+echo '<img {{#ATTRIBUTES}}{{ATTRIBUTE}}{{/ATTRIBUTES}}>' > $TMPDIR/ok2.tpl
+echo '<html><head><title>{{TITLE}}</title></head></html>' > $TMPDIR/ok3.tpl
+echo '<a href={{QCHAR}}{{HREF}}{{QC' > $TMPDIR/bad1.tpl
+echo '<img {{#ATTRIBUTES}}{{ATTRIBUTE}}>' > $TMPDIR/bad2.tpl
+echo '<html><head><title>{{TITLE?}}</title></head></html>' > $TMPDIR/bad3.tpl
+
+# We'll make some templates with modifiers as well.
+echo '<a href={{HREF:h}} {{PARAMS}}>' > $TMPDIR/ok4.tpl
+echo '<a href={{HREF:html_escape_with_arg=url}} {{PARAMS}}>' > $TMPDIR/ok5.tpl
+echo '<a href={{HREF:x-custom-modifier}} {{PARAMS}}>' > $TMPDIR/ok6.tpl
+echo '<a href={{HREF:x-custom-modifier=arg}} {{PARAMS}}>' > $TMPDIR/ok7.tpl
+echo '<a href={{HREF:x-custom-modifier=}} {{PARAMS}}>' > $TMPDIR/ok8.tpl
+
+
+# First, test commandline flags
+$MAKETPL >/dev/null 2>&1 \
+   && die "$LINENO: $MAKETPL with no args didn't give an error"
+$MAKETPL -o$TMPDIR/foo >/dev/null 2>&1 \
+   && die "$LINENO: $MAKETPL with no template didn't give an error"
+$MAKETPL -h >/dev/null 2>&1 \
+   || die "$LINENO: $MAKETPL -h failed"
+$MAKETPL --help >/dev/null 2>&1 \
+   || die "$LINENO: $MAKETPL --help failed"
+$MAKETPL --nonsense >/dev/null 2>&1 \
+   && die "$LINENO: $MAKETPL --nonsense didn't give an error"
+$MAKETPL -f$TMPDIR/bar.h >/dev/null 2>&1 \
+   && die "$LINENO: $MAKETPL -f with no templates didn't give an error"
+
+# Some weird (broken) shells leave the ending EOF in the here-document,
+# hence the grep.
+expected_ok1=`cat <<EOF | grep -v '^EOF$'
+#ifndef %%%OUTPUT_NAME%%%
+#define %%%OUTPUT_NAME%%%
+
+#include <ctemplate/template_string.h>
+static const ::ctemplate::StaticTemplateString ko_QCHAR = STS_INIT_WITH_HASH(ko_QCHAR, "QCHAR", 13739615363438531061ULL);
+static const ::ctemplate::StaticTemplateString ko_HREF = STS_INIT_WITH_HASH(ko_HREF, "HREF", 4441707909033668369ULL);
+static const ::ctemplate::StaticTemplateString ko_PARAMS = STS_INIT_WITH_HASH(ko_PARAMS, "PARAMS", 10755877064288701757ULL);
+
+#endif  // %%%OUTPUT_NAME%%%
+EOF`
+
+expected_ok2=`cat <<EOF | grep -v '^EOF$'
+#ifndef %%%OUTPUT_NAME%%%
+#define %%%OUTPUT_NAME%%%
+
+#include <ctemplate/template_string.h>
+static const ::ctemplate::StaticTemplateString ko_ATTRIBUTES = STS_INIT_WITH_HASH(ko_ATTRIBUTES, "ATTRIBUTES", 11813232524653503831ULL);
+static const ::ctemplate::StaticTemplateString ko_ATTRIBUTE = STS_INIT_WITH_HASH(ko_ATTRIBUTE, "ATTRIBUTE", 14959290143384361001ULL);
+
+#endif  // %%%OUTPUT_NAME%%%
+EOF`
+
+expected_ok3=`cat <<EOF | grep -v '^EOF$'
+#ifndef %%%OUTPUT_NAME%%%
+#define %%%OUTPUT_NAME%%%
+
+#include <ctemplate/template_string.h>
+static const ::ctemplate::StaticTemplateString ko_TITLE = STS_INIT_WITH_HASH(ko_TITLE, "TITLE", 8931122033088041025ULL);
+
+#endif  // %%%OUTPUT_NAME%%%
+EOF`
+
+expected_ok4=`cat <<EOF | grep -v '^EOF$'
+#ifndef %%%OUTPUT_NAME%%%
+#define %%%OUTPUT_NAME%%%
+
+#include <ctemplate/template_string.h>
+static const ::ctemplate::StaticTemplateString ko_HREF = STS_INIT_WITH_HASH(ko_HREF, "HREF", 4441707909033668369ULL);
+static const ::ctemplate::StaticTemplateString ko_PARAMS = STS_INIT_WITH_HASH(ko_PARAMS, "PARAMS", 10755877064288701757ULL);
+
+#endif  // %%%OUTPUT_NAME%%%
+EOF`
+
+expected_ok5=`echo "$expected_ok4" | sed s/ok4/ok5/g`
+expected_ok6=`echo "$expected_ok4" | sed s/ok4/ok6/g`
+expected_ok7=`echo "$expected_ok4" | sed s/ok4/ok7/g`
+expected_ok8=`echo "$expected_ok4" | sed s/ok4/ok8/g`
+
+# When -f (--output-file) is used on ok1.tpl and ok2.tpl
+# Note that there are no variables in common in these two templates.
+# All should be returned.
+expected_ok1and2=`cat <<EOF | grep -v '^EOF$'
+#ifndef %%%OUTPUT_NAME%%%
+#define %%%OUTPUT_NAME%%%
+
+#include <ctemplate/template_string.h>
+static const ::ctemplate::StaticTemplateString ko_QCHAR = STS_INIT_WITH_HASH(ko_QCHAR, "QCHAR", 13739615363438531061ULL);
+static const ::ctemplate::StaticTemplateString ko_HREF = STS_INIT_WITH_HASH(ko_HREF, "HREF", 4441707909033668369ULL);
+static const ::ctemplate::StaticTemplateString ko_PARAMS = STS_INIT_WITH_HASH(ko_PARAMS, "PARAMS", 10755877064288701757ULL);
+static const ::ctemplate::StaticTemplateString ko_ATTRIBUTES = STS_INIT_WITH_HASH(ko_ATTRIBUTES, "ATTRIBUTES", 11813232524653503831ULL);
+static const ::ctemplate::StaticTemplateString ko_ATTRIBUTE = STS_INIT_WITH_HASH(ko_ATTRIBUTE, "ATTRIBUTE", 14959290143384361001ULL);
+
+#endif  // %%%OUTPUT_NAME%%%
+EOF`
+
+# When -f (--output-file) is used on ok1.tpl and ok4.tpl
+# Note that both variables in ok4.tpl will be duplicates and hence not returned.
+expected_ok1and4=`echo "$expected_ok1" | sed s/ok1/ok1and4/g`
+
+# Suppress unimportant aspects of the make_tpl_varnames_h output.
+Cleanse() {
+  # Replace the file name guard with %%%OUTPUT_NAME%%% so we can use
+  # the same expected_ok* variables for different file names.
+  # Note that we only append 'H_' to the end of the string, instead
+  # of '_H_'.  This is because the first call to 'tr' is already
+  # adding a '_' at the end of the converted $1 (due to the newline
+  # emitted by echo).
+  n="`basename $1 | sed -e 's/[^0-9a-zA-Z]/_/g' | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`"
+  grep -v '^//' "$1" | sed -e "s:TPL_.*${n}_H_:%%%OUTPUT_NAME%%%:" > "$1.cleansed"
+}
+
+# syntax-check these templates
+$MAKETPL -n $TMPDIR/ok1.tpl $TMPDIR/ok2.tpl $TMPDIR/ok3.tpl >/dev/null 2>&1 \
+   || die "$LINENO: $MAKETPL gave error parsing good templates"
+$MAKETPL -n $TMPDIR/ok1.tpl $TMPDIR/ok2.tpl $TMPDIR/bad3.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $MAKETPL gave no error parsing bad template"
+$MAKETPL -n $TMPDIR/ok1.tpl $TMPDIR/ok2.tpl $TMPDIR/ok100.tpl >/dev/null 2>&1 \
+   && die "$LINENO: $MAKETPL gave no error parsing non-existent template"
+
+# Now try the same thing, but use template-root so we don't need absdirs
+$MAKETPL -n --template_dir=$TMPDIR ok1.tpl ok2.tpl ok3.tpl >/dev/null 2>&1 \
+   || die "$LINENO: $MAKETPL gave error parsing good templates"
+
+# Parse the templates.  Bad one in the middle should be ignored.
+$MAKETPL --header_dir=$TMPDIR $TMPDIR/ok1.tpl $TMPDIR/bad2.tpl $TMPDIR/ok3.tpl >/dev/null 2>&1
+[ $? = 1 ] || die "$LINENO: $MAKETPL gave wrong error-code parsing 1 bad template: $?"
+Cleanse "$TMPDIR/ok1.tpl.varnames.h"
+echo "$expected_ok1" | diff - "$TMPDIR/ok1.tpl.varnames.h.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok1 output correctly"
+[ -f "$TMPDIR/bad2.tpl.varnames.h" ] \
+   && die "$LINENO: $MAKETPL >did< make bad2 output"
+Cleanse "$TMPDIR/ok3.tpl.varnames.h"
+echo "$expected_ok3" | diff - "$TMPDIR/ok3.tpl.varnames.h.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok3 output correctly"
+
+# Now try the same but with a different suffix.  Try an alternate -t/-o form too.
+# Also test not being able to output the file for some reason.
+$MAKETPL -t$TMPDIR -o$TMPDIR -s.out ok1.tpl bad2.tpl ok3.tpl >/dev/null 2>&1
+[ $? = 1 ] || die "$LINENO: $MAKETPL gave wrong error-code parsing 1 bad template: $?"
+Cleanse "$TMPDIR/ok1.tpl.out"
+echo "$expected_ok1" | diff - "$TMPDIR/ok1.tpl.out.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok1 output correctly"
+[ -f "$TMPDIR/bad2.tpl.out" ] && die "$LINENO: $MAKETPL >did< make bad2 output"
+Cleanse "$TMPDIR/ok3.tpl.out"
+echo "$expected_ok3" | diff - "$TMPDIR/ok3.tpl.out.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok3 output correctly"
+
+# Verify that -f generates the requested output file:
+# -f with one file
+$MAKETPL -t$TMPDIR -f$TMPDIR/ok1.h ok1.tpl >/dev/null 2>&1
+Cleanse "$TMPDIR/ok1.h"
+echo "$expected_ok1" | diff - "$TMPDIR/ok1.h.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok1.h output correctly"
+# -f with two files - no common template variables
+$MAKETPL -t$TMPDIR -f$TMPDIR/ok1and2.h ok1.tpl ok2.tpl >/dev/null 2>&1
+Cleanse "$TMPDIR/ok1and2.h"
+echo "$expected_ok1and2" | diff - "$TMPDIR/ok1and2.h.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok1and2.h output correctly"
+# -f with two files - two common template variables
+$MAKETPL -t$TMPDIR -f$TMPDIR/ok1and4.h ok1.tpl ok4.tpl >/dev/null 2>&1
+Cleanse "$TMPDIR/ok1and4.h"
+echo "$expected_ok1and4" | diff - "$TMPDIR/ok1and4.h.cleansed" \
+   || die "$LINENO: $MAKETPL didn't make ok1and4.h output correctly"
+# -f with a bad file should not produce an output
+$MAKETPL -t$TMPDIR -f$TMPDIR/bar.h ok1.tpl bad1.tpl >/dev/null 2>&1 \
+  && die "$LINENO: $MAKETPL -f gave no error parsing bad template"
+
+# Verify we don't give any output iff everything works, with -q flag.
+# Also test using a different output dir.  Also, test *every* ok template.
+mkdir $TMPDIR/output
+# Normally I'd do {1,2,3,4,...}, but solaris sh doesn't understand that syntax
+out=`$MAKETPL -q -t$TMPDIR -o$TMPDIR/output -s"#" \
+     ok1.tpl ok2.tpl ok3.tpl ok4.tpl ok5.tpl ok6.tpl ok7.tpl ok8.tpl \
+     2>&1`
+[ -z "$out" ] || die "$LINENO: $MAKETPL -q wasn't so quiet: '$out'"
+for i in 1 2 3 4 5 6 7 8; do
+  Cleanse "$TMPDIR/output/ok$i.tpl#"
+  eval "echo \"\$expected_ok$i\"" | diff - "$TMPDIR/output/ok$i.tpl#.cleansed" \
+     || die "$LINENO: $MAKETPL didn't make ok$i output correctly"
+done
+
+out=`$MAKETPL -q --outputfile_suffix=2 $TMPDIR/bad{1,2,3}.tpl 2>&1`
+[ -z "$out" ] && die "$LINENO: $MAKETPL -q was too quiet"
+for i in 1 2 3; do
+  [ -f "$TMPDIR/output/bad$i.tpl2" ] && die "$LINENO: $MAKETPL made bad$i output"
+done
+
+echo "PASSED"
diff --git a/src/tests/statemachine_test.c b/src/tests/statemachine_test.c
new file mode 100644
index 0000000..33481d6
--- /dev/null
+++ b/src/tests/statemachine_test.c
@@ -0,0 +1,365 @@
+/* Copyright (c) 2007, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "htmlparser/statemachine.h"
+
+enum {
+  SIMPLE_STATE_A,
+  SIMPLE_STATE_B,
+  SIMPLE_STATE_C,
+  SIMPLE_STATE_D,
+  SIMPLE_STATE_ERROR_TEST
+};
+
+/* Include the test state machine definition. */
+#include "tests/statemachine_test_fsm.h"
+
+/* Taken from google templates */
+
+#define ASSERT(cond)  do {                                      \
+  if (!(cond)) {                                                \
+    printf("%s: %d: ASSERT FAILED: %s\n", __FILE__, __LINE__,   \
+           #cond);                                              \
+    assert(cond);                                               \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+#define ASSERT_STREQ(a, b)  do {                                          \
+  if (strcmp((a), (b))) {                                                 \
+    printf("%s: %d: ASSERT FAILED: '%s' != '%s'\n", __FILE__, __LINE__,   \
+           (a), (b));                                                     \
+    assert(!strcmp((a), (b)));                                            \
+    exit(1);                                                              \
+  }                                                                       \
+} while (0)
+
+#define ASSERT_STRSTR(text, substr)  do {                       \
+  if (!strstr((text), (substr))) {                              \
+    printf("%s: %d: ASSERT FAILED: '%s' not in '%s'\n",         \
+           __FILE__, __LINE__, (substr), (text));               \
+    assert(strstr((text), (substr)));                           \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+
+#define NUM_STATES 10
+
+/* To simply the tests */
+#define statemachine_parse_str(a,b) statemachine_parse(a, b, strlen(b));
+
+/* Simple state machine test. */
+int test_simple()
+{
+  statemachine_definition *def;
+  statemachine_ctx *sm;
+  def = statemachine_definition_new(NUM_STATES);
+  sm = statemachine_new(def, NULL);
+
+  statemachine_definition_populate(def, simple_state_transitions,
+                                   simple_states_internal_names);
+  ASSERT(sm->current_state == SIMPLE_STATE_A);
+
+  statemachine_parse(sm, "001", 3);
+  ASSERT(sm->current_state == SIMPLE_STATE_B);
+
+  statemachine_parse(sm, "001", 3);
+  ASSERT(sm->current_state == SIMPLE_STATE_C);
+
+  statemachine_parse(sm, "2", 1);
+  ASSERT(sm->current_state == SIMPLE_STATE_B);
+
+  statemachine_parse(sm, "11", 2);
+  ASSERT(sm->current_state == SIMPLE_STATE_D);
+
+  statemachine_delete(sm);
+  return 0;
+}
+
+/* Tests error handling logic when we try to follow non existent transitions. */
+int test_error()
+{
+  statemachine_definition *def;
+  statemachine_ctx *sm;
+  int res;
+
+  def = statemachine_definition_new(NUM_STATES);
+  sm = statemachine_new(def, NULL);
+
+  statemachine_definition_populate(def, simple_state_transitions,
+                                   NULL);
+  ASSERT(sm->current_state == SIMPLE_STATE_A);
+
+  ASSERT(statemachine_get_error_msg(sm) == NULL);
+
+  res = statemachine_parse_str(sm, "00E");
+  ASSERT(sm->current_state == SIMPLE_STATE_ERROR_TEST);
+  ASSERT(sm->current_state == res);
+
+  res = statemachine_parse(sm, "3", 1);
+  ASSERT(res == STATEMACHINE_ERROR);
+  ASSERT_STREQ(statemachine_get_error_msg(sm),
+               "Unexpected character '3'");
+
+  statemachine_reset(sm);
+  ASSERT(statemachine_get_error_msg(sm) == NULL);
+
+  statemachine_delete(sm);
+
+  def = statemachine_definition_new(NUM_STATES);
+  sm = statemachine_new(def, NULL);
+
+  statemachine_definition_populate(def, simple_state_transitions,
+                                   simple_states_internal_names);
+  ASSERT(sm->current_state == SIMPLE_STATE_A);
+
+  res = statemachine_parse_str(sm, "00E");
+  ASSERT(sm->current_state == SIMPLE_STATE_ERROR_TEST);
+  ASSERT(sm->current_state == res);
+
+  res = statemachine_parse(sm, "3", 1);
+  ASSERT(res == STATEMACHINE_ERROR);
+  ASSERT_STREQ(statemachine_get_error_msg(sm),
+               "Unexpected character '3' in state 'error_test'");
+
+  statemachine_delete(sm);
+
+  return 0;
+}
+
+/* Tests htmlparser_start_record() and htmlparser_end_record() logic. */
+
+int test_record()
+{
+  statemachine_definition *def;
+  statemachine_ctx *sm;
+  const char *actual;
+  char expected[STATEMACHINE_RECORD_BUFFER_SIZE];
+  int res;
+  int counter;
+  def = statemachine_definition_new(NUM_STATES);
+  sm = statemachine_new(def, NULL);
+
+  statemachine_definition_populate(def, simple_state_transitions,
+                                   simple_states_internal_names);
+
+  ASSERT(sm->current_state == SIMPLE_STATE_A);
+
+  res = statemachine_parse_str(sm, "001");
+  ASSERT(sm->current_state == SIMPLE_STATE_B);
+  ASSERT(sm->current_state == res);
+
+  statemachine_start_record(sm);
+  statemachine_parse_str(sm, "121212");
+  ASSERT_STREQ("121212", statemachine_stop_record(sm));
+
+  statemachine_parse_str(sm, "not recorded");
+
+  statemachine_start_record(sm);
+  statemachine_parse_str(sm, "121212000");
+  ASSERT_STREQ("121212000", statemachine_stop_record(sm));
+
+  statemachine_start_record(sm);
+  statemachine_parse_str(sm, "1234567890");
+  ASSERT_STREQ("1234567890", statemachine_record_buffer(sm));
+
+  statemachine_parse_str(sm, "test");
+  ASSERT_STREQ("1234567890test", statemachine_stop_record(sm));
+
+  statemachine_start_record(sm);
+
+  /* Record 1000 chars + strlen("beginning-") */
+  statemachine_parse_str(sm, "beginning-");
+  for (counter = 0; counter < 100; counter++) {
+    statemachine_parse_str(sm, "1234567890");
+  }
+
+  /* Make sure we preserved the start of the buffer. */
+  ASSERT_STRSTR(statemachine_record_buffer(sm), "beginning-");
+
+  /* And make sure the size is what we expect. */
+  ASSERT(STATEMACHINE_RECORD_BUFFER_SIZE - 1 ==
+         strlen(statemachine_stop_record(sm)));
+
+  statemachine_start_record(sm);
+  for (counter = 0; counter < 100; counter++) {
+    statemachine_parse_str(sm, "0123456789ABCDEF");
+  }
+
+  expected[0] = '\0';
+  /* Fill the buffer with a pattern 255 chars long (16 * 15 + 15). */
+  for (counter = 0; counter < 15; counter++) {
+    strcat(expected, "0123456789ABCDEF");
+  }
+  strcat(expected, "0123456789ABCDE");
+  actual = statemachine_stop_record(sm);
+  ASSERT_STREQ(expected, actual);
+
+  statemachine_delete(sm);
+  return 0;
+}
+
+/* Test with characters outside of the ascii range */
+int test_no_ascii()
+{
+  statemachine_definition *def;
+  statemachine_ctx *sm;
+  def = statemachine_definition_new(NUM_STATES);
+  sm = statemachine_new(def, NULL);
+
+  statemachine_definition_populate(def, simple_state_transitions,
+                                   simple_states_internal_names);
+
+  ASSERT(sm->current_state == SIMPLE_STATE_A);
+
+  statemachine_parse(sm, "\xf0\xf0\xf1", 3);
+  ASSERT(sm->current_state == SIMPLE_STATE_B);
+
+  statemachine_parse(sm, "\xf0\xf0\xf1", 3);
+  ASSERT(sm->current_state == SIMPLE_STATE_C);
+
+  statemachine_parse(sm, "\xf2", 1);
+  ASSERT(sm->current_state == SIMPLE_STATE_B);
+
+  statemachine_parse(sm, "\xf1\xf1", 2);
+  ASSERT(sm->current_state == SIMPLE_STATE_D);
+
+  statemachine_delete(sm);
+  return 0;
+
+}
+
+int test_copy()
+{
+  statemachine_definition *def;
+  statemachine_ctx *sm1;
+  statemachine_ctx *sm2;
+  statemachine_ctx *sm3;
+  def = statemachine_definition_new(NUM_STATES);
+  sm1 = statemachine_new(def, NULL);
+
+  statemachine_definition_populate(def, simple_state_transitions,
+                                   simple_states_internal_names);
+
+  ASSERT(sm1->current_state == SIMPLE_STATE_A);
+
+  sm2 = statemachine_duplicate(sm1, def, NULL);
+  ASSERT(sm2->current_state == SIMPLE_STATE_A);
+
+  statemachine_parse(sm1, "001", 3);
+  ASSERT(sm1->current_state == SIMPLE_STATE_B);
+  ASSERT(sm2->current_state == SIMPLE_STATE_A);
+
+
+  statemachine_parse(sm1, "001", 3);
+  statemachine_parse(sm2, "001", 3);
+  ASSERT(sm1->current_state == SIMPLE_STATE_C);
+  ASSERT(sm2->current_state == SIMPLE_STATE_B);
+
+  sm3 = statemachine_duplicate(sm2, def, NULL);
+  ASSERT(sm3->current_state == SIMPLE_STATE_B);
+
+  statemachine_parse(sm1, "001", 3);
+  statemachine_parse(sm2, "001", 3);
+  statemachine_parse(sm3, "002", 3);
+  ASSERT(sm1->current_state == SIMPLE_STATE_D);
+  ASSERT(sm2->current_state == SIMPLE_STATE_C);
+  ASSERT(sm3->current_state == SIMPLE_STATE_A);
+
+  statemachine_delete(sm1);
+  statemachine_delete(sm2);
+  statemachine_delete(sm3);
+
+  return 0;
+}
+
+/* Tests statemachine_encode_char().
+ */
+int test_encode_char()
+{
+  char encoded_char[10];
+  int i;
+
+  struct {
+    char chr;
+    const char *result;
+  } encode_map[] = {
+    { 'x', "x" },
+    { '0', "0" },
+    { '\n', "\\n" },
+    { '\r', "\\r" },
+    { '\t', "\\t" },
+    { '\\', "\\\\" },
+    { '\0', "\\x00" },
+    { '\xF0', "\\xf0" },
+    { '\0', NULL}  // Terminates when output == NULL
+  };
+
+  for (i = 0; encode_map[i].result; i++) {
+    statemachine_encode_char(encode_map[i].chr, encoded_char,
+                             sizeof(encoded_char) / sizeof(*encoded_char));
+    ASSERT_STREQ(encoded_char, encode_map[i].result);
+  }
+
+  statemachine_encode_char('\xFF', encoded_char, 1);
+  ASSERT_STREQ(encoded_char, "");
+
+  statemachine_encode_char('\xFF', encoded_char, 2);
+  ASSERT_STREQ(encoded_char, "\\");
+
+  statemachine_encode_char('\xFF', encoded_char, 3);
+  ASSERT_STREQ(encoded_char, "\\x");
+
+  statemachine_encode_char('\xFF', encoded_char, 4);
+  ASSERT_STREQ(encoded_char, "\\xf");
+
+  statemachine_encode_char('\xFF', encoded_char, 5);
+  ASSERT_STREQ(encoded_char, "\\xff");
+
+  return 0;
+}
+
+int main(int argc, char **argv)
+{
+  test_simple();
+  test_error();
+  test_record();
+  test_no_ascii();
+  test_copy();
+  test_encode_char();
+  printf("DONE.\n");
+  return 0;
+}
diff --git a/src/tests/statemachine_test_fsm.config b/src/tests/statemachine_test_fsm.config
new file mode 100644
index 0000000..c781c0a
--- /dev/null
+++ b/src/tests/statemachine_test_fsm.config
@@ -0,0 +1,79 @@
+# Copyright (c) 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# ---
+#
+# Author: falmeida@google.com (Filipe Almeida)
+#
+# Simple state machine definition used in for testing the state machine.
+
+name = 'simple'
+
+comment = 'Simple state machine'
+
+condition('1', '1\xf1')
+condition('2', '2\xf2')
+condition('E', 'E')
+condition('default', '[:default:]')
+
+state(name = 'A',
+      external = 'A',
+      transitions = [
+        ['1', 'B'],
+        ['E', 'error_test'],
+        ['default', 'A'],
+      ])
+
+state(name = 'B',
+      external = 'B',
+      transitions = [
+        ['1', 'C'],
+        ['2', 'A'],
+        ['default', 'B'],
+      ])
+
+state(name = 'C',
+      external = 'C',
+      transitions = [
+        ['1', 'D'],
+        ['2', 'B'],
+        ['default', 'C'],
+      ])
+
+state(name = 'D',
+      external = 'D',
+      transitions = [
+        ['2', 'C'],
+        ['default', 'D'],
+      ])
+
+state(name = 'error_test',
+      external = 'error_test',
+      transitions = [
+        ['2', 'A'],
+      ])
diff --git a/src/tests/template_cache_test.cc b/src/tests/template_cache_test.cc
new file mode 100644
index 0000000..5a23716
--- /dev/null
+++ b/src/tests/template_cache_test.cc
@@ -0,0 +1,1064 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: csilvers@google.com (Craig Silverstein)
+//
+
+#include "config_for_unittests.h"
+#include <ctemplate/template_cache.h>
+#include <assert.h>      // for assert()
+#include <stdio.h>       // for printf()
+#include <stdlib.h>      // for exit()
+#include <string.h>      // for strcmp()
+#include <sys/types.h>   // for mode_t
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif      // for unlink()
+#include <ctemplate/template.h>  // for Template
+#include <ctemplate/template_dictionary.h>  // for TemplateDictionary
+#include <ctemplate/template_enums.h>  // for DO_NOT_STRIP, etc
+#include <ctemplate/template_pathops.h>  // for PathJoin(), kCWD
+#include <ctemplate/template_string.h>  // for TemplateString
+#include "tests/template_test_util.h"  // for AssertExpandIs(), etc
+using std::string;
+using GOOGLE_NAMESPACE::FLAGS_test_tmpdir;
+using GOOGLE_NAMESPACE::AssertExpandIs;
+using GOOGLE_NAMESPACE::CreateOrCleanTestDir;
+using GOOGLE_NAMESPACE::CreateOrCleanTestDirAndSetAsTmpdir;
+using GOOGLE_NAMESPACE::DO_NOT_STRIP;
+using GOOGLE_NAMESPACE::PathJoin;
+using GOOGLE_NAMESPACE::STRIP_BLANK_LINES;
+using GOOGLE_NAMESPACE::STRIP_WHITESPACE;
+using GOOGLE_NAMESPACE::StaticTemplateString;
+using GOOGLE_NAMESPACE::StringToFile;
+using GOOGLE_NAMESPACE::StringToTemplateCache;
+using GOOGLE_NAMESPACE::StringToTemplateFile;
+using GOOGLE_NAMESPACE::Template;
+using GOOGLE_NAMESPACE::TemplateCache;
+using GOOGLE_NAMESPACE::TemplateCachePeer;
+using GOOGLE_NAMESPACE::TemplateDictionary;
+using GOOGLE_NAMESPACE::kCWD;
+
+#define ASSERT(cond)  do {                                      \
+  if (!(cond)) {                                                \
+    printf("ASSERT FAILED, line %d: %s\n", __LINE__, #cond);    \
+    assert(cond);                                               \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+#define ASSERT_STREQ(a, b)  ASSERT(strcmp(a, b) == 0)
+
+static const StaticTemplateString kKey = STS_INIT(kKey, "MY_KEY");
+static const StaticTemplateString kContent = STS_INIT(kContent, "content");
+
+// It would be nice to use the TEST framework, but it makes friendship
+// more difficult.  (TemplateCache befriends TemplateCacheUnittest.)
+class TemplateCacheUnittest {
+ public:
+  static void TestGetTemplate() {
+    // Tests the cache
+    TemplateCache cache1;
+    const char* text = "{This is perfectly valid} yay!";
+    TemplateDictionary empty_dict("dict");
+
+    string filename = StringToTemplateFile(text);
+    const Template* tpl1 = cache1.GetTemplate(filename, DO_NOT_STRIP);
+    const Template* tpl2 = cache1.GetTemplate(filename.c_str(), DO_NOT_STRIP);
+    const Template* tpl3 = cache1.GetTemplate(filename, STRIP_WHITESPACE);
+    ASSERT(tpl1 && tpl2 && tpl3);
+    ASSERT(tpl1 == tpl2);
+    ASSERT(tpl1 != tpl3);
+    AssertExpandIs(tpl1, &empty_dict, text, true);
+    AssertExpandIs(tpl2, &empty_dict, text, true);
+    AssertExpandIs(tpl3, &empty_dict, text, true);
+
+    // Tests that a nonexistent template returns NULL
+    const Template* tpl4 = cache1.GetTemplate("/yakakak", STRIP_WHITESPACE);
+    ASSERT(!tpl4);
+
+    // Make sure we get different results if we use a different cache.
+    TemplateCache cache2;
+    const Template* tpl5 = cache2.GetTemplate(filename, DO_NOT_STRIP);
+    ASSERT(tpl5);
+    ASSERT(tpl5 != tpl1);
+    AssertExpandIs(tpl5, &empty_dict, text, true);
+
+    // And different results yet if we use the default cache.
+    const Template* tpl6 = Template::GetTemplate(filename, DO_NOT_STRIP);
+    ASSERT(tpl6);
+    ASSERT(tpl6 != tpl1);
+    AssertExpandIs(tpl6, &empty_dict, text, true);
+  }
+
+  static void TestLoadTemplate() {
+    // Tests the cache
+    TemplateCache cache1;
+    const char* text = "{This is perfectly valid} yay!";
+    TemplateDictionary empty_dict("dict");
+    string filename = StringToTemplateFile(text);
+
+    ASSERT(cache1.LoadTemplate(filename, DO_NOT_STRIP));
+
+    // Tests that a nonexistent template returns false
+    ASSERT(!cache1.LoadTemplate("/yakakak", STRIP_WHITESPACE));
+  }
+
+  static void TestStringGetTemplate() {
+    // If you use these same cache keys somewhere else,
+    // call Template::ClearCache first.
+    const string cache_key_a = "cache key a";
+    const string text = "Test template 1";
+    TemplateDictionary empty_dict("dict");
+
+    TemplateCache cache1;
+    const Template *tpl1;
+    ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+    tpl1 = cache1.GetTemplate(cache_key_a, DO_NOT_STRIP);
+    AssertExpandIs(tpl1, &empty_dict, text, true);
+
+    // A different cache should give different templates.
+    TemplateCache cache2;
+    const Template *tpl3;
+    ASSERT(cache2.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+    tpl3 = cache2.GetTemplate(cache_key_a, DO_NOT_STRIP);
+    ASSERT(tpl3 != tpl1);
+    AssertExpandIs(tpl3, &empty_dict, text, true);
+
+    // And the main cache different still
+    const Template *tpl4;
+    ASSERT(StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+    tpl4 = Template::GetTemplate(cache_key_a, DO_NOT_STRIP);
+    ASSERT(tpl4 != tpl1);
+    AssertExpandIs(tpl4, &empty_dict, text, true);
+
+    // If we register a new string with the same text, it should be ignored.
+    ASSERT(!cache1.StringToTemplateCache(cache_key_a, "new text",
+                                         DO_NOT_STRIP));
+
+    Template::ClearCache();
+  }
+
+  static void TestStringToTemplateCacheWithStrip() {
+    const string cache_key_a = "cache key a";
+    const string text = "Test template 1";
+    TemplateDictionary empty_dict("dict");
+
+    TemplateCache cache;
+    ASSERT(cache.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+
+    TemplateCachePeer cache_peer(&cache);
+    TemplateCachePeer::TemplateCacheKey cache_key1(cache_key_a, DO_NOT_STRIP);
+    ASSERT(cache_peer.TemplateIsCached(cache_key1));
+    const Template* tpl1 = cache_peer.GetTemplate(cache_key_a, DO_NOT_STRIP);
+    ASSERT(tpl1);
+    AssertExpandIs(tpl1, &empty_dict, text, true);
+
+    // Different strip: when a string template is registered via
+    // StringToTemplateCache with a strip, we cannot use a different
+    // strip later to fetch the template.
+    TemplateCachePeer::TemplateCacheKey cache_key2(cache_key_a,
+                                                   STRIP_WHITESPACE);
+    ASSERT(!cache_peer.TemplateIsCached(cache_key2));
+  }
+
+  static void TestExpandNoLoad() {
+    TemplateCache cache;
+    string filename = StringToTemplateFile("alone");
+    string top_filename = StringToTemplateFile("Hello, {{>WORLD}}");
+    string inc_filename = StringToTemplateFile("world");
+
+    TemplateDictionary dict("ExpandNoLoad");
+    dict.AddIncludeDictionary("WORLD")->SetFilename(inc_filename);
+    string out;
+
+    // This should fail because the cache is empty.
+    cache.Freeze();
+    ASSERT(!cache.ExpandNoLoad(filename, DO_NOT_STRIP, &dict, NULL, &out));
+
+    cache.ClearCache();   // also clears the "frozen" state
+    // This should succeed -- it loads inc_filename from disk.
+    ASSERT(cache.ExpandWithData(filename, DO_NOT_STRIP, &dict, NULL, &out));
+    ASSERT(out == "alone");
+    out.clear();
+    // Now this should succeed -- it's in the cache.
+    cache.Freeze();
+    ASSERT(cache.ExpandNoLoad(filename, DO_NOT_STRIP, &dict, NULL, &out));
+    ASSERT(out == "alone");
+    out.clear();
+
+    // This should fail because neither top nor inc are in the cache.
+    cache.ClearCache();
+    cache.Freeze();
+    ASSERT(!cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+    cache.ClearCache();
+    ASSERT(cache.LoadTemplate(top_filename, DO_NOT_STRIP));
+    // This *should* fail, but because inc_filename isn't in the cache.
+    cache.Freeze();
+    ASSERT(!cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+    // TODO(csilvers): this should not be necessary.  But expand writes
+    //                 to its output even before it fails.
+    out.clear();
+    cache.ClearCache();
+    ASSERT(cache.LoadTemplate(top_filename, DO_NOT_STRIP));
+    ASSERT(cache.LoadTemplate(inc_filename, DO_NOT_STRIP));
+    cache.Freeze();
+    // *Now* it should succeed, with everything it needs loaded.
+    ASSERT(cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+    ASSERT(out == "Hello, world");
+    out.clear();
+    // This should succeed too, of course.
+    ASSERT(cache.ExpandWithData(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+    ASSERT(out == "Hello, world");
+    out.clear();
+
+    cache.ClearCache();
+    ASSERT(cache.ExpandWithData(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+    ASSERT(out == "Hello, world");
+    out.clear();
+    // Now everything NoLoad needs should be in the cache again.
+    cache.Freeze();
+    ASSERT(cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+    ASSERT(out == "Hello, world");
+    out.clear();
+
+    cache.ClearCache();
+    ASSERT(cache.LoadTemplate(top_filename, DO_NOT_STRIP));
+    cache.Freeze();
+    // This fails, of course, because we're frozen.
+    ASSERT(!cache.LoadTemplate(inc_filename, DO_NOT_STRIP));
+    // And thus, this fails too.
+    ASSERT(!cache.ExpandNoLoad(top_filename, DO_NOT_STRIP, &dict, NULL, &out));
+  }
+
+  static void TestTemplateSearchPath() {
+    TemplateCache cache1;
+
+    const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
+    const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
+    CreateOrCleanTestDir(pathA);
+    CreateOrCleanTestDir(pathB);
+
+    TemplateDictionary dict("");
+    cache1.SetTemplateRootDirectory(pathA);
+    cache1.AddAlternateTemplateRootDirectory(pathB);
+    ASSERT(cache1.template_root_directory() == pathA);
+
+    // 1. Show that a template in the secondary path can be found.
+    const string path_b_bar = PathJoin(pathB, "template_bar");
+    StringToFile("b/template_bar", path_b_bar);
+    ASSERT_STREQ(path_b_bar.c_str(),
+                 cache1.FindTemplateFilename("template_bar").c_str());
+    const Template* b_bar = cache1.GetTemplate("template_bar", DO_NOT_STRIP);
+    ASSERT(b_bar);
+    AssertExpandIs(b_bar, &dict, "b/template_bar", true);
+
+    // 2. Show that the search stops once the first match is found.
+    //    Create two templates in separate directories with the same name.
+    const string path_a_foo = PathJoin(pathA, "template_foo");
+    const string path_b_foo = PathJoin(pathB, "template_foo");
+    StringToFile("a/template_foo", path_a_foo);
+    StringToFile("b/template_foo", path_b_foo);
+    ASSERT_STREQ(path_a_foo.c_str(),
+                 cache1.FindTemplateFilename("template_foo").c_str());
+    const Template* a_foo = cache1.GetTemplate("template_foo", DO_NOT_STRIP);
+    ASSERT(a_foo);
+    AssertExpandIs(a_foo, &dict, "a/template_foo", true);
+
+    // 3. Show that attempting to find a non-existent template gives an
+    //    empty path.
+    ASSERT(cache1.FindTemplateFilename("baz").empty());
+
+    // 4. If we make a new cache, its path will be followed.
+    TemplateCache cache2;
+    cache2.SetTemplateRootDirectory(pathB);
+    ASSERT_STREQ(path_b_foo.c_str(),
+                 cache2.FindTemplateFilename("template_foo").c_str());
+    const Template* b_foo = cache2.GetTemplate("template_foo", DO_NOT_STRIP);
+    ASSERT(b_foo);
+    AssertExpandIs(b_foo, &dict, "b/template_foo", true);
+
+    // 5. Neither path will work for the default cache, which has no path.
+    ASSERT(Template::template_root_directory() == kCWD);
+    ASSERT(Template::FindTemplateFilename("template_foo").empty());
+    ASSERT(!Template::GetTemplate("template_foo", DO_NOT_STRIP));
+
+    CreateOrCleanTestDir(pathA);
+    CreateOrCleanTestDir(pathB);
+  }
+
+  static void TestDelete() {
+    Template::ClearCache();   // just for exercise.
+    const string cache_key = "TestRemoveStringFromTemplateCache";
+    const string text = "<html>here today...</html>";
+    const string text2 = "<html>on disk tomorrow</html>";
+
+    TemplateDictionary dict("test");
+    TemplateCache cache1;
+
+    ASSERT(cache1.StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
+    const Template* tpl = cache1.GetTemplate(cache_key, DO_NOT_STRIP);
+    ASSERT(tpl);
+    AssertExpandIs(tpl, &dict, text, true);
+
+    cache1.Delete(cache_key);
+    tpl = cache1.GetTemplate(cache_key, DO_NOT_STRIP);
+    ASSERT(!tpl);
+    tpl = cache1.GetTemplate(cache_key, STRIP_WHITESPACE);
+    ASSERT(!tpl);
+    tpl = cache1.GetTemplate(cache_key, STRIP_BLANK_LINES);
+    ASSERT(!tpl);
+
+    // Try delete on a file-based template as well.
+    string filename = StringToTemplateFile(text2);
+    tpl = cache1.GetTemplate(filename, DO_NOT_STRIP);
+    ASSERT(tpl);
+    AssertExpandIs(tpl, &dict, text2, true);
+    cache1.Delete(filename);
+    tpl = cache1.GetTemplate(filename, DO_NOT_STRIP);
+    ASSERT(tpl);
+    AssertExpandIs(tpl, &dict, text2, true);
+
+    // Try re-adding a cache key after deleting it.
+    ASSERT(cache1.StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
+    tpl = cache1.GetTemplate(cache_key, DO_NOT_STRIP);
+    ASSERT(tpl);
+    AssertExpandIs(tpl, &dict, text, true);
+
+    // Try ClearCache while we're at it.
+    cache1.ClearCache();
+    tpl = cache1.GetTemplate(cache_key, STRIP_BLANK_LINES);
+    ASSERT(!tpl);
+
+    // Test on the Template class, which has a different function name.
+    ASSERT(StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
+    tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
+    ASSERT(tpl);
+    AssertExpandIs(tpl, &dict, text, true);
+
+    Template::RemoveStringFromTemplateCache(cache_key);
+    tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
+    ASSERT(!tpl);
+    tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE);
+    ASSERT(!tpl);
+    tpl = Template::GetTemplate(cache_key, STRIP_BLANK_LINES);
+    ASSERT(!tpl);
+  }
+
+  static void TestTemplateCache() {
+    const string filename_a = StringToTemplateFile("Test template 1");
+    const string filename_b = StringToTemplateFile("Test template 2.");
+
+    TemplateCache cache1;
+    const Template *tpl, *tpl2;
+    ASSERT(tpl = cache1.GetTemplate(filename_a, DO_NOT_STRIP));
+
+    ASSERT(tpl2 = cache1.GetTemplate(filename_b, DO_NOT_STRIP));
+    ASSERT(tpl2 != tpl);  // different filenames.
+    ASSERT(tpl2 = cache1.GetTemplate(filename_a, STRIP_BLANK_LINES));
+    ASSERT(tpl2 != tpl);  // different strip.
+    ASSERT(tpl2 = cache1.GetTemplate(filename_b, STRIP_BLANK_LINES));
+    ASSERT(tpl2 != tpl);  // different filenames and strip.
+    ASSERT(tpl2 = cache1.GetTemplate(filename_a, DO_NOT_STRIP));
+    ASSERT(tpl2 == tpl);  // same filename and strip.
+  }
+
+  static void TestReloadAllIfChangedLazyLoad() {
+    TemplateDictionary dict("empty");
+    TemplateCache cache1;
+
+    string filename = StringToTemplateFile("{valid template}");
+    string nonexistent = StringToTemplateFile("dummy");
+    unlink(nonexistent.c_str());
+
+    const Template* tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE);
+    assert(tpl);
+    const Template* tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
+    assert(!tpl2);
+
+    StringToFile("exists now!", nonexistent);
+    tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
+    ASSERT(!tpl2);
+    cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE);  // force the reload
+    tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
+    ASSERT(tpl2);                     // file exists now
+
+    unlink(nonexistent.c_str());      // here today...
+    cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    ASSERT(cache1.GetTemplate(filename, STRIP_WHITESPACE));
+    ASSERT(!cache1.GetTemplate(nonexistent, STRIP_WHITESPACE));
+
+    StringToFile("lazarus", nonexistent);
+    StringToFile("{new template}", filename);
+    tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE);
+    AssertExpandIs(tpl, &dict, "{valid template}", true);   // haven't reloaded
+    // But a different cache (say, the default) should load the new content.
+    const Template* tpl3 = Template::GetTemplate(filename, STRIP_WHITESPACE);
+    AssertExpandIs(tpl3, &dict, "{new template}", true);
+
+    cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    tpl = cache1.GetTemplate(filename, STRIP_WHITESPACE);  // needed
+    AssertExpandIs(tpl, &dict, "{new template}", true);
+    tpl2 = cache1.GetTemplate(nonexistent, STRIP_WHITESPACE);
+    ASSERT(tpl2);
+    AssertExpandIs(tpl2, &dict, "lazarus", true);
+
+    // Ensure that string templates don't reload
+    const string cache_key_a = "cache key a";
+    const string text = "Test template 1";
+    const Template *str_tpl;
+    ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+    str_tpl = cache1.GetTemplate(cache_key_a, DO_NOT_STRIP);
+    AssertExpandIs(str_tpl, &dict, text, true);
+    cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    ASSERT(cache1.GetTemplate(cache_key_a, DO_NOT_STRIP) == str_tpl);
+
+    cache1.ClearCache();
+  }
+
+  static void TestReloadAllIfChangedImmediateLoad() {
+    TemplateDictionary dict("empty");
+    TemplateCache cache1;
+    TemplateCachePeer cache_peer(&cache1);
+
+    // Add templates
+    string filename1 = StringToTemplateFile("{valid template}");
+    string filename2 = StringToTemplateFile("{another valid template}");
+
+    const Template* tpl1 = cache1.GetTemplate(filename1,
+                                              STRIP_WHITESPACE);
+    assert(tpl1);
+    const Template* tpl2 = cache1.GetTemplate(filename2,
+                                              STRIP_WHITESPACE);
+    assert(tpl2);
+
+    StringToFile("{file1 contents changed}", filename1);
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+
+    TemplateCachePeer::TemplateCacheKey cache_key1(filename1, STRIP_WHITESPACE);
+    ASSERT(cache_peer.TemplateIsCached(cache_key1));
+    const Template* tpl1_post_reload = cache_peer.GetTemplate(filename1,
+                                                              STRIP_WHITESPACE);
+    ASSERT(tpl1_post_reload != tpl1);
+    // Check that cache1's tpl1 has the new contents
+    AssertExpandIs(tpl1_post_reload, &dict, "{file1 contents changed}",
+                   true);
+
+    // Ensure tpl2 is unchanged
+    TemplateCachePeer::TemplateCacheKey cache_key2(filename2, STRIP_WHITESPACE);
+    ASSERT(cache_peer.TemplateIsCached(cache_key2));
+    const Template* tpl2_post_reload = cache_peer.GetTemplate(filename2,
+                                                              STRIP_WHITESPACE);
+    ASSERT(tpl2_post_reload == tpl2);
+
+    // Test delete & re-add: delete tpl2, and reload.
+    unlink(filename2.c_str());
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    ASSERT(!cache_peer.GetTemplate(filename2, STRIP_WHITESPACE));
+    // Re-add tpl2 and ensure it reloads.
+    StringToFile("{re-add valid template contents}", filename2);
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    ASSERT(cache_peer.GetTemplate(filename2, STRIP_WHITESPACE));
+
+    // Ensure that string templates don't reload
+    const string cache_key_a = "cache key a";
+    const string text = "Test template 1";
+    const Template *str_tpl;
+    ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+    str_tpl = cache1.GetTemplate(cache_key_a, DO_NOT_STRIP);
+    AssertExpandIs(str_tpl, &dict, text, true);
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    ASSERT(cache1.GetTemplate(cache_key_a, DO_NOT_STRIP) == str_tpl);
+
+    cache1.ClearCache();
+  }
+
+  static void TestReloadImmediateWithDifferentSearchPaths() {
+    TemplateDictionary dict("empty");
+    TemplateCache cache1;
+    TemplateCachePeer cache_peer(&cache1);
+
+    const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
+    const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
+    CreateOrCleanTestDir(pathA);
+    CreateOrCleanTestDir(pathB);
+
+    cache1.SetTemplateRootDirectory(pathA);
+    cache1.AddAlternateTemplateRootDirectory(pathB);
+    ASSERT(cache1.template_root_directory() == pathA);
+
+    // Add b/foo
+    const string path_b_foo = PathJoin(pathB, "template_foo");
+    StringToFile("b/template_foo", path_b_foo);
+    ASSERT_STREQ(path_b_foo.c_str(),
+                 cache1.FindTemplateFilename("template_foo").c_str());
+    // Add b/foo to the template cache.
+    cache1.GetTemplate("template_foo", DO_NOT_STRIP);
+
+    // Add a/foo
+    const string path_a_foo = PathJoin(pathA, "template_foo");
+    StringToFile("a/template_foo", path_a_foo);
+    ASSERT_STREQ(path_a_foo.c_str(),
+                 cache1.FindTemplateFilename("template_foo").c_str());
+
+    // Now, on reload we pick up foo from the earlier search path: a/foo
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    const Template* foo_post_reload = cache_peer.GetTemplate("template_foo",
+                                                             STRIP_WHITESPACE);
+    AssertExpandIs(foo_post_reload, &dict, "a/template_foo",
+                   true);
+
+    // Delete a/foo and reload. Now we pick up the next available foo: b/foo
+    unlink(path_a_foo.c_str());
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    foo_post_reload = cache_peer.GetTemplate("template_foo",
+                                             STRIP_WHITESPACE);
+    AssertExpandIs(foo_post_reload, &dict, "b/template_foo",
+                   true);
+  }
+
+  static void TestReloadLazyWithDifferentSearchPaths() {
+    // Identical test as above with but with LAZY_RELOAD
+    TemplateDictionary dict("empty");
+    TemplateCache cache1;
+    TemplateCachePeer cache_peer(&cache1);
+
+    const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
+    const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
+    CreateOrCleanTestDir(pathA);
+    CreateOrCleanTestDir(pathB);
+
+    cache1.SetTemplateRootDirectory(pathA);
+    cache1.AddAlternateTemplateRootDirectory(pathB);
+    ASSERT(cache1.template_root_directory() == pathA);
+
+    // Add b/foo
+    const string path_b_foo = PathJoin(pathB, "template_foo");
+    StringToFile("b/template_foo", path_b_foo);
+    ASSERT_STREQ(path_b_foo.c_str(),
+                 cache1.FindTemplateFilename("template_foo").c_str());
+    // Add b/foo to the template cache.
+    cache1.GetTemplate("template_foo", DO_NOT_STRIP);
+
+    // Add a/foo
+    const string path_a_foo = PathJoin(pathA, "template_foo");
+    StringToFile("a/template_foo", path_a_foo);
+    ASSERT_STREQ(path_a_foo.c_str(),
+                 cache1.FindTemplateFilename("template_foo").c_str());
+
+    // Now, on reload we pick up foo from the earlier search path: a/foo
+    cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    const Template* foo_post_reload = cache_peer.GetTemplate("template_foo",
+                                                             STRIP_WHITESPACE);
+    AssertExpandIs(foo_post_reload, &dict, "a/template_foo",
+                   true);
+
+    // Delete a/foo and reload. Now we pick up the next available foo: b/foo
+    unlink(path_a_foo.c_str());
+    cache1.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    foo_post_reload = cache_peer.GetTemplate("template_foo",
+                                             STRIP_WHITESPACE);
+    AssertExpandIs(foo_post_reload, &dict, "b/template_foo",
+                   true);
+  }
+
+  static void TestRefcounting() {
+    TemplateCache cache1;
+    TemplateCachePeer cache_peer(&cache1);
+    TemplateDictionary dict("dict");
+
+    // Add templates
+    string filename1 = StringToTemplateFile("{valid template}");
+    string filename2 = StringToTemplateFile("{another valid template}");
+
+    const Template* cache1_tpl1 = cache1.GetTemplate(filename1,
+                                                     STRIP_WHITESPACE);
+    assert(cache1_tpl1);
+    const Template* cache1_tpl2 = cache1.GetTemplate(filename2,
+                                                     STRIP_WHITESPACE);
+    assert(cache1_tpl2);
+
+    // Check refcount.  It should be 2 -- one for the originalvalue
+    // when it's constructed, and one for the call to GetTemplate.
+    TemplateCachePeer::TemplateCacheKey cache_key1(filename1, STRIP_WHITESPACE);
+    ASSERT(cache_peer.Refcount(cache_key1) == 2);
+    TemplateCachePeer::TemplateCacheKey cache_key2(filename2, STRIP_WHITESPACE);
+    ASSERT(cache_peer.Refcount(cache_key2) == 2);
+
+    // Clone cache2 from cache1
+    TemplateCache* cache2 = cache1.Clone();
+    TemplateCachePeer cache_peer2(cache2);
+
+    // Check refcount was incremented.  It should be the same for both caches.
+    ASSERT(cache_peer.Refcount(cache_key1) == 3);
+    ASSERT(cache_peer2.Refcount(cache_key1) == 3);
+    ASSERT(cache_peer.Refcount(cache_key2) == 3);
+    ASSERT(cache_peer2.Refcount(cache_key2) == 3);
+
+    // Check that the template ptrs in both caches are the same.
+    const Template* cache2_tpl1 = cache2->GetTemplate(filename1,
+                                                      STRIP_WHITESPACE);
+    const Template* cache2_tpl2 = cache2->GetTemplate(filename2,
+                                                      STRIP_WHITESPACE);
+    ASSERT(cache2_tpl1 == cache1_tpl1);
+    ASSERT(cache2_tpl2 == cache1_tpl2);
+
+    // GetTemplate should have augmented the refcount.
+    ASSERT(cache_peer.Refcount(cache_key1) == 4);
+    ASSERT(cache_peer2.Refcount(cache_key1) == 4);
+    ASSERT(cache_peer.Refcount(cache_key2) == 4);
+    ASSERT(cache_peer2.Refcount(cache_key2) == 4);
+
+    // Change tpl1 file contents and reload.
+    StringToFile("{file1 contents changed}", filename1);
+    cache2->ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    // Since the template will be reloaded into a new instance,
+    // GetTemplate will return new pointers. The older template
+    // pointer was moved to the freelist.
+    const Template* cache2_tpl1_post_reload = cache2->GetTemplate(
+        filename1, STRIP_WHITESPACE);
+    ASSERT(cache2_tpl1_post_reload != cache2_tpl1);
+    // Check that cache1's tpl1 has the new contents
+    AssertExpandIs(cache2_tpl1_post_reload, &dict, "{file1 contents changed}",
+                   true);
+
+    // Ensure tpl2 is unchanged
+    const Template* cache2_tpl2_post_reload = cache2->GetTemplate(
+        filename2, STRIP_WHITESPACE);
+    ASSERT(cache2_tpl2_post_reload == cache2_tpl2);
+
+    // Now key1 points to different templates in cache1 and cache2.
+    // cache1's version should have a refcount of 3 (was 4, went down
+    // by 1 when cache2 dropped its reference to it).  cache2's
+    // version should be 2 (one for the new file, 1 for the call to
+    // GetTemplate() that followed it), while key2 should have a
+    // refcount of 5 in both caches (due to the new call, above, to
+    // GetTemplate()).
+    ASSERT(cache_peer.Refcount(cache_key1) == 3);
+    ASSERT(cache_peer2.Refcount(cache_key1) == 2);
+    ASSERT(cache_peer.Refcount(cache_key2) == 5);
+    ASSERT(cache_peer2.Refcount(cache_key2) == 5);
+
+    const int old_delete_count = cache_peer.NumTotalTemplateDeletes();
+
+    // Clear up the cache2's freelist, this should drop all refcounts,
+    // due to the calls cache_peer2 made to
+    // GetTemplate(the-old-filename1), GetTemplate(the-new-filename1),
+    // and GetTemplate(filename2) (twice!)
+    cache_peer2.DoneWithGetTemplatePtrs();
+    ASSERT(cache_peer.Refcount(cache_key1) == 2);
+    ASSERT(cache_peer2.Refcount(cache_key1) == 1);
+    ASSERT(cache_peer.Refcount(cache_key2) == 3);
+    ASSERT(cache_peer2.Refcount(cache_key2) == 3);
+
+    // Make sure that deleting from the cache causes deletion.
+    // ClearCache() on peer1 should finally get rid of the old filename1.
+    cache_peer.ClearCache();
+    ASSERT(cache_peer.NumTotalTemplateDeletes() == old_delete_count + 1);
+    cache_peer2.ClearCache();
+    // Delete-count should go up by 2 as both the new tpl1, and tpl2, go away.
+    ASSERT(cache_peer.NumTotalTemplateDeletes() == old_delete_count + 3);
+
+    delete cache2;
+  }
+
+  static void TestDoneWithGetTemplatePtrs() {
+    TemplateCache cache1;
+    TemplateCachePeer cache_peer1(&cache1);
+    TemplateDictionary dict("dict");
+
+    // Add templates
+    string fname = StringToTemplateFile("{valid template}");
+    TemplateCachePeer::TemplateCacheKey cache_key(fname, STRIP_WHITESPACE);
+    string out;
+
+    int old_delete_count = cache_peer1.NumTotalTemplateDeletes();
+
+    // OK, let's get the templates in the cache.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    // This should not have changed the delete-count.
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+    // And the refcount should be 1.
+    ASSERT(cache_peer1.Refcount(cache_key) == 1);
+    // Same holds if we expand again.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+    ASSERT(cache_peer1.Refcount(cache_key) == 1);
+
+    // Now we delete from the cache.  Should up the delete_count.
+    ASSERT(cache1.Delete(fname));
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // Calling DoneWithGetTemplatePtrs() should be a noop -- we
+    // haven't called GetTemplate() yet.
+    cache1.DoneWithGetTemplatePtrs();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+
+    // Now do the same thing, but throw in a GetTemplate().  Now
+    // DoneWithGetTemplatePtrs() should still cause a delete, but only
+    // after a call to Delete() deletes the cache's refcount too.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    cache1.DoneWithGetTemplatePtrs();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+    ASSERT(cache1.Delete(fname));
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+    cache1.ClearCache();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+
+    // Now load in a replacement.  The loading itself should cause a
+    // delete (no GetTemplate calls, so no need to involve the freelist).
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    StringToFile("{file1 contents changed}", fname);
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+    // DoneWithGetTemplatePtrs() should just be a noop.
+    cache1.DoneWithGetTemplatePtrs();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+    // Delete the new version of fname too!
+    cache1.Delete(fname);
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // Now load in a replacement, but having done a GetTemplate() first.
+    // We need DoneWithGetTemplatePtrs() to delete, in this case.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    ASSERT(cache_peer1.Refcount(cache_key) == 2);
+    StringToFile("{file1 contents changed}", fname);
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+    cache1.DoneWithGetTemplatePtrs();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+    // Delete the new version of fname too!
+    cache1.Delete(fname);
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // Add a Clone() into the mix.  Now Delete() calls, even from both
+    // caches, won't up the delete-count until we DoneWithGetTemplatePtrs()
+    // -- but only from the cache that called GetTemplate().
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    ASSERT(cache_peer1.Refcount(cache_key) == 2);
+    {
+      TemplateCache* cache2 = cache1.Clone();
+      TemplateCachePeer cache_peer2(cache2);
+      ASSERT(cache_peer1.Refcount(cache_key) == 3);
+      ASSERT(cache_peer2.Refcount(cache_key) == 3);
+      // Do all sorts of Delete()s.
+      StringToFile("{file1 contents changed}", fname);
+      cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+      ASSERT(cache_peer1.Refcount(cache_key) == 1);  // the new file
+      ASSERT(cache_peer2.Refcount(cache_key) == 2);  // the old file
+      cache2->ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+      // Each cache has a different copy of the new file.
+      ASSERT(cache_peer1.Refcount(cache_key) == 1);  // the new file
+      ASSERT(cache_peer2.Refcount(cache_key) == 1);  // the new file
+      ASSERT(cache1.Delete(fname));   // should delete the new file
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+      ASSERT(cache2->Delete(fname));
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+      cache2->DoneWithGetTemplatePtrs();
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+      cache1.DoneWithGetTemplatePtrs();
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+      cache1.ClearCache();
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+      delete cache2;
+    }
+
+    // If we call DoneWithGetTemplatePtrs() while a clone points to the
+    // template, it won't delete the template yet.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    {
+      TemplateCache* cache2 = cache1.Clone();
+      TemplateCachePeer cache_peer2(cache2);
+      StringToFile("{file1 contents changed}", fname);
+      cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+      delete cache2;
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+    }
+    cache1.ClearCache();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // If we throw an explicit GetTemplate() in, we still need
+    // DoneWithGetTemplatePtrs().
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    {
+      TemplateCache* cache2 = cache1.Clone();
+      TemplateCachePeer cache_peer2(cache2);
+      StringToFile("{file1 contents changed}", fname);
+      cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+      cache1.DoneWithGetTemplatePtrs();
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+      delete cache2;
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+    }
+    cache1.ClearCache();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // Multiple GetTemplate()s should still all be cleared by
+    // DoneWithGetTemplatePtrs().
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    ASSERT(cache_peer1.Refcount(cache_key) == 3);
+    StringToFile("{file1 contents changed}", fname);
+    cache1.ReloadAllIfChanged(TemplateCache::IMMEDIATE_RELOAD);
+    cache1.DoneWithGetTemplatePtrs();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+    cache1.ClearCache();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // Calling ClearCache() deletes old templates too -- we don't even
+    // need to change the content.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    cache1.GetTemplate(fname, STRIP_WHITESPACE);
+    cache1.ClearCache();
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+
+    // So does deleting the cache object.
+    ASSERT(cache1.ExpandWithData(fname, STRIP_WHITESPACE, &dict, NULL, &out));
+    {
+      TemplateCache* cache2 = cache1.Clone();
+      TemplateCachePeer cache_peer2(cache2);
+      ASSERT(cache_peer1.Refcount(cache_key) == 2);
+      cache2->GetTemplate(fname, STRIP_WHITESPACE);
+      ASSERT(cache_peer1.Refcount(cache_key) == 3);
+      ASSERT(cache_peer2.Refcount(cache_key) == 3);
+      ASSERT(cache1.Delete(fname));
+      ASSERT(cache_peer1.NumTotalTemplateDeletes() == old_delete_count);
+      ASSERT(cache_peer2.Refcount(cache_key) == 2);
+      delete cache2;
+    }
+    ASSERT(cache_peer1.NumTotalTemplateDeletes() == ++old_delete_count);
+  }
+
+  static void TestCloneStringTemplates() {
+    TemplateCache cache1;
+
+    // Create & insert a string template
+    const string cache_key_a = "cache key a";
+    const string text = "Test template 1";
+    TemplateDictionary empty_dict("dict");
+
+    ASSERT(cache1.StringToTemplateCache(cache_key_a, text, DO_NOT_STRIP));
+
+    // Clone cache2 from cache1
+    TemplateCache* cache2 = cache1.Clone();
+
+    // Check that the string template was copied into cache2
+    const Template* cache2_tpl = cache2->GetTemplate(cache_key_a,
+                                                     DO_NOT_STRIP);
+    ASSERT(cache2_tpl);
+    AssertExpandIs(cache2_tpl, &empty_dict, text, true);
+
+    delete cache2;
+  }
+
+  static void TestInclude() {
+    TemplateCache cache;
+    string incname = StringToTemplateFile("include & print file\n");
+    string tpl_file = StringToTemplateFile("hi {{>INC:h}} bar\n");
+    const Template* tpl = cache.GetTemplate(tpl_file, DO_NOT_STRIP);
+    ASSERT(tpl);
+
+    TemplateDictionary dict("dict");
+    AssertExpandWithCacheIs(&cache, tpl_file, DO_NOT_STRIP, &dict, NULL,
+                            "hi  bar\n", true);
+    dict.AddIncludeDictionary("INC")->SetFilename(incname);
+    AssertExpandWithCacheIs(&cache, tpl_file, DO_NOT_STRIP, &dict, NULL,
+                            "hi include &amp; print file  bar\n",
+                            true);
+  }
+
+  // Make sure we don't deadlock when a template includes itself.
+  // This also tests we handle recursive indentation properly.
+  static void TestRecursiveInclude() {
+    TemplateCache cache;
+    string incname = StringToTemplateFile("hi {{>INC}} bar\n  {{>INC}}!");
+    const Template* tpl = cache.GetTemplate(incname, DO_NOT_STRIP);
+    ASSERT(tpl);
+    TemplateDictionary dict("dict");
+    dict.AddIncludeDictionary("INC")->SetFilename(incname);
+    // Note the last line is indented 4 spaces instead of 2.  This is
+    // because the last sub-include is indented.
+    AssertExpandWithCacheIs(&cache, incname, DO_NOT_STRIP, &dict, NULL,
+                            "hi hi  bar\n  ! bar\n  hi  bar\n    !!",
+                            true);
+  }
+
+  static void TestStringTemplateInclude() {
+    const string cache_key = "TestStringTemplateInclude";
+    const string cache_key_inc = "TestStringTemplateInclude-inc";
+    const string text = "<html>{{>INC}}</html>";
+    const string text_inc = "<div>\n<p>\nUser {{USER}}\n</div>";
+
+    TemplateCache cache;
+    ASSERT(cache.StringToTemplateCache(cache_key, text, DO_NOT_STRIP));
+    ASSERT(cache.StringToTemplateCache(cache_key_inc, text_inc, DO_NOT_STRIP));
+
+    const Template *tpl = cache.GetTemplate(cache_key, DO_NOT_STRIP);
+    ASSERT(tpl);
+
+    TemplateDictionary dict("dict");
+    TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC");
+    sub_dict->SetFilename(cache_key_inc);
+
+    sub_dict->SetValue("USER", "John<>Doe");
+    string expected = "<html><div>\n<p>\nUser John<>Doe\n</div></html>";
+    AssertExpandWithCacheIs(&cache, cache_key, DO_NOT_STRIP, &dict, NULL,
+                            expected, true);
+  }
+
+  static void TestTemplateString() {
+    TemplateCache cache;
+    ASSERT(cache.StringToTemplateCache(kKey, kContent, DO_NOT_STRIP));
+    const Template *tpl = cache.GetTemplate(kKey, DO_NOT_STRIP);
+    ASSERT(tpl);
+
+    TemplateDictionary dict("dict");
+    AssertExpandWithCacheIs(&cache, "MY_KEY", DO_NOT_STRIP, &dict, NULL,
+                            "content", true);
+
+    // Try retrieving with a char* rather than a TemplateString*.
+    tpl = cache.GetTemplate("MY_KEY", DO_NOT_STRIP);
+    ASSERT(tpl);
+    AssertExpandWithCacheIs(&cache, "MY_KEY", DO_NOT_STRIP, &dict, NULL,
+                            "content", true);
+
+    // Delete with a char* rather than a TemplateString*.
+    cache.Delete("MY_KEY");
+    tpl = cache.GetTemplate("MY_KEY", DO_NOT_STRIP);
+    ASSERT(!tpl);
+
+    ASSERT(cache.StringToTemplateCache("MY_KEY", "content", DO_NOT_STRIP));
+    tpl = cache.GetTemplate(kKey, DO_NOT_STRIP);
+    ASSERT(tpl);
+    cache.Delete(kKey);
+    tpl = cache.GetTemplate("MY_KEY", DO_NOT_STRIP);
+    ASSERT(!tpl);
+  }
+
+  static void TestFreeze() {
+    TemplateCache cache;
+     TemplateDictionary dict("dict");
+
+    // Load some templates
+    string filename1 = StringToTemplateFile("{valid template}");
+    string filename2 = StringToTemplateFile("hi {{>INC:h}} bar\n");
+
+    const Template* cache_tpl1 = cache.GetTemplate(filename1, STRIP_WHITESPACE);
+    assert(cache_tpl1);
+    AssertExpandIs(cache_tpl1, &dict, "{valid template}", true);
+    const Template* cache_tpl2 = cache.GetTemplate(filename2, DO_NOT_STRIP);
+    assert(cache_tpl2);
+    static_cast<void>(cache_tpl2);  // avoid unused var warning in opt mode
+    AssertExpandWithCacheIs(&cache, filename2, DO_NOT_STRIP, &dict, NULL,
+                            "hi  bar\n", true);
+
+    // Set the root directory
+    const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
+    CreateOrCleanTestDir(pathA);
+    cache.SetTemplateRootDirectory(pathA);
+    ASSERT(cache.template_root_directory() == pathA);
+
+    // Freeze the cache now, and test its impact.
+    cache.Freeze();
+
+    // 1. Loading new templates fails.
+    string filename3 = StringToTemplateFile("{yet another valid template}");
+    const Template* cache_tpl3 = cache.GetTemplate(filename3, STRIP_WHITESPACE);
+    assert(!cache_tpl3);
+    static_cast<void>(cache_tpl3);  // avoid unused var warning in opt mode
+
+    // 2. Reloading existing templates fails.
+    StringToFile("{file1 contents changed}", filename1);
+    cache.ReloadAllIfChanged(TemplateCache::LAZY_RELOAD);
+    const Template* cache_tpl1_post_reload = cache.GetTemplate(
+        filename1, STRIP_WHITESPACE);
+    ASSERT(cache_tpl1_post_reload == cache_tpl1);
+    // Check that cache's tpl1 has the same old contents
+    AssertExpandIs(cache_tpl1_post_reload, &dict, "{valid template}",
+                   true);
+    // 3. Cannot delete from a frozen cache.
+    cache.Delete(filename1);
+    ASSERT(cache.GetTemplate(filename1, STRIP_WHITESPACE));
+
+    // 4. Expand won't load an included template on-demand.
+    string incname = StringToTemplateFile("include & print file\n");
+    dict.AddIncludeDictionary("INC")->SetFilename(incname);
+    AssertExpandWithCacheIs(&cache, filename2, DO_NOT_STRIP, &dict, NULL,
+                            "hi  bar\n", false);
+
+    // 5. Cannot change template root directory.
+    const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
+    CreateOrCleanTestDir(pathB);
+    cache.SetTemplateRootDirectory(pathB);
+    ASSERT(cache.template_root_directory() == pathA);  // Still the old path
+
+    CreateOrCleanTestDir(pathA);
+    CreateOrCleanTestDir(pathB);
+  }
+};
+
+
+int main(int argc, char** argv) {
+
+  CreateOrCleanTestDirAndSetAsTmpdir(FLAGS_test_tmpdir);
+
+  TemplateCacheUnittest::TestGetTemplate();
+  TemplateCacheUnittest::TestLoadTemplate();
+  TemplateCacheUnittest::TestStringGetTemplate();
+  TemplateCacheUnittest::TestStringToTemplateCacheWithStrip();
+  TemplateCacheUnittest::TestExpandNoLoad();
+  TemplateCacheUnittest::TestTemplateSearchPath();
+  TemplateCacheUnittest::TestDelete();
+  TemplateCacheUnittest::TestTemplateCache();
+  TemplateCacheUnittest::TestReloadAllIfChangedLazyLoad();
+  TemplateCacheUnittest::TestReloadAllIfChangedImmediateLoad();
+  TemplateCacheUnittest::TestReloadImmediateWithDifferentSearchPaths();
+  TemplateCacheUnittest::TestReloadLazyWithDifferentSearchPaths();
+  TemplateCacheUnittest::TestRefcounting();
+  TemplateCacheUnittest::TestDoneWithGetTemplatePtrs();
+  TemplateCacheUnittest::TestCloneStringTemplates();
+  TemplateCacheUnittest::TestInclude();
+  TemplateCacheUnittest::TestRecursiveInclude();
+  TemplateCacheUnittest::TestStringTemplateInclude();
+  TemplateCacheUnittest::TestTemplateString();
+  TemplateCacheUnittest::TestFreeze();
+
+  printf("DONE\n");
+  return 0;
+}
diff --git a/src/tests/template_dictionary_unittest.cc b/src/tests/template_dictionary_unittest.cc
new file mode 100644
index 0000000..f524a21
--- /dev/null
+++ b/src/tests/template_dictionary_unittest.cc
@@ -0,0 +1,1012 @@
+// Copyright (c) 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: csilvers@google.com (Craig Silverstein)
+//
+// This code is written to not use the google testing framework
+// as much as possible, to make it easier to opensource.
+
+#include "config_for_unittests.h"
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <vector>
+#include "base/arena.h"
+#include <ctemplate/template_dictionary.h>
+#include <ctemplate/template_modifiers.h>
+#include <ctemplate/per_expand_data.h>
+#include "tests/template_test_util.h"
+#include "base/util.h"
+TEST_INIT               // defines RUN_ALL_TESTS
+
+using std::string;
+using std::vector;
+using GOOGLE_NAMESPACE::UnsafeArena;
+using GOOGLE_NAMESPACE::DO_NOT_STRIP;
+using GOOGLE_NAMESPACE::ExpandEmitter;
+using GOOGLE_NAMESPACE::PerExpandData;
+using GOOGLE_NAMESPACE::StaticTemplateString;
+using GOOGLE_NAMESPACE::StringToTemplateCache;
+using GOOGLE_NAMESPACE::TemplateDictionary;
+using GOOGLE_NAMESPACE::TemplateDictionaryInterface;
+using GOOGLE_NAMESPACE::TemplateDictionaryPeer;
+using GOOGLE_NAMESPACE::TemplateString;
+
+#define ASSERT_STRSTR(text, substr)  do {                       \
+  if (!strstr((text), (substr))) {                              \
+    printf("%s: %d: ASSERT FAILED: '%s' not in '%s'\n",         \
+           __FILE__, __LINE__, (substr), (text));               \
+    assert(strstr((text), (substr)));                           \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+
+// test escape-functor that replaces all input with "foo"
+class FooEscaper : public GOOGLE_NAMESPACE::TemplateModifier {
+ public:
+  void Modify(const char* in, size_t inlen,
+              const PerExpandData*,
+              ExpandEmitter* outbuf, const string& arg) const {
+    assert(arg.empty());    // we don't take an argument
+    outbuf->Emit("foo");
+  }
+};
+
+// test escape-functor that replaces all input with ""
+class NullEscaper : public GOOGLE_NAMESPACE::TemplateModifier {
+ public:
+  void Modify(const char* in, size_t inlen,
+              const PerExpandData*,
+              ExpandEmitter* outbuf, const string& arg) const {
+    assert(arg.empty());    // we don't take an argument
+  }
+};
+
+// first does javascript-escaping, then html-escaping
+class DoubleEscaper : public GOOGLE_NAMESPACE::TemplateModifier {
+ public:
+  void Modify(const char* in, size_t inlen,
+              const PerExpandData* data,
+              ExpandEmitter* outbuf, const string& arg) const {
+    assert(arg.empty());    // we don't take an argument
+    string tmp = GOOGLE_NAMESPACE::javascript_escape(in, inlen);
+    GOOGLE_NAMESPACE::html_escape.Modify(tmp.data(), tmp.size(), data, outbuf, "");
+  }
+};
+
+namespace {
+
+static const TemplateDictionary* GetSectionDict(
+    const TemplateDictionary* d, const char* name, int i) {
+  TemplateDictionaryPeer peer(d);
+  vector<const TemplateDictionary*> dicts;
+  EXPECT_GE(peer.GetSectionDictionaries(name, &dicts), i);
+  return dicts[i];
+}
+static const TemplateDictionary* GetIncludeDict(
+    const TemplateDictionary* d, const char* name, int i) {
+  TemplateDictionaryPeer peer(d);
+  vector<const TemplateDictionary*> dicts;
+  EXPECT_GE(peer.GetIncludeDictionaries(name, &dicts), i);
+  return dicts[i];
+}
+
+static void SetUp() {
+  TemplateDictionary::SetGlobalValue("GLOBAL", "top");
+}
+
+TEST(TemplateDictionary, SetValueAndTemplateStringAndArena) {
+  // Try both with the arena, and without.
+  UnsafeArena arena(100);
+  // We run the test with arena twice to double-check we don't ever delete it
+  UnsafeArena* arenas[] = {&arena, &arena, NULL};
+  for (int i = 0; i < sizeof(arenas)/sizeof(*arenas); ++i) {
+    TemplateDictionary dict(string("test_arena") + char('0'+i), arenas[i]);
+
+    // Test copying char*s, strings, and explicit TemplateStrings
+    dict.SetValue("FOO", "foo");
+    dict.SetValue(string("FOO2"), TemplateString("foo2andmore", 4));
+		dict["FOO3"] = "foo3";
+		dict[string("FOO4")] = TemplateString("foo4andmore", 4);
+		dict["FOO5"] = string("Olaf");
+		dict["FOO6"] = 6;
+		dict["FOO7"] = long(7);
+
+    TemplateDictionaryPeer peer(&dict);
+    // verify what happened
+    EXPECT_TRUE(peer.ValueIs("FOO", "foo"));
+    EXPECT_TRUE(peer.ValueIs("FOO2", "foo2"));
+    string dump;
+    dict.DumpToString(&dump);
+    char expected[256];
+    snprintf(expected, sizeof(expected),
+             ("global dictionary {\n"
+              "   BI_NEWLINE: >\n"
+              "<\n"
+              "   BI_SPACE: > <\n"
+              "   GLOBAL: >top<\n"
+              "};\n"
+              "dictionary 'test_arena%d' {\n"
+              "   FOO: >foo<\n"
+              "   FOO2: >foo2<\n"
+              "   FOO3: >foo3<\n"
+              "   FOO4: >foo4<\n"
+              "   FOO5: >Olaf<\n"
+              "   FOO6: >6<\n"
+              "   FOO7: >7<\n"
+              "}\n"), i);
+    EXPECT_STREQ(dump.c_str(), expected);
+  }
+}
+
+TEST(TemplateDictionary, SetValueWithoutCopy) {
+  UnsafeArena arena(100);
+  TemplateDictionary dict("Test arena", &arena);
+
+  char value[32];
+  snprintf(value, sizeof(value), "%s", "value");
+
+  const void* const ptr = arena.Alloc(0);
+  dict.SetValueWithoutCopy("key", value);
+  // We shouldn't have copied the value string.
+  EXPECT_EQ(ptr, arena.Alloc(0));
+
+  TemplateDictionaryPeer peer(&dict);
+  EXPECT_TRUE(peer.ValueIs("key", "value"));
+  // If our content changes, so does what's in the dictionary -- but
+  // only the contents of the buffer, not its length!
+  snprintf(value, sizeof(value), "%s", "not_value");
+  EXPECT_TRUE(peer.ValueIs("key", "not_v"));   // sizeof("not_v") == sizeof("value")
+}
+
+TEST(TemplateDictionary, SetIntValue) {
+  TemplateDictionary dict("test_SetIntValue", NULL);
+  TemplateDictionaryPeer peer(&dict);
+
+  dict.SetIntValue("INT", 5);
+  // - is an illegal varname in templates, but perfectly fine in dicts
+  dict.SetIntValue("-INT", -5);
+
+  EXPECT_TRUE(peer.ValueIs("INT", "5"));
+  EXPECT_TRUE(peer.ValueIs("-INT", "-5"));
+  string dump;
+  dict.DumpToString(&dump);
+  ASSERT_STRSTR(dump.c_str(), "\n   INT: >5<\n");
+  ASSERT_STRSTR(dump.c_str(), "\n   -INT: >-5<\n");
+
+}
+
+TEST(TemplateDictionary, SetFormattedValue) {
+  TemplateDictionary dict("test_SetFormattedValue", NULL);
+  TemplateDictionaryPeer peer(&dict);
+
+  dict.SetFormattedValue(TemplateString("PRINTF", sizeof("PRINTF")-1),
+                         "%s test %04d", "template test", 1);
+
+  EXPECT_TRUE(peer.ValueIs("PRINTF", "template test test 0001"));
+  string dump;
+  dict.DumpToString(&dump);
+  ASSERT_STRSTR(dump.c_str(), "\n   PRINTF: >template test test 0001<\n");
+
+  // Now test something of size 4k or so, where we can't use scratchbuf
+  dict.SetFormattedValue(TemplateString("PRINTF", sizeof("PRINTF")-1),
+                         "%s test %04444d", "template test", 2);
+  string expected("template test test ");
+  for (int i = 0; i < 4443; ++i)
+    expected.append("0");
+  expected.append("2");
+  EXPECT_TRUE(peer.ValueIs("PRINTF", expected));
+  string dump2;
+  dict.DumpToString(&dump2);
+  expected = string("\n   PRINTF: >") + expected + string("<\n");
+  ASSERT_STRSTR(dump2.c_str(), expected.c_str());
+}
+
+TEST(TemplateDictionary, SetEscapedValue) {
+  TemplateDictionary dict("test_SetEscapedValue", NULL);
+  TemplateDictionaryPeer peer(&dict);
+
+  dict.SetEscapedValue("hardest HTML",
+                       "<A HREF='foo'\nid=\"bar\t\t&&\vbaz\">",
+                       GOOGLE_NAMESPACE::html_escape);
+  dict.SetEscapedValue("hardest JS",
+                       ("f = 'foo';\r\n\tprint \"\\&foo = \b\", \"foo\""),
+                       GOOGLE_NAMESPACE::javascript_escape);
+  dict.SetEscapedValue("query escape 0", "",
+                       GOOGLE_NAMESPACE::url_query_escape);
+
+  EXPECT_TRUE(peer.ValueIs("hardest HTML",
+                           "&lt;A HREF=&#39;foo&#39; id=&quot;bar  &amp;&amp; "
+                           "baz&quot;&gt;"));
+  EXPECT_TRUE(peer.ValueIs("hardest JS",
+                           "f \\x3d \\x27foo\\x27;\\r\\n\\tprint \\x22\\\\\\x26"
+                           "foo \\x3d \\b\\x22, \\x22foo\\x22"));
+  EXPECT_TRUE(peer.ValueIs("query escape 0", ""));
+
+  // Test using hand-made modifiers.
+  FooEscaper foo_escaper;
+  dict.SetEscapedValue("easy foo", "hello there!",
+                       FooEscaper());
+  dict.SetEscapedValue("harder foo", "so much to say\nso many foos",
+                       foo_escaper);
+  DoubleEscaper double_escaper;
+  dict.SetEscapedValue("easy double", "doo",
+                       double_escaper);
+  dict.SetEscapedValue("harder double", "<A HREF='foo'>\n",
+                       DoubleEscaper());
+  dict.SetEscapedValue("hardest double",
+                       "print \"<A HREF='foo'>\";\r\n\\1;",
+                       double_escaper);
+
+  EXPECT_TRUE(peer.ValueIs("easy foo", "foo"));
+  EXPECT_TRUE(peer.ValueIs("harder foo", "foo"));
+  EXPECT_TRUE(peer.ValueIs("easy double", "doo"));
+  EXPECT_TRUE(peer.ValueIs("harder double",
+                           "\\x3cA HREF\\x3d\\x27foo\\x27\\x3e\\n"));
+  EXPECT_TRUE(peer.ValueIs("hardest double",
+                           "print \\x22\\x3cA HREF\\x3d\\x27foo\\x27\\x3e\\x22;"
+                           "\\r\\n\\\\1;"));
+}
+
+TEST(TemplateDictionary, SetEscapedFormattedValue) {
+  TemplateDictionary dict("test_SetEscapedFormattedValue", NULL);
+  TemplateDictionaryPeer peer(&dict);
+
+  dict.SetEscapedFormattedValue("HTML", GOOGLE_NAMESPACE::html_escape,
+                                "This is <%s> #%.4f", "a & b", 1.0/3);
+  dict.SetEscapedFormattedValue("PRE", GOOGLE_NAMESPACE::pre_escape,
+                                "if %s x = %.4f;", "(a < 1 && b > 2)\n\t", 1.0/3);
+  dict.SetEscapedFormattedValue("URL", GOOGLE_NAMESPACE::url_query_escape,
+                                "pageviews-%s", "r?egex");
+  dict.SetEscapedFormattedValue("XML", GOOGLE_NAMESPACE::xml_escape,
+                                "This&is%s -- ok?", "just&");
+
+  EXPECT_TRUE(peer.ValueIs("HTML",
+                           "This is &lt;a &amp; b&gt; #0.3333"));
+  EXPECT_TRUE(peer.ValueIs("PRE",
+                           "if (a &lt; 1 &amp;&amp; b &gt; 2)\n\t x = 0.3333;"));
+  EXPECT_TRUE(peer.ValueIs("URL", "pageviews-r%3Fegex"));
+
+  EXPECT_TRUE(peer.ValueIs("XML", "This&amp;isjust&amp; -- ok?"));
+}
+
+static const StaticTemplateString kSectName =
+    STS_INIT(kSectName, "test_SetAddSectionDictionary");
+
+TEST(TemplateDictionary, AddSectionDictionary) {
+  // For fun, we'll make this constructor take a static template string.
+  TemplateDictionary dict(kSectName, NULL);
+  TemplateDictionaryPeer peer(&dict);
+  dict.SetValue("TOPLEVEL", "foo");
+  dict.SetValue("TOPLEVEL2", "foo2");
+
+  TemplateDictionary* subdict_1a = dict.AddSectionDictionary("section1");
+  // This is the same dict, but name is specified a different way.
+  TemplateDictionary* subdict_1b = dict.AddSectionDictionary(
+      TemplateString("section1__ignored__", strlen("section1")));
+  TemplateDictionaryPeer subdict_1a_peer(subdict_1a);
+  TemplateDictionaryPeer subdict_1b_peer(subdict_1b);
+  subdict_1a->SetValue("SUBLEVEL", "subfoo");
+  subdict_1b->SetValue("SUBLEVEL", "subbar");
+
+  TemplateDictionary* subdict_2 = dict.AddSectionDictionary("section2");
+  TemplateDictionaryPeer subdict_2_peer(subdict_2);
+  subdict_2->SetValue("TOPLEVEL", "bar");    // overriding top dict
+  TemplateDictionary* subdict_2_1 = subdict_2->AddSectionDictionary("sub");
+  TemplateDictionaryPeer subdict_2_1_peer(subdict_2_1);
+  subdict_2_1->SetIntValue("GLOBAL", 21);    // overrides value in setUp()
+
+  // Verify that all variables that should be look-up-able are, and that
+  // we have proper precedence.
+  EXPECT_TRUE(peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(peer.ValueIs("TOPLEVEL", "foo"));
+  EXPECT_TRUE(peer.ValueIs("TOPLEVEL2", "foo2"));
+  EXPECT_TRUE(peer.ValueIs("SUBLEVEL", ""));
+
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("TOPLEVEL", "foo"));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("TOPLEVEL2", "foo2"));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("SUBLEVEL", "subfoo"));
+
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("TOPLEVEL", "foo"));
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("TOPLEVEL2", "foo2"));
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("SUBLEVEL", "subbar"));
+
+  EXPECT_TRUE(subdict_2_peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("TOPLEVEL", "bar"));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("TOPLEVEL2", "foo2"));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("SUBLEVEL", ""));
+
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("GLOBAL", "21"));
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("TOPLEVEL", "bar"));
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("TOPLEVEL2", "foo2"));
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("SUBLEVEL", ""));
+
+  // Verify that everyone knows about its sub-dictionaries, and also
+  // that these go 'up the chain' on lookup failure
+  EXPECT_FALSE(peer.IsHiddenSection("section1"));
+  EXPECT_FALSE(peer.IsHiddenSection("section2"));
+  EXPECT_TRUE(peer.IsHiddenSection("section3"));
+  EXPECT_TRUE(peer.IsHiddenSection("sub"));
+  EXPECT_FALSE(subdict_1a_peer.IsHiddenSection("section1"));
+  EXPECT_TRUE(subdict_1a_peer.IsHiddenSection("sub"));
+  EXPECT_FALSE(subdict_2_peer.IsHiddenSection("sub"));
+  EXPECT_FALSE(subdict_2_1_peer.IsHiddenSection("sub"));
+
+  // We should get the dictionary-lengths right as well
+  vector<const TemplateDictionary*> dummy;
+  EXPECT_EQ(2, peer.GetSectionDictionaries("section1", &dummy));
+  EXPECT_EQ(1, peer.GetSectionDictionaries("section2", &dummy));
+  EXPECT_EQ(1, subdict_2_peer.GetSectionDictionaries("sub", &dummy));
+  // Test some of the values
+  EXPECT_TRUE(TemplateDictionaryPeer(GetSectionDict(&dict, "section1", 0))
+              .ValueIs("SUBLEVEL", "subfoo"));
+  EXPECT_TRUE(TemplateDictionaryPeer(GetSectionDict(&dict, "section1", 1))
+              .ValueIs("SUBLEVEL", "subbar"));
+  EXPECT_TRUE(TemplateDictionaryPeer(GetSectionDict(&dict, "section2", 0))
+              .ValueIs("TOPLEVEL", "bar"));
+  EXPECT_TRUE(TemplateDictionaryPeer(
+      GetSectionDict(GetSectionDict(&dict, "section2", 0), "sub", 0))
+              .ValueIs("TOPLEVEL", "bar"));
+  EXPECT_TRUE(TemplateDictionaryPeer(
+      GetSectionDict(GetSectionDict(&dict, "section2", 0), "sub", 0))
+              .ValueIs("GLOBAL", "21"));
+
+  // Make sure we're making descriptive names
+  EXPECT_STREQ(dict.name().c_str(),
+               "test_SetAddSectionDictionary");
+  EXPECT_STREQ(subdict_1a->name().c_str(),
+               "test_SetAddSectionDictionary/section1#1");
+  EXPECT_STREQ(subdict_1b->name().c_str(),
+               "test_SetAddSectionDictionary/section1#2");
+  EXPECT_STREQ(subdict_2->name().c_str(),
+               "test_SetAddSectionDictionary/section2#1");
+  EXPECT_STREQ(subdict_2_1->name().c_str(),
+               "test_SetAddSectionDictionary/section2#1/sub#1");
+
+  // Finally, we can test the whole kit and kaboodle
+  string dump;
+  dict.DumpToString(&dump);
+  const char* const expected =
+    ("global dictionary {\n"
+     "   BI_NEWLINE: >\n"
+     "<\n"
+     "   BI_SPACE: > <\n"
+     "   GLOBAL: >top<\n"
+     "};\n"
+     "dictionary 'test_SetAddSectionDictionary' {\n"
+     "   TOPLEVEL: >foo<\n"
+     "   TOPLEVEL2: >foo2<\n"
+     "   section section1 (dict 1 of 2) -->\n"
+     "     dictionary 'test_SetAddSectionDictionary/section1#1' {\n"
+     "       SUBLEVEL: >subfoo<\n"
+     "     }\n"
+     "   section section1 (dict 2 of 2) -->\n"
+     "     dictionary 'test_SetAddSectionDictionary/section1#2' {\n"
+     "       SUBLEVEL: >subbar<\n"
+     "     }\n"
+     "   section section2 (dict 1 of 1) -->\n"
+     "     dictionary 'test_SetAddSectionDictionary/section2#1' {\n"
+     "       TOPLEVEL: >bar<\n"
+     "       section sub (dict 1 of 1) -->\n"
+     "         dictionary 'test_SetAddSectionDictionary/section2#1/sub#1' {\n"
+     "           GLOBAL: >21<\n"
+     "         }\n"
+     "     }\n"
+     "}\n");
+  EXPECT_STREQ(dump.c_str(), expected);
+}
+
+TEST(TemplateDictionary, ShowSection) {
+  TemplateDictionary dict("test_SetShowSection", NULL);
+  // Let's say what filename dict is associated with
+  dict.SetFilename("bigmamainclude!.tpl");
+  dict.SetValue("TOPLEVEL", "foo");
+  dict.SetValue("TOPLEVEL2", "foo2");
+  dict.ShowSection("section1");
+  dict.ShowSection("section2");
+  // Test calling ShowSection twice on the same section
+  dict.ShowSection("section2");
+  // Test that ShowSection is a no-op if called after AddSectionDictionary()
+  TemplateDictionary* subdict = dict.AddSectionDictionary("section3");
+  TemplateDictionaryPeer subdict_peer(subdict);
+  subdict->SetValue("TOPLEVEL", "bar");
+  dict.ShowSection("section3");
+
+  EXPECT_TRUE(subdict_peer.ValueIs("TOPLEVEL", "bar"));
+
+  // Since ShowSection() doesn't return a sub-dict, the only way to
+  // probe what the dicts look like is via Dump()
+  string dump;
+  dict.DumpToString(&dump);
+  const char* const expected =
+    ("global dictionary {\n"
+     "   BI_NEWLINE: >\n"
+     "<\n"
+     "   BI_SPACE: > <\n"
+     "   GLOBAL: >top<\n"
+     "};\n"
+     "dictionary 'test_SetShowSection (intended for bigmamainclude!.tpl)' {\n"
+     "   TOPLEVEL: >foo<\n"
+     "   TOPLEVEL2: >foo2<\n"
+     "   section section1 (dict 1 of 1) -->\n"
+     "     dictionary 'empty dictionary' {\n"
+     "     }\n"
+     "   section section2 (dict 1 of 1) -->\n"
+     "     dictionary 'empty dictionary' {\n"
+     "     }\n"
+     "   section section3 (dict 1 of 1) -->\n"
+     "     dictionary 'test_SetShowSection/section3#1' {\n"
+     "       TOPLEVEL: >bar<\n"
+     "     }\n"
+     "}\n");
+  EXPECT_STREQ(dump.c_str(), expected);
+}
+
+TEST(TemplateDictionary, SetValueAndShowSection) {
+  TemplateDictionary dict("test_SetValueAndShowSection");
+  TemplateDictionaryPeer peer(&dict);
+  dict.SetValue("TOPLEVEL", "foo");
+
+  dict.SetValueAndShowSection("INSEC", "bar", "SEC1");
+  dict.SetValueAndShowSection("NOTINSEC", "", "SEC2");
+  dict.SetValueAndShowSection("NOTINSEC2", NULL, "SEC3");
+
+  EXPECT_FALSE(peer.IsHiddenSection("SEC1"));
+  EXPECT_TRUE(peer.IsHiddenSection("SEC2"));
+  EXPECT_TRUE(peer.IsHiddenSection("SEC3"));
+
+  // Again, we don't get subdicts, so we have to dump to check values
+  string dump;
+  dict.DumpToString(&dump);
+  const char* const expected =
+    ("global dictionary {\n"
+     "   BI_NEWLINE: >\n"
+     "<\n"
+     "   BI_SPACE: > <\n"
+     "   GLOBAL: >top<\n"
+     "};\n"
+     "dictionary 'test_SetValueAndShowSection' {\n"
+     "   TOPLEVEL: >foo<\n"
+     "   section SEC1 (dict 1 of 1) -->\n"
+     "     dictionary 'test_SetValueAndShowSection/SEC1#1' {\n"
+     "       INSEC: >bar<\n"
+     "     }\n"
+     "}\n");
+  EXPECT_STREQ(dump.c_str(), expected);
+}
+
+TEST(TemplateDictionary, SetTemplateGlobalValue) {
+  // The functionality involving it passing across the included dictionaries
+  // is also tested in TestAddIncludeDictionary
+  TemplateDictionary dict("test_SetTemplateGlobalValue", NULL);
+  TemplateDictionary* subdict = dict.AddSectionDictionary("section1");
+  TemplateDictionary* subsubdict =
+    subdict->AddSectionDictionary("section1's child");
+  TemplateDictionary* includedict = dict.AddIncludeDictionary("include1");
+
+  TemplateDictionaryPeer peer(&dict);
+  TemplateDictionaryPeer subdict_peer(subdict);
+  TemplateDictionaryPeer subsubdict_peer(subsubdict);
+  TemplateDictionaryPeer includedict_peer(includedict);
+
+  // Setting a template value after sub dictionaries are created should
+  // affect the sub dictionaries as well.
+  dict.SetTemplateGlobalValue("TEMPLATEVAL", "templateval");
+  EXPECT_TRUE(peer.ValueIs("TEMPLATEVAL", "templateval"));
+  EXPECT_TRUE(subdict_peer.ValueIs("TEMPLATEVAL", "templateval"));
+  EXPECT_TRUE(subsubdict_peer.ValueIs("TEMPLATEVAL", "templateval"));
+  EXPECT_TRUE(includedict_peer.ValueIs("TEMPLATEVAL", "templateval"));
+
+  // sub dictionaries after you set the template value should also
+  // get the template value
+  TemplateDictionary* subdict2 = dict.AddSectionDictionary("section2");
+  TemplateDictionary* includedict2 = dict.AddIncludeDictionary("include2");
+  TemplateDictionaryPeer subdict2_peer(subdict2);
+  TemplateDictionaryPeer includedict2_peer(includedict2);
+
+  EXPECT_TRUE(subdict2_peer.ValueIs("TEMPLATEVAL", "templateval"));
+  EXPECT_TRUE(includedict2_peer.ValueIs("TEMPLATEVAL", "templateval"));
+
+  // setting a template value on a sub dictionary should affect all the other
+  // sub dictionaries and the parent as well
+  subdict->SetTemplateGlobalValue("TEMPLATEVAL2", "templateval2");
+  EXPECT_TRUE(peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(subdict_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(subsubdict_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(includedict_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(subdict2_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(includedict2_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+
+  includedict->SetTemplateGlobalValue("TEMPLATEVAL3", "templateval3");
+  EXPECT_TRUE(peer.ValueIs("TEMPLATEVAL3", "templateval3"));
+  EXPECT_TRUE(subdict_peer.ValueIs("TEMPLATEVAL3", "templateval3"));
+  EXPECT_TRUE(subsubdict_peer.ValueIs("TEMPLATEVAL3", "templateval3"));
+  EXPECT_TRUE(includedict_peer.ValueIs("TEMPLATEVAL3", "templateval3"));
+  EXPECT_TRUE(subdict2_peer.ValueIs("TEMPLATEVAL3", "templateval3"));
+  EXPECT_TRUE(includedict2_peer.ValueIs("TEMPLATEVAL3", "templateval3"));
+
+  // you should be able to override a template value with a regular value
+  // and the overwritten regular value should pass on to its children
+  subdict->SetValue("TEMPLATEVAL2", "subdictval");
+  includedict->SetValue("TEMPLATEVAL2", "includedictval");
+  EXPECT_TRUE(peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(subdict_peer.ValueIs("TEMPLATEVAL2", "subdictval"));
+  EXPECT_TRUE(subsubdict_peer.ValueIs("TEMPLATEVAL2", "subdictval"));
+  EXPECT_TRUE(includedict_peer.ValueIs("TEMPLATEVAL2", "includedictval"));
+  EXPECT_TRUE(subdict2_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+  EXPECT_TRUE(includedict2_peer.ValueIs("TEMPLATEVAL2", "templateval2"));
+
+  // A section shown template-globally will be shown in all its children.
+  dict.ShowTemplateGlobalSection("ShownTemplateGlobalSection");
+  EXPECT_FALSE(peer.IsHiddenSection("ShownTemplateGlobalSection"));
+
+  EXPECT_FALSE(subdict2_peer.IsHiddenSection("ShownTemplateGlobalSection"));
+  EXPECT_FALSE(subsubdict_peer.IsHiddenSection("ShownTemplateGlobalSection"));
+
+  // Showing a template-global section in a child will show it in all templates
+  // in the tree
+  subdict->ShowTemplateGlobalSection("ShownFromAChild");
+  EXPECT_FALSE(peer.IsHiddenSection("ShownFromAChild"));
+  EXPECT_FALSE(subsubdict_peer.IsHiddenSection("ShownFromAChild"));
+
+  // Asking for a section that doesn't exist shouldn't cause infinite recursion
+  peer.IsHiddenSection("NAVBAR_SECTION");
+}
+
+TEST(TemplateDictionary, SetTemplateGlobalValueWithoutCopy) {
+  UnsafeArena arena(100);
+  TemplateDictionary dict("Test arena", &arena);
+  TemplateDictionaryPeer peer(&dict);
+
+  char value[32];
+  snprintf(value, sizeof(value), "%s", "value");
+
+  const void* const ptr = arena.Alloc(0);
+  dict.SetTemplateGlobalValueWithoutCopy("key", value);
+  // We shouldn't have copied the value string.
+  EXPECT_EQ(ptr, arena.Alloc(0));
+
+  EXPECT_TRUE(peer.ValueIs("key", "value"));
+  // If our content changes, so does what's in the dictionary -- but
+  // only the contents of the buffer, not its length!
+  snprintf(value, sizeof(value), "%s", "not_value");
+  EXPECT_TRUE(peer.ValueIs("key", "not_v"));   // "not_v" size == value" size
+}
+
+TEST(TemplateDictionary, AddIncludeDictionary) {
+  TemplateDictionary dict("test_SetAddIncludeDictionary", NULL);
+  TemplateDictionaryPeer peer(&dict);
+  dict.SetValue("TOPLEVEL", "foo");
+  dict.SetValue("TOPLEVEL2", "foo2");
+  dict.SetTemplateGlobalValue("TEMPLATELEVEL", "foo3");
+
+  TemplateDictionary* subdict_1a = dict.AddIncludeDictionary("include1");
+  TemplateDictionaryPeer subdict_1a_peer(subdict_1a);
+  subdict_1a->SetFilename("incfile1a");
+  // This is the same dict, but name is specified a different way.
+  TemplateDictionary* subdict_1b = dict.AddIncludeDictionary(
+      TemplateString("include1__ignored__", strlen("include1")));
+  TemplateDictionaryPeer subdict_1b_peer(subdict_1b);
+  // Let's try not calling SetFilename on this one.
+  subdict_1a->SetValue("SUBLEVEL", "subfoo");
+  subdict_1b->SetValue("SUBLEVEL", "subbar");
+
+  TemplateDictionary* subdict_2 = dict.AddIncludeDictionary("include2");
+  TemplateDictionaryPeer subdict_2_peer(subdict_2);
+  subdict_2->SetFilename("foo/bar");
+  subdict_2->SetValue("TOPLEVEL", "bar");    // overriding top dict
+  // overriding template dict
+  subdict_2->SetValue("TEMPLATELEVEL", "subfoo3");
+  TemplateDictionary* subdict_2_1 = subdict_2->AddIncludeDictionary("sub");
+  TemplateDictionaryPeer subdict_2_1_peer(subdict_2_1);
+  subdict_2_1->SetFilename("baz");
+  subdict_2_1->SetIntValue("GLOBAL", 21);    // overrides value in setUp()
+
+  // Verify that all variables that should be look-up-able are, and that
+  // we have proper precedence.  Unlike with sections, includes lookups
+  // do not go 'up the chain'.
+  EXPECT_TRUE(peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(peer.ValueIs("TOPLEVEL", "foo"));
+  EXPECT_TRUE(peer.ValueIs("TOPLEVEL2", "foo2"));
+  EXPECT_TRUE(peer.ValueIs("TEMPLATELEVEL", "foo3"));
+  EXPECT_TRUE(peer.ValueIs("SUBLEVEL", ""));
+
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("TOPLEVEL", ""));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("TOPLEVEL2", ""));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("TEMPLATELEVEL", "foo3"));
+  EXPECT_TRUE(subdict_1a_peer.ValueIs("SUBLEVEL", "subfoo"));
+
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("TOPLEVEL", ""));
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("TOPLEVEL2", ""));
+  EXPECT_TRUE(subdict_1b_peer.ValueIs("SUBLEVEL", "subbar"));
+
+  EXPECT_TRUE(subdict_2_peer.ValueIs("GLOBAL", "top"));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("TOPLEVEL", "bar"));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("TOPLEVEL2", ""));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("TEMPLATELEVEL", "subfoo3"));
+  EXPECT_TRUE(subdict_2_peer.ValueIs("SUBLEVEL", ""));
+
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("GLOBAL", "21"));
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("TOPLEVEL", ""));
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("TOPLEVEL2", ""));
+  EXPECT_TRUE(subdict_2_1_peer.ValueIs("SUBLEVEL", ""));
+
+  // Verify that everyone knows about its sub-dictionaries, but that
+  // these do not try to go 'up the chain' on lookup failure
+  EXPECT_FALSE(peer.IsHiddenTemplate("include1"));
+  EXPECT_FALSE(peer.IsHiddenTemplate("include2"));
+  EXPECT_TRUE(peer.IsHiddenTemplate("include3"));
+  EXPECT_TRUE(peer.IsHiddenTemplate("sub"));
+  EXPECT_TRUE(subdict_1a_peer.IsHiddenTemplate("include1"));
+  EXPECT_TRUE(subdict_1a_peer.IsHiddenTemplate("sub"));
+  EXPECT_FALSE(subdict_2_peer.IsHiddenTemplate("sub"));
+  EXPECT_TRUE(subdict_2_1_peer.IsHiddenTemplate("sub"));
+
+  // We should get the dictionary-lengths right as well
+  vector<const TemplateDictionary*> dummy;
+  EXPECT_EQ(2, peer.GetIncludeDictionaries("include1", &dummy));
+  EXPECT_EQ(1, peer.GetIncludeDictionaries("include2", &dummy));
+  EXPECT_EQ(1, subdict_2_peer.GetIncludeDictionaries("sub", &dummy));
+
+  // We can also test the include-files are right
+  EXPECT_EQ(2, peer.GetIncludeDictionaries("include1", &dummy));
+  EXPECT_EQ(1, peer.GetIncludeDictionaries("include2", &dummy));
+  EXPECT_EQ(1, subdict_2_peer.GetIncludeDictionaries("sub", &dummy));
+  // Test some of the values
+  EXPECT_TRUE(TemplateDictionaryPeer(GetIncludeDict(&dict, "include1", 0))
+              .ValueIs("SUBLEVEL", "subfoo"));
+  EXPECT_TRUE(TemplateDictionaryPeer(GetIncludeDict(&dict, "include1", 1))
+              .ValueIs("SUBLEVEL", "subbar"));
+  EXPECT_TRUE(TemplateDictionaryPeer(GetIncludeDict(&dict, "include2", 0))
+              .ValueIs("TOPLEVEL", "bar"));
+  EXPECT_TRUE(TemplateDictionaryPeer(
+      GetIncludeDict(GetIncludeDict(&dict, "include2", 0), "sub", 0))
+              .ValueIs("TOPLEVEL", ""));
+  EXPECT_TRUE(TemplateDictionaryPeer(
+      GetIncludeDict(GetIncludeDict(&dict, "include2", 0), "sub", 0))
+              .ValueIs("GLOBAL", "21"));
+  // We can test the include-names as well
+  EXPECT_STREQ(peer.GetIncludeTemplateName("include1", 0), "incfile1a");
+  EXPECT_STREQ(peer.GetIncludeTemplateName("include1", 1), "");
+  EXPECT_STREQ(peer.GetIncludeTemplateName("include2", 0), "foo/bar");
+  EXPECT_STREQ(TemplateDictionaryPeer(GetIncludeDict(&dict, "include2", 0))
+               .GetIncludeTemplateName("sub", 0),
+               "baz");
+
+  // Make sure we're making descriptive names
+  EXPECT_STREQ(dict.name().c_str(),
+               "test_SetAddIncludeDictionary");
+  EXPECT_STREQ(subdict_1a->name().c_str(),
+               "test_SetAddIncludeDictionary/include1#1");
+  EXPECT_STREQ(subdict_1b->name().c_str(),
+               "test_SetAddIncludeDictionary/include1#2");
+  EXPECT_STREQ(subdict_2->name().c_str(),
+               "test_SetAddIncludeDictionary/include2#1");
+  EXPECT_STREQ(subdict_2_1->name().c_str(),
+               "test_SetAddIncludeDictionary/include2#1/sub#1");
+
+  // Finally, we can test the whole kit and kaboodle
+  string dump;
+  dict.DumpToString(&dump);
+  const char* const expected =
+    ("global dictionary {\n"
+     "   BI_NEWLINE: >\n"
+     "<\n"
+     "   BI_SPACE: > <\n"
+     "   GLOBAL: >top<\n"
+     "};\n"
+     "template dictionary {\n"
+     "   TEMPLATELEVEL: >foo3<\n"
+     "};\n"
+     "dictionary 'test_SetAddIncludeDictionary' {\n"
+     "   TOPLEVEL: >foo<\n"
+     "   TOPLEVEL2: >foo2<\n"
+     "   include-template include1 (dict 1 of 2, from incfile1a) -->\n"
+     "     global dictionary {\n"
+     "       BI_NEWLINE: >\n"
+     "<\n"
+     "       BI_SPACE: > <\n"
+     "       GLOBAL: >top<\n"
+     "     };\n"
+     "     dictionary 'test_SetAddIncludeDictionary/include1#1 (intended for incfile1a)' {\n"
+     "       SUBLEVEL: >subfoo<\n"
+     "     }\n"
+     "   include-template include1 (dict 2 of 2, **NO FILENAME SET; THIS DICT WILL BE IGNORED**) -->\n"
+     "     global dictionary {\n"
+     "       BI_NEWLINE: >\n"
+     "<\n"
+     "       BI_SPACE: > <\n"
+     "       GLOBAL: >top<\n"
+     "     };\n"
+     "     dictionary 'test_SetAddIncludeDictionary/include1#2' {\n"
+     "       SUBLEVEL: >subbar<\n"
+     "     }\n"
+     "   include-template include2 (dict 1 of 1, from foo/bar) -->\n"
+     "     global dictionary {\n"
+     "       BI_NEWLINE: >\n"
+     "<\n"
+     "       BI_SPACE: > <\n"
+     "       GLOBAL: >top<\n"
+     "     };\n"
+     "     dictionary 'test_SetAddIncludeDictionary/include2#1 (intended for foo/bar)' {\n"
+     "       TEMPLATELEVEL: >subfoo3<\n"
+     "       TOPLEVEL: >bar<\n"
+     "       include-template sub (dict 1 of 1, from baz) -->\n"
+     "         global dictionary {\n"
+     "           BI_NEWLINE: >\n"
+     "<\n"
+     "           BI_SPACE: > <\n"
+     "           GLOBAL: >top<\n"
+     "         };\n"
+     "         dictionary 'test_SetAddIncludeDictionary/include2#1/sub#1 (intended for baz)' {\n"
+     "           GLOBAL: >21<\n"
+     "         }\n"
+     "     }\n"
+     "}\n");
+  EXPECT_STREQ(dump.c_str(), expected);
+}
+
+static void TestMakeCopy(bool use_local_arena) {
+  UnsafeArena local_arena(1024);
+  UnsafeArena* arena = NULL;
+  if (use_local_arena)
+    arena = &local_arena;
+
+  // First, let's make a non-trivial template dictionary (We use
+  // 'new' because later we'll test deleting this dict but keeping
+  // around the copy.)
+  TemplateDictionary* dict = new TemplateDictionary("testdict", arena);
+
+  dict->SetValue("TOPLEVEL", "foo");
+
+  dict->SetTemplateGlobalValue("TEMPLATELEVEL", "foo3");
+
+  TemplateDictionary* subdict_1a = dict->AddIncludeDictionary("include1");
+  subdict_1a->SetFilename("incfile1a");
+  subdict_1a->SetValue("SUBLEVEL", "subfoo");
+  TemplateDictionary* subdict_1b = dict->AddIncludeDictionary("include1");
+  // Let's try not calling SetFilename on this one.
+  subdict_1b->SetValue("SUBLEVEL", "subbar");
+
+  TemplateDictionary* subdict_2a = dict->AddSectionDictionary("section1");
+  TemplateDictionary* subdict_2b = dict->AddSectionDictionary("section1");
+  subdict_2a->SetValue("SUBLEVEL", "subfoo");
+  subdict_2b->SetValue("SUBLEVEL", "subbar");
+  TemplateDictionary* subdict_3 = dict->AddSectionDictionary("section2");
+  subdict_3->SetValue("TOPLEVEL", "bar");    // overriding top dict
+  TemplateDictionary* subdict_3_1 = subdict_3->AddSectionDictionary("sub");
+  subdict_3_1->SetIntValue("GLOBAL", 21);    // overrides value in setUp()
+
+  string orig;
+  dict->DumpToString(&orig);
+
+  // Make a copy
+  TemplateDictionary* dict_copy = dict->MakeCopy("testdict", NULL);
+  // Make sure it doesn't work to copy a sub-dictionary
+  EXPECT_TRUE(subdict_1a->MakeCopy("copy of subdict") == NULL);
+  EXPECT_TRUE(subdict_2a->MakeCopy("copy of subdict") == NULL);
+
+  // Delete the original dict, to make sure the copy really is independent
+  delete dict;
+  dict = NULL;
+  string copy;
+  dict_copy->DumpToString(&copy);
+  delete dict_copy;
+
+  EXPECT_STREQ(orig.c_str(), copy.c_str());
+}
+
+TEST(MakeCopy, UseLocalArena) {
+  TestMakeCopy(true);
+}
+
+TEST(MakeCopy, DoNotUseLocalArena) {
+  TestMakeCopy(false);
+}
+
+TEST(TemplateDictionary, SetModifierData) {
+  PerExpandData per_expand_data;
+  const void* data = "test";
+  per_expand_data.InsertForModifiers("a", data);
+  EXPECT_EQ(data, per_expand_data.LookupForModifiers("a"));
+}
+
+TEST(TemplateDictionary, Iterator) {
+  // Build up a nice community of TemplateDictionaries.
+  TemplateDictionary farm("Farm");
+  TemplateDictionaryPeer farm_peer(&farm);
+  TemplateDictionaryInterface* grey_barn =
+      farm.AddIncludeDictionary("BARN");
+  TemplateDictionaryInterface* duck_pond =
+      farm.AddIncludeDictionary("POND");
+  TemplateDictionaryInterface* cattle_pond =
+      farm.AddIncludeDictionary("POND");
+  TemplateDictionaryInterface* irrigation_pond =
+      farm.AddIncludeDictionary("POND");
+
+  // A section name with repeated sections
+  TemplateDictionaryInterface* lillies = farm.AddSectionDictionary("FLOWERS");
+  TemplateDictionaryInterface* lilacs = farm.AddSectionDictionary("FLOWERS");
+  TemplateDictionaryInterface* daisies = farm.AddSectionDictionary("FLOWERS");
+  // A section name with one repeat
+  TemplateDictionaryInterface* wheat = farm.AddSectionDictionary("WHEAT");
+  // A section name, just shown
+  farm.ShowSection("CORN");
+
+  // Check that the iterators expose all of the dictionaries.
+  TemplateDictionaryPeer::Iterator* barns =
+      farm_peer.CreateTemplateIterator("BARN");
+  EXPECT_TRUE(barns->HasNext());
+  EXPECT_EQ(&barns->Next(), grey_barn);
+  EXPECT_FALSE(barns->HasNext());
+  delete barns;
+
+  TemplateDictionaryPeer::Iterator* ponds =
+      farm_peer.CreateTemplateIterator("POND");
+  EXPECT_TRUE(ponds->HasNext());
+  EXPECT_EQ(&ponds->Next(), duck_pond);
+  EXPECT_TRUE(ponds->HasNext());
+  EXPECT_EQ(&ponds->Next(), cattle_pond);
+  EXPECT_TRUE(ponds->HasNext());
+  EXPECT_EQ(&ponds->Next(), irrigation_pond);
+  EXPECT_FALSE(ponds->HasNext());
+  delete ponds;
+
+  TemplateDictionaryPeer::Iterator* flowers =
+      farm_peer.CreateSectionIterator("FLOWERS");
+  EXPECT_TRUE(flowers->HasNext());
+  EXPECT_EQ(&flowers->Next(), lillies);
+  EXPECT_TRUE(flowers->HasNext());
+  EXPECT_EQ(&flowers->Next(), lilacs);
+  EXPECT_TRUE(flowers->HasNext());
+  EXPECT_EQ(&flowers->Next(), daisies);
+  EXPECT_FALSE(flowers->HasNext());
+  delete flowers;
+
+  TemplateDictionaryPeer::Iterator* crop =
+      farm_peer.CreateSectionIterator("WHEAT");
+  EXPECT_TRUE(crop->HasNext());
+  EXPECT_EQ(&crop->Next(), wheat);
+  EXPECT_FALSE(crop->HasNext());
+  delete crop;
+
+  TemplateDictionaryPeer::Iterator* corn_crop =
+      farm_peer.CreateSectionIterator("CORN");
+  EXPECT_TRUE(corn_crop->HasNext());
+  EXPECT_TRUE(&corn_crop->Next());  // ShowSection doesn't give us the dict back
+  EXPECT_FALSE(corn_crop->HasNext());
+  delete corn_crop;
+}
+
+TEST(TemplateDictionary, IsHiddenSectionDefault) {
+  TemplateDictionary dict("dict");
+  TemplateDictionaryPeer peer(&dict);
+  EXPECT_TRUE(peer.IsHiddenSection("UNDEFINED"));
+  EXPECT_FALSE(peer.IsUnhiddenSection("UNDEFINED"));
+  dict.ShowSection("VISIBLE");
+  EXPECT_FALSE(peer.IsHiddenSection("VISIBLE"));
+  EXPECT_TRUE(peer.IsUnhiddenSection("VISIBLE"));
+}
+
+// This has to run last, since its SetGlobalValue modifies the global
+// state, which can affect other tests (especially given the embedded
+// NUL!)  So we don't use the normal TEST() here, and call it manually
+// in main().
+
+void TestSetValueWithNUL() {
+  TemplateDictionary dict("test_SetValueWithNUL", NULL);
+  TemplateDictionaryPeer peer(&dict);
+
+  // Test copying char*s, strings, and explicit TemplateStrings
+  dict.SetValue(string("FOO\0BAR", 7), string("QUX\0QUUX", 8));
+  dict.SetGlobalValue(string("GOO\0GAR", 7), string("GUX\0GUUX", 8));
+
+  // FOO should not match FOO\0BAR
+  EXPECT_TRUE(peer.ValueIs("FOO", ""));
+  EXPECT_TRUE(peer.ValueIs("GOO", ""));
+
+  EXPECT_TRUE(peer.ValueIs(string("FOO\0BAR", 7), string("QUX\0QUUX", 8)));
+  EXPECT_TRUE(peer.ValueIs(string("GOO\0GAR", 7), string("GUX\0GUUX", 8)));
+
+  string dump;
+  dict.DumpToString(&dump);
+  // We can't use EXPECT_STREQ here because of the embedded NULs.
+  // They also require I count the length of the string by hand. :-(
+  string expected(("global dictionary {\n"
+                   "   BI_NEWLINE: >\n"
+                   "<\n"
+                   "   BI_SPACE: > <\n"
+                   "   GLOBAL: >top<\n"
+                   "   GOO\0GAR: >GUX\0GUUX<\n"
+                   "};\n"
+                   "dictionary 'test_SetValueWithNUL' {\n"
+                   "   FOO\0BAR: >QUX\0QUUX<\n"
+                   "}\n"),
+                  160);
+  EXPECT_EQ(dump, expected);
+}
+
+TEST(TemplateDictionary, TestShowTemplateGlobalSection) {
+  StringToTemplateCache("test.tpl", "{{#sect}}OK{{/sect}}", DO_NOT_STRIP);
+
+  TemplateDictionary dict("mydict");
+  dict.ShowTemplateGlobalSection("sect");
+
+  string out;
+  ExpandTemplate("test.tpl", DO_NOT_STRIP, &dict, &out);
+}
+
+TEST(TemplateDictionary, TestShowTemplateGlobalSection_Child) {
+  // The TemplateDictionary::template_global_dict_ behaves differently for child
+  // dictionaries than for the root parent dictionary.
+  StringToTemplateCache("test2.tpl",
+                        "{{#foo}}{{#sect}}OK{{/sect}}{{/foo}}",
+                        DO_NOT_STRIP);
+
+  TemplateDictionary dict("mydict");
+  dict.ShowTemplateGlobalSection("sect");
+
+  dict.AddSectionDictionary("foo");
+
+  string out;
+  ExpandTemplate("test2.tpl", DO_NOT_STRIP, &dict, &out);
+}
+
+TEST(TemplateDictionary, TestShowTemplateGlobalSection_SectionDoesntExist) {
+  StringToTemplateCache("test3.tpl",
+                        "{{#bad}}bad{{/bad}}",
+                        DO_NOT_STRIP);
+
+  TemplateDictionary dict("mydict");
+
+  string out;
+  ExpandTemplate("test3.tpl", DO_NOT_STRIP, &dict, &out);
+}
+
+
+}  // unnamed namespace
+
+
+int main(int argc, char** argv) {
+
+  SetUp();
+
+  const int retval = RUN_ALL_TESTS();
+
+  // This has to run last, so we run it manually
+  TestSetValueWithNUL();
+
+  return retval;
+}
diff --git a/src/tests/template_modifiers_unittest.cc b/src/tests/template_modifiers_unittest.cc
new file mode 100644
index 0000000..781d3d3
--- /dev/null
+++ b/src/tests/template_modifiers_unittest.cc
@@ -0,0 +1,1117 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: csilvers@google.com (Craig Silverstein)
+//
+// This code is written to not use the google testing framework
+// as much as possible, to make it easier to opensource.
+
+#include "config_for_unittests.h"
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <string>
+#include <vector>
+#include <ctemplate/template.h>
+#include <ctemplate/template_dictionary.h>
+#include <ctemplate/template_emitter.h>
+#include <ctemplate/template_modifiers.h>
+#include "template_modifiers_internal.h"
+#include "tests/template_test_util.h"
+#include "base/util.h"
+TEST_INIT               // defines RUN_ALL_TESTS
+
+using std::string;
+using std::vector;
+
+// Rather than put all these tests in the ctemplate namespace, or use
+// using-declarations, for this test I've decided to manually prepend
+// GOOGLE_NAMESPACE:: everywhere it's needed.  This test can serve as an
+// example of how that approach looks.
+
+TEST(TemplateModifiers, HtmlEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestHtmlEscape", NULL);
+  dict.SetEscapedValue("easy HTML", "foo",
+                       GOOGLE_NAMESPACE::html_escape);
+  dict.SetEscapedValue("harder HTML", "foo & bar",
+                       GOOGLE_NAMESPACE::html_escape);
+  dict.SetEscapedValue("hardest HTML",
+                       "<A HREF='foo'\nid=\"bar\t\t&&\vbaz\">",
+                       GOOGLE_NAMESPACE::html_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  // TODO(csilvers): change this (and all other expect_*'s in all files
+  // in this directory) to take the expected value first, not second.
+  EXPECT_STREQ(peer.GetSectionValue("easy HTML"), "foo");
+  EXPECT_STREQ(peer.GetSectionValue("harder HTML"), "foo &amp; bar");
+  EXPECT_STREQ(peer.GetSectionValue("hardest HTML"),
+               "&lt;A HREF=&#39;foo&#39; id=&quot;bar  &amp;&amp; "
+               "baz&quot;&gt;");
+}
+
+TEST(TemplateModifiers, SnippetEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestSnippetEscape", NULL);
+  dict.SetEscapedValue("easy snippet", "foo",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("valid snippet",
+                       "<b>foo<br> &amp; b<wbr>&shy;ar</b>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("invalid snippet",
+                       "<b><A HREF='foo'\nid=\"bar\t\t&&{\vbaz\">",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("snippet with italics",
+                       "<i>foo<br> &amp; b<wbr>&shy;ar</i>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unclosed snippet",
+                       "<b>foo",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("snippet with interleaving",
+                       "<b><i>foo</b></i>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unclosed interleaving",
+                       "<b><i><b>foo</b>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unclosed",
+                       "<b><i>foo",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unterminated 1",
+                       "foo<",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unterminated 2",
+                       "foo<b",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unterminated 3",
+                       "foo</",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unterminated 4",
+                       "foo</b",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unterminated 5",
+                       "<b>foo</b",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("close b i",
+                       "<i><b>foo",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("close i b",
+                       "<b><i>foo",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("em",
+                       "<em>foo</em>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("nested em",
+                       "<b>This is foo<em>...</em></b>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unclosed em",
+                       "<em>foo",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("wrongly closed em",
+                       "foo</em>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("misnested em",
+                       "<i><em>foo</i></em>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("span ltr",
+                       "<span dir=ltr>bidi text</span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("span rtl",
+                       "<span dir=rtl>bidi text</span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("span garbage dir attr",
+                       "<span dir=foo>bidi text</span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("span no dir",
+                       "<span>bidi text</span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("span bad attribute",
+                       "<span onclick=alert('foo')>bidi text</span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("span quotes",
+                       "<span dir=\"rtl\">bidi text</span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("nested span",
+                       "<b>This is <span dir=rtl>bidi text</span></b>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("doubly-nested span",
+                       "<span dir=rtl>This is <span dir=rtl>"
+                       "bidi text</span></span>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("two spans",
+                       "<b>This is <span dir=rtl>text</span> that is "
+                       "<span dir=rtl>bidi.</span></b>",
+                       GOOGLE_NAMESPACE::snippet_escape);
+  dict.SetEscapedValue("unclosed span",
+                       "<b>This is <span dir=rtl>bidi text",
+                       GOOGLE_NAMESPACE::snippet_escape);
+
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy snippet"), "foo");
+  EXPECT_STREQ(peer.GetSectionValue("valid snippet"),
+               "<b>foo<br> &amp; b<wbr>&shy;ar</b>");
+  EXPECT_STREQ(peer.GetSectionValue("invalid snippet"),
+               "<b>&lt;A HREF=&#39;foo&#39; id=&quot;bar  &&amp;{ "
+               "baz&quot;&gt;</b>");
+  EXPECT_STREQ(peer.GetSectionValue("snippet with italics"),
+               "<i>foo<br> &amp; b<wbr>&shy;ar</i>");
+  EXPECT_STREQ(peer.GetSectionValue("unclosed snippet"),
+               "<b>foo</b>");
+  EXPECT_STREQ(peer.GetSectionValue("snippet with interleaving"),
+               "<b><i>foo</b></i>");
+  EXPECT_STREQ(peer.GetSectionValue("unclosed interleaving"),
+               "<b><i>&lt;b&gt;foo</b></i>");
+  EXPECT_STREQ(peer.GetSectionValue("unclosed"),
+               "<b><i>foo</i></b>");
+  EXPECT_STREQ(peer.GetSectionValue("unterminated 1"), "foo&lt;");
+  EXPECT_STREQ(peer.GetSectionValue("unterminated 2"), "foo&lt;b");
+  EXPECT_STREQ(peer.GetSectionValue("unterminated 3"), "foo&lt;/");
+  EXPECT_STREQ(peer.GetSectionValue("unterminated 4"), "foo&lt;/b");
+  EXPECT_STREQ(peer.GetSectionValue("unterminated 5"), "<b>foo&lt;/b</b>");
+  EXPECT_STREQ(peer.GetSectionValue("close b i"), "<i><b>foo</b></i>");
+  EXPECT_STREQ(peer.GetSectionValue("close i b"), "<b><i>foo</i></b>");
+  EXPECT_STREQ(peer.GetSectionValue("em"), "<em>foo</em>");
+  EXPECT_STREQ(peer.GetSectionValue("nested em"),
+               "<b>This is foo<em>...</em></b>");
+  EXPECT_STREQ(peer.GetSectionValue("unclosed em"), "<em>foo</em>");
+  EXPECT_STREQ(peer.GetSectionValue("wrongly closed em"), "foo&lt;/em&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("misnested em"), "<i><em>foo</i></em>");
+  EXPECT_STREQ(peer.GetSectionValue("span ltr"),
+               "<span dir=ltr>bidi text</span>");
+  EXPECT_STREQ(peer.GetSectionValue("span rtl"),
+               "<span dir=rtl>bidi text</span>");
+  EXPECT_STREQ(peer.GetSectionValue("span garbage dir attr"),
+               "&lt;span dir=foo&gt;bidi text&lt;/span&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("span no dir"),
+               "&lt;span&gt;bidi text&lt;/span&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("span bad attribute"),
+               "&lt;span onclick=alert(&#39;foo&#39;)&gt;bidi text&lt;/span&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("span quotes"),
+               "&lt;span dir=&quot;rtl&quot;&gt;bidi text&lt;/span&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("nested span"),
+               "<b>This is <span dir=rtl>bidi text</span></b>");
+  EXPECT_STREQ(peer.GetSectionValue("doubly-nested span"),
+               "<span dir=rtl>This is &lt;span dir=rtl&gt;bidi text"
+               "</span>&lt;/span&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("two spans"),
+               "<b>This is <span dir=rtl>text</span> that is "
+               "<span dir=rtl>bidi.</span></b>");
+  EXPECT_STREQ(peer.GetSectionValue("unclosed span"),
+               "<b>This is <span dir=rtl>bidi text</span></b>");
+}
+
+TEST(TemplateModifiers, PreEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestPreEscape", NULL);
+  dict.SetEscapedValue("easy PRE", "foo",
+                       GOOGLE_NAMESPACE::pre_escape);
+  dict.SetEscapedValue("harder PRE", "foo & bar",
+                       GOOGLE_NAMESPACE::pre_escape);
+  dict.SetEscapedValue("hardest PRE",
+                       " \"--\v--\f--\n--\t--&--<-->--'--\"",
+                       GOOGLE_NAMESPACE::pre_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy PRE"), "foo");
+  EXPECT_STREQ(peer.GetSectionValue("harder PRE"), "foo &amp; bar");
+  EXPECT_STREQ(peer.GetSectionValue("hardest PRE"),
+               " &quot;--\v--\f--\n--\t--&amp;--&lt;--&gt;--&#39;--&quot;");
+}
+
+TEST(TemplateModifiers, XmlEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestXmlEscape", NULL);
+  dict.SetEscapedValue("no XML", "",
+                       GOOGLE_NAMESPACE::xml_escape);
+  dict.SetEscapedValue("easy XML", "xoo",
+                       GOOGLE_NAMESPACE::xml_escape);
+  dict.SetEscapedValue("harder XML-1", "<>&'\"",
+                       GOOGLE_NAMESPACE::xml_escape);
+  dict.SetEscapedValue("harder XML-2", "Hello<script>alert('&')</script>",
+                       GOOGLE_NAMESPACE::xml_escape);
+  dict.SetEscapedValue("hardest XML", "<<b>>&!''\"\"foo",
+                       GOOGLE_NAMESPACE::xml_escape);
+  // Characters 0x00-0x1F (except \t, \r and \n) are not valid for XML and
+  // compliant parsers are allowed to die when they encounter them. They
+  // should be replaced with spaces.
+  dict.SetEscapedValue("Spacey XML", " \r\n\f",
+                       GOOGLE_NAMESPACE::xml_escape);
+  dict.SetEscapedValue("XML with control chars",
+                       "\x01\x02\x03\x09\x0A\x0B\x0D\x15\x16\x1F",
+                       GOOGLE_NAMESPACE::xml_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("no XML"), "");
+  EXPECT_STREQ(peer.GetSectionValue("easy XML"), "xoo");
+  EXPECT_STREQ(peer.GetSectionValue("harder XML-1"),
+               "&lt;&gt;&amp;&#39;&quot;");
+  EXPECT_STREQ(peer.GetSectionValue("harder XML-2"),
+               "Hello&lt;script&gt;alert(&#39;&amp;&#39;)&lt;/script&gt;");
+  EXPECT_STREQ(peer.GetSectionValue("hardest XML"),
+               "&lt;&lt;b&gt;&gt;&amp;!&#39;&#39;&quot;&quot;foo");
+  EXPECT_STREQ(peer.GetSectionValue("Spacey XML"),
+               " \r\n ");
+  EXPECT_STREQ(peer.GetSectionValue("XML with control chars"),
+               "   \t\n \r   ");
+}
+
+TEST(TemplateModifiers, ValidateUrlHtmlEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestValidateUrlHtmlEscape", NULL);
+  dict.SetEscapedValue("easy http URL", "http://www.google.com",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+  dict.SetEscapedValue("harder https URL",
+                       "https://www.google.com/search?q=f&hl=en",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+  dict.SetEscapedValue("easy javascript URL",
+                       "javascript:alert(document.cookie)",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+  dict.SetEscapedValue("harder javascript URL",
+                       "javascript:alert(10/5)",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+  dict.SetEscapedValue("easy relative URL",
+                       "foobar.html",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+  dict.SetEscapedValue("harder relative URL",
+                       "/search?q=green flowers&hl=en",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+  dict.SetEscapedValue("ftp URL",
+                       "ftp://ftp.example.org/pub/file.txt",
+                       GOOGLE_NAMESPACE::validate_url_and_html_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy http URL"),
+               "http://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("harder https URL"),
+               "https://www.google.com/search?q=f&amp;hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("easy javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("harder javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("easy relative URL"),
+               "foobar.html");
+  EXPECT_STREQ(peer.GetSectionValue("harder relative URL"),
+               "/search?q=green flowers&amp;hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("ftp URL"),
+               "ftp://ftp.example.org/pub/file.txt");
+}
+
+TEST(TemplateModifiers, ValidateImgSrcUrlHtmlEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestValidateImgSrcUrlHtmlEscape", NULL);
+  dict.SetEscapedValue("easy http URL", "http://www.google.com",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+  dict.SetEscapedValue("harder https URL",
+                       "https://www.google.com/search?q=f&hl=en",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+  dict.SetEscapedValue("easy javascript URL",
+                       "javascript:alert(document.cookie)",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+  dict.SetEscapedValue("harder javascript URL",
+                       "javascript:alert(10/5)",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+  dict.SetEscapedValue("easy relative URL",
+                       "foobar.html",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+  dict.SetEscapedValue("harder relative URL",
+                       "/search?q=green flowers&hl=en",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+  dict.SetEscapedValue("ftp URL",
+                       "ftp://ftp.example.org/pub/file.txt",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_html_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy http URL"),
+               "http://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("harder https URL"),
+               "https://www.google.com/search?q=f&amp;hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("easy javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("harder javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("easy relative URL"),
+               "foobar.html");
+  EXPECT_STREQ(peer.GetSectionValue("harder relative URL"),
+               "/search?q=green flowers&amp;hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("ftp URL"),
+               "ftp://ftp.example.org/pub/file.txt");
+}
+
+TEST(TemplateModifiers, ValidateUrlJavascriptEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestValidateUrlJavascriptEscape", NULL);
+  dict.SetEscapedValue(
+      "easy http URL", "http://www.google.com",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder https URL",
+      "https://www.google.com/search?q=f&hl=en",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "mangled http URL", "HTTP://www.google.com",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "easy javascript URL",
+      "javascript:alert(document.cookie)",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder javascript URL",
+      "javascript:alert(10/5)",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "easy relative URL",
+      "foobar.html",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder relative URL",
+      "/search?q=green flowers&hl=en",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "data URL",
+      "data: text/html",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "mangled javascript URL",
+      "javaSCRIPT:alert(5)",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder mangled javascript URL",
+      "java\nSCRIPT:alert(5)",
+      GOOGLE_NAMESPACE::validate_url_and_javascript_escape);
+
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy http URL"),
+               "http://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("harder https URL"),
+               "https://www.google.com/search?q\\x3df\\x26hl\\x3den");
+  EXPECT_STREQ(peer.GetSectionValue("mangled http URL"),
+               "HTTP://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("easy javascript URL"), "#");
+  EXPECT_STREQ(peer.GetSectionValue("harder javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("easy relative URL"),
+               "foobar.html");
+  EXPECT_STREQ(peer.GetSectionValue("harder relative URL"),
+               "/search?q\\x3dgreen flowers\\x26hl\\x3den");
+  EXPECT_STREQ(peer.GetSectionValue("data URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("mangled javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("harder mangled javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+}
+
+TEST(TemplateModifiers, ValidateImgSrcUrlJavascriptEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestValidateImgSrcUrlJavascriptEscape", NULL);
+  dict.SetEscapedValue(
+      "easy http URL", "http://www.google.com",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder https URL",
+      "https://www.google.com/search?q=f&hl=en",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "mangled http URL", "HTTP://www.google.com",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "easy javascript URL",
+      "javascript:alert(document.cookie)",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder javascript URL",
+      "javascript:alert(10/5)",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "easy relative URL",
+      "foobar.html",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder relative URL",
+      "/search?q=green flowers&hl=en",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "data URL",
+      "data: text/html",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "mangled javascript URL",
+      "javaSCRIPT:alert(5)",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+  dict.SetEscapedValue(
+      "harder mangled javascript URL",
+      "java\nSCRIPT:alert(5)",
+      GOOGLE_NAMESPACE::validate_img_src_url_and_javascript_escape);
+
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy http URL"),
+               "http://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("harder https URL"),
+               "https://www.google.com/search?q\\x3df\\x26hl\\x3den");
+  EXPECT_STREQ(peer.GetSectionValue("mangled http URL"),
+               "HTTP://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("easy javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("harder javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("easy relative URL"),
+               "foobar.html");
+  EXPECT_STREQ(peer.GetSectionValue("harder relative URL"),
+               "/search?q\\x3dgreen flowers\\x26hl\\x3den");
+  EXPECT_STREQ(peer.GetSectionValue("data URL"),
+               "/images/cleardot.gif");
+  EXPECT_STREQ(peer.GetSectionValue("mangled javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("harder mangled javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+}
+
+TEST(TemplateModifiers, ValidateUrlCssEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestValidateUrlCssEscape", NULL);
+  dict.SetEscapedValue("easy http URL", "http://www.google.com",
+                       GOOGLE_NAMESPACE::validate_url_and_css_escape);
+  dict.SetEscapedValue("harder https URL",
+                       "https://www.google.com/search?q=f&hl=en",
+                       GOOGLE_NAMESPACE::validate_url_and_css_escape);
+  dict.SetEscapedValue("javascript URL",
+                       "javascript:alert(document.cookie)",
+                       GOOGLE_NAMESPACE::validate_url_and_css_escape);
+  dict.SetEscapedValue("relative URL", "/search?q=green flowers&hl=en",
+                       GOOGLE_NAMESPACE::validate_url_and_css_escape);
+  dict.SetEscapedValue("hardest URL", "http://www.google.com/s?q='bla'"
+                       "&a=\"\"&b=(<tag>)&c=*\r\n\\\\bla",
+                       GOOGLE_NAMESPACE::validate_url_and_css_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy http URL"),
+               "http://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("harder https URL"),
+               "https://www.google.com/search?q=f&hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("relative URL"),
+               "/search?q=green flowers&hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("hardest URL"),
+               "http://www.google.com/s?q=%27bla%27"
+               "&a=%22%22&b=%28%3Ctag%3E%29&c=%2A%0D%0A%5C%5Cbla");
+}
+
+TEST(TemplateModifiers, ValidateImgSrcUrlCssEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestValidateImgSrcUrlCssEscape", NULL);
+  dict.SetEscapedValue("easy http URL", "http://www.google.com",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_css_escape);
+  dict.SetEscapedValue("harder https URL",
+                       "https://www.google.com/search?q=f&hl=en",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_css_escape);
+  dict.SetEscapedValue("javascript URL",
+                       "javascript:alert(document.cookie)",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_css_escape);
+  dict.SetEscapedValue("relative URL", "/search?q=green flowers&hl=en",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_css_escape);
+  dict.SetEscapedValue("hardest URL", "http://www.google.com/s?q='bla'"
+                       "&a=\"\"&b=(<tag>)&c=*\r\n\\\\bla",
+                       GOOGLE_NAMESPACE::validate_img_src_url_and_css_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy http URL"),
+               "http://www.google.com");
+  EXPECT_STREQ(peer.GetSectionValue("harder https URL"),
+               "https://www.google.com/search?q=f&hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("javascript URL"),
+               GOOGLE_NAMESPACE::ValidateUrl::kUnsafeImgSrcUrlReplacement);
+  EXPECT_STREQ(peer.GetSectionValue("relative URL"),
+               "/search?q=green flowers&hl=en");
+  EXPECT_STREQ(peer.GetSectionValue("hardest URL"),
+               "http://www.google.com/s?q=%27bla%27"
+               "&a=%22%22&b=%28%3Ctag%3E%29&c=%2A%0D%0A%5C%5Cbla");
+}
+
+TEST(TemplateModifiers, CleanseAttribute) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestCleanseAttribute", NULL);
+  dict.SetEscapedValue("easy attribute", "top",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("harder attribute", "foo & bar",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("hardest attribute",
+                       "top onclick='alert(document.cookie)'",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("equal in middle", "foo = bar",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("leading equal", "=foo",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("trailing equal", "foo=",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("all equals", "===foo===bar===",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+  dict.SetEscapedValue("just equals", "===",
+                       GOOGLE_NAMESPACE::cleanse_attribute);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy attribute"), "top");
+  EXPECT_STREQ(peer.GetSectionValue("harder attribute"), "foo___bar");
+  EXPECT_STREQ(peer.GetSectionValue("hardest attribute"),
+               "top_onclick=_alert_document.cookie__");
+
+  EXPECT_STREQ(peer.GetSectionValue("equal in middle"), "foo_=_bar");
+  EXPECT_STREQ(peer.GetSectionValue("leading equal"), "_foo");
+  EXPECT_STREQ(peer.GetSectionValue("trailing equal"), "foo_");
+  EXPECT_STREQ(peer.GetSectionValue("just equals"), "_=_");
+  EXPECT_STREQ(peer.GetSectionValue("all equals"), "_==foo===bar==_");
+}
+
+TEST(TemplateModifiers, CleanseCss) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestCleanseCss", NULL);
+  dict.SetEscapedValue("easy css", "top",
+                       GOOGLE_NAMESPACE::cleanse_css);
+  dict.SetEscapedValue("harder css", "foo & bar",
+                       GOOGLE_NAMESPACE::cleanse_css);
+  dict.SetEscapedValue("hardest css",
+                       ";width:expression(document.cookie)",
+                       GOOGLE_NAMESPACE::cleanse_css);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy css"),
+               "top");
+  EXPECT_STREQ(peer.GetSectionValue("harder css"),
+               "foo  bar");
+  EXPECT_STREQ(peer.GetSectionValue("hardest css"),
+               "widthexpressiondocument.cookie");
+}
+
+TEST(TemplateModifiers, JavascriptEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestJavascriptEscape", NULL);
+  dict.SetEscapedValue("easy JS", "joo",
+                       GOOGLE_NAMESPACE::javascript_escape);
+  dict.SetEscapedValue("harder JS", "f = 'joo';",
+                       GOOGLE_NAMESPACE::javascript_escape);
+  dict.SetEscapedValue("hardest JS",
+                       ("f = 'foo\f';\r\n\tprint \"\\&foo = \b\", \"foo\""),
+                       GOOGLE_NAMESPACE::javascript_escape);
+  dict.SetEscapedValue("close script JS",
+                       "//--></script><script>alert(123);</script>",
+                       GOOGLE_NAMESPACE::javascript_escape);
+  dict.SetEscapedValue("unicode codepoints",
+                       ("line1" "\xe2\x80\xa8" "line2" "\xe2\x80\xa9" "line3"
+                        /* \u2027 */ "\xe2\x80\xa7"
+                        /* \u202A */ "\xe2\x80\xaa"
+                        /* malformed */ "\xe2" "\xe2\x80\xa8"
+                        /* truncated */ "\xe2\x80"),
+                       GOOGLE_NAMESPACE::javascript_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy JS"), "joo");
+  EXPECT_STREQ(peer.GetSectionValue("harder JS"), "f \\x3d \\x27joo\\x27;");
+  EXPECT_STREQ(peer.GetSectionValue("hardest JS"),
+               "f \\x3d \\x27foo\\f\\x27;\\r\\n\\tprint \\x22\\\\\\x26foo "
+               "\\x3d \\b\\x22, \\x22foo\\x22");
+  EXPECT_STREQ(peer.GetSectionValue("close script JS"),
+               "//--\\x3e\\x3c/script\\x3e\\x3cscript\\x3e"
+               "alert(123);\\x3c/script\\x3e");
+  EXPECT_STREQ(peer.GetSectionValue("unicode codepoints"),
+               "line1" "\\u2028" "line2" "\\u2029" "line3"
+               "\xe2\x80\xa7"
+               "\xe2\x80\xaa"
+               "\xe2" "\\u2028"
+               "\xe2\x80");
+}
+
+TEST(TemplateModifiers, JavascriptNumber) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestJavascriptNumber", NULL);
+  dict.SetEscapedValue("empty string", "",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("boolean true", "true",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("boolean false", "false",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad boolean 1", "tfalse",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad boolean 2", "tru",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad boolean 3", "truee",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad boolean 4", "invalid",
+                       GOOGLE_NAMESPACE::javascript_number);
+
+  // Check that our string comparisons for booleans do not
+  // assume input is null terminated.
+  dict.SetEscapedValue("good boolean 5", GOOGLE_NAMESPACE::TemplateString("truee", 4),
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad boolean 6", GOOGLE_NAMESPACE::TemplateString("true", 3),
+                       GOOGLE_NAMESPACE::javascript_number);
+
+  dict.SetEscapedValue("hex number 1", "0x123456789ABCDEF",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("hex number 2", "0X123456789ABCDEF",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad hex number 1", "0x123GAC",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("bad hex number 2", "0x",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("number zero", "0",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("invalid number", "A9",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("decimal zero", "0.0",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("octal number", "01234567",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("decimal number", "799.123",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("negative number", "-244",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("positive number", "+244",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("valid float 1", ".55",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("valid float 2", "8.55e-12",
+                       GOOGLE_NAMESPACE::javascript_number);
+  dict.SetEscapedValue("invalid float", "8.55ABC",
+                       GOOGLE_NAMESPACE::javascript_number);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("empty string"), "");
+  EXPECT_STREQ(peer.GetSectionValue("boolean true"), "true");
+  EXPECT_STREQ(peer.GetSectionValue("boolean false"), "false");
+  EXPECT_STREQ(peer.GetSectionValue("bad boolean 1"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("bad boolean 2"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("bad boolean 3"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("bad boolean 4"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("good boolean 5"), "true");
+  EXPECT_STREQ(peer.GetSectionValue("bad boolean 6"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("hex number 1"), "0x123456789ABCDEF");
+  EXPECT_STREQ(peer.GetSectionValue("hex number 2"), "0X123456789ABCDEF");
+  EXPECT_STREQ(peer.GetSectionValue("bad hex number 1"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("bad hex number 2"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("number zero"), "0");
+  EXPECT_STREQ(peer.GetSectionValue("invalid number"), "null");
+  EXPECT_STREQ(peer.GetSectionValue("decimal zero"), "0.0");
+  EXPECT_STREQ(peer.GetSectionValue("octal number"), "01234567");
+  EXPECT_STREQ(peer.GetSectionValue("decimal number"), "799.123");
+  EXPECT_STREQ(peer.GetSectionValue("negative number"), "-244");
+  EXPECT_STREQ(peer.GetSectionValue("positive number"), "+244");
+  EXPECT_STREQ(peer.GetSectionValue("valid float 1"), ".55");
+  EXPECT_STREQ(peer.GetSectionValue("valid float 2"), "8.55e-12");
+  EXPECT_STREQ(peer.GetSectionValue("invalid float"), "null");
+}
+
+TEST(TemplateModifiers, JsonEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestJsonEscape", NULL);
+  dict.SetEscapedValue("easy JSON", "joo",
+                       GOOGLE_NAMESPACE::json_escape);
+  dict.SetEscapedValue("harder JSON", "f = \"joo\"; e = 'joo';",
+                       GOOGLE_NAMESPACE::json_escape);
+  dict.SetEscapedValue("hardest JSON",
+                       "f = 'foo<>';\r\n\t\fprint \"\\&foo = /\b\", \"foo\"",
+                       GOOGLE_NAMESPACE::json_escape);
+  dict.SetEscapedValue("html in JSON", "<html>&nbsp;</html>",
+                       GOOGLE_NAMESPACE::json_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("easy JSON"), "joo");
+  EXPECT_STREQ(peer.GetSectionValue("harder JSON"), "f = \\\"joo\\\"; "
+               "e = 'joo';");
+  EXPECT_STREQ(peer.GetSectionValue("html in JSON"),
+               "\\u003Chtml\\u003E\\u0026nbsp;\\u003C\\/html\\u003E");
+  // There's a bug in MSVC 7.1 where you can't pass a literal string
+  // with more than one \" in it to a macro (!) -- see
+  //    http://marc.info/?t=110853662500001&r=1&w=2
+  // We work around this by assigning the string to a variable first.
+  const char* expected = ("f = 'foo\\u003C\\u003E';\\r\\n\\t\\fprint \\\""
+                          "\\\\\\u0026foo = \\/\\b\\\", \\\"foo\\\"");
+  EXPECT_STREQ(peer.GetSectionValue("hardest JSON"), expected);
+}
+
+TEST(TemplateModifiers, UrlQueryEscape) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestUrlQueryEscape", NULL);
+  // The first three tests do not need escaping.
+  dict.SetEscapedValue("query escape 0", "",
+                       GOOGLE_NAMESPACE::url_query_escape);
+  dict.SetEscapedValue("query escape 1", "noop",
+                       GOOGLE_NAMESPACE::url_query_escape);
+  dict.SetEscapedValue("query escape 2",
+                       "0123456789abcdefghjijklmnopqrstuvwxyz"
+                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_*/~!(),",
+                       GOOGLE_NAMESPACE::url_query_escape);
+  dict.SetEscapedValue("query escape 3", " ?a=b;c#d ",
+                       GOOGLE_NAMESPACE::url_query_escape);
+  dict.SetEscapedValue("query escape 4", "#$%&+<=>?@[\\]^`{|}",
+                       GOOGLE_NAMESPACE::url_query_escape);
+  dict.SetEscapedValue("query escape 5", "\xDE\xAD\xCA\xFE",
+                       GOOGLE_NAMESPACE::url_query_escape);
+  dict.SetEscapedValue("query escape 6", "\"':",
+                       GOOGLE_NAMESPACE::url_query_escape);
+
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&dict);  // peer can look inside dicts
+  EXPECT_STREQ(peer.GetSectionValue("query escape 0"), "");
+  EXPECT_STREQ(peer.GetSectionValue("query escape 1"), "noop");
+  EXPECT_STREQ(peer.GetSectionValue("query escape 2"),
+               "0123456789abcdefghjijklmnopqrstuvwxyz"
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_*/~!(),");
+  EXPECT_STREQ(peer.GetSectionValue("query escape 3"), "+%3Fa%3Db%3Bc%23d+");
+  EXPECT_STREQ(peer.GetSectionValue("query escape 4"),
+               "%23%24%25%26%2B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D");
+  EXPECT_STREQ(peer.GetSectionValue("query escape 5"), "%DE%AD%CA%FE");
+  EXPECT_STREQ(peer.GetSectionValue("query escape 6"), "%22%27%3A");
+}
+
+TEST(TemplateModifiers, PrefixLine) {
+  GOOGLE_NAMESPACE::TemplateDictionary dict("TestPrefixLine", NULL);
+  // These don't escape: we don't put the prefix before the first line
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1", "   ").c_str(),
+               "pt 1");
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1", "::").c_str(),
+               "pt 1");
+
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\npt 2", ":").c_str(),
+               "pt 1\n:pt 2");
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\npt 2", " ").c_str(),
+               "pt 1\n pt 2");
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\npt 2", "\n").c_str(),
+               "pt 1\n\npt 2");
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\npt 2\n", "  ").c_str(),
+               "pt 1\n  pt 2\n  ");
+
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\rpt 2\n", ":").c_str(),
+               "pt 1\r:pt 2\n:");
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\npt 2\r", ":").c_str(),
+               "pt 1\n:pt 2\r:");
+  EXPECT_STREQ(GOOGLE_NAMESPACE::prefix_line("pt 1\r\npt 2\r", ":").c_str(),
+               "pt 1\r\n:pt 2\r:");
+}
+
+TEST(TemplateModifiers, FindModifier) {
+  const GOOGLE_NAMESPACE::ModifierInfo* info;
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("html_escape", 11, "", 0)));
+  EXPECT_EQ(info->modifier, &GOOGLE_NAMESPACE::html_escape);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("h", 1, "", 0)));
+  EXPECT_EQ(info->modifier, &GOOGLE_NAMESPACE::html_escape);
+
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("html_escape_with_arg", 20,
+                                              "=pre", 4)));
+  EXPECT_EQ(info->modifier, &GOOGLE_NAMESPACE::pre_escape);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("H", 1, "=pre", 4)));
+  EXPECT_EQ(info->modifier, &GOOGLE_NAMESPACE::pre_escape);
+
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("javascript_escape_with_arg",
+                                              26, "=number", 7)));
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("J", 1, "=number", 7)));
+  EXPECT_EQ(info->modifier, &GOOGLE_NAMESPACE::javascript_number);
+
+  // html_escape_with_arg doesn't have a default value, so these should fail.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("H", 1, "=pre", 2));  // "=p"
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("H", 1, "=pree", 5));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("H", 1, "=notpresent", 11));
+
+  // If we don't have a modifier-value when we ought, we should fail.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("html_escape", 11, "=p", 2));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("h", 1, "=p", 2));
+
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("html_escape_with_arg", 20,
+                                       "", 0));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::FindModifier("H", 1, "", 0));
+
+  // Test with added modifiers as well.
+  GOOGLE_NAMESPACE::NullModifier foo_modifier1;
+  GOOGLE_NAMESPACE::NullModifier foo_modifier2;
+  GOOGLE_NAMESPACE::NullModifier foo_modifier3;
+  GOOGLE_NAMESPACE::NullModifier foo_modifier4;
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-test", &foo_modifier1));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-test-arg=", &foo_modifier2));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-test-arg=h", &foo_modifier3));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-test-arg=json", &foo_modifier4));
+
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test", 6, "", 0)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier1);
+  EXPECT_EQ(info->xss_class, GOOGLE_NAMESPACE::XSS_UNIQUE);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test", 6, "=h", 2)));
+  EXPECT_FALSE(info->is_registered);
+  // This tests default values
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10, "=p", 2)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier2);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10, "=h", 2)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier3);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10, "=html", 5)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier2);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10, "=json", 5)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier4);
+  // The value is required to start with an '=' to match the
+  // specialization.  If it doesn't, it will match the default.
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10, "json", 4)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier2);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10,
+                                              "=jsonnabbe", 5)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier4);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10,
+                                              "=jsonnabbe", 6)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier2);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-test-arg", 10,
+                                              "=jsonnabbe", 4)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->modifier, &foo_modifier2);
+
+  // If we try to find an x- modifier that wasn't added, we should get
+  // a legit but "unknown" modifier back.
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-foo", 5, "", 0)));
+  EXPECT_FALSE(info->is_registered);
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-bar", 5, "=p", 2)));
+  EXPECT_FALSE(info->is_registered);
+
+  // Basic test with added XssSafe modifier.
+  GOOGLE_NAMESPACE::NullModifier foo_modifier5;
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-safetest",
+                                            &foo_modifier5));
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-safetest", 10, "", 0)));
+  EXPECT_TRUE(info->is_registered);
+  EXPECT_EQ(info->xss_class, GOOGLE_NAMESPACE::XSS_SAFE);
+  EXPECT_EQ(info->modifier, &foo_modifier5);
+}
+
+TEST(TemplateModifiers, AddModifier) {
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=h", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=html", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=json", &GOOGLE_NAMESPACE::json_escape));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=j", &GOOGLE_NAMESPACE::json_escape));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=J", &GOOGLE_NAMESPACE::json_escape));
+
+  // Make sure AddModifier fails with an invalid name.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("test", &GOOGLE_NAMESPACE::html_escape));
+
+  // Make sure AddModifier fails with a duplicate name.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-atest", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=h", &GOOGLE_NAMESPACE::html_escape));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-atest-arg=html", &GOOGLE_NAMESPACE::html_escape));
+
+  const GOOGLE_NAMESPACE::ModifierInfo* info;
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-atest", 7, "", 0)));
+  EXPECT_FALSE(info->modval_required);
+
+  // Make sure we can still add a modifier after having already
+  // searched for it.
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-foo", 5, "", 0)));
+  EXPECT_FALSE(info->is_registered);
+
+  GOOGLE_NAMESPACE::NullModifier foo_modifier;
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-foo", &foo_modifier));
+  EXPECT_TRUE((info = GOOGLE_NAMESPACE::FindModifier("x-foo", 5, "", 0)));
+  EXPECT_EQ(info->modifier, &foo_modifier);
+}
+
+TEST(TemplateModifiers, AddXssSafeModifier) {
+  // For shorter lines.
+  const GOOGLE_NAMESPACE::TemplateModifier* esc_fn =
+      &GOOGLE_NAMESPACE::html_escape;
+
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-asafetest", esc_fn));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-asafetest-arg=", esc_fn));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-asafetest-arg=h", esc_fn));
+
+  // Make sure AddXssSafeModifier fails with an invalid name.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("test", esc_fn));
+
+  // Make sure AddXssSafeModifier fails with a duplicate name.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-asafetest", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-asafetest-arg=", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-asafetest-arg=h",
+                                             esc_fn));
+
+  // Make sure AddXssSafeModifier fails if the same modifier was
+  // previously added via AddModifier.
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-safetest2", esc_fn));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-safetest2-arg=", esc_fn));
+  EXPECT_TRUE(GOOGLE_NAMESPACE::AddModifier("x-safetest2-arg=h", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-safetest2", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-safetest2-arg=", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddXssSafeModifier("x-safetest2-arg=h", esc_fn));
+
+  // and vice versa.
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-asafetest", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-asafetest-arg=", esc_fn));
+  EXPECT_FALSE(GOOGLE_NAMESPACE::AddModifier("x-asafetest-arg=h", esc_fn));
+}
+
+// Helper function. Determines whether the Modifier specified by
+// alt_modname/alt_modval is a safe XSS alternative to
+// the Modifier specified by modname/modval.
+static bool CheckXSSAlternative(const string& modname, const string& modval,
+                                const string& alt_modname,
+                                const string& alt_modval) {
+  const GOOGLE_NAMESPACE::ModifierInfo *mod, *alt_mod;
+  mod = GOOGLE_NAMESPACE::FindModifier(modname.c_str(), modname.length(),
+                                modval.c_str(), modval.length());
+  alt_mod = GOOGLE_NAMESPACE::FindModifier(alt_modname.c_str(),
+                                    alt_modname.length(),
+                                    alt_modval.c_str(),
+                                    alt_modval.length());
+  EXPECT_TRUE(mod != NULL && alt_mod != NULL);
+  return IsSafeXSSAlternative(*mod, *alt_mod);
+}
+
+TEST(TemplateModifiers, XSSAlternatives) {
+  // A modifier is always a safe replacement to itself, even non built-in.
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "h", ""));
+  EXPECT_TRUE(CheckXSSAlternative("url_escape_with_arg", "=javascript",
+                                  "url_escape_with_arg", "=javascript"));
+  EXPECT_TRUE(CheckXSSAlternative("x-bla", "", "x-bla", ""));
+
+  // A built-in modifier is always a safe replacement to
+  // another with the same function.
+  EXPECT_TRUE(CheckXSSAlternative("H", "=pre", "p", ""));
+  EXPECT_TRUE(CheckXSSAlternative("url_query_escape", "",
+                                  "url_escape_with_arg", "=query"));
+
+  // H=(pre|snippet|attribute), p, u, U=query, U=html (a.k.a H=url)
+  // and I=html are all alternatives to h.
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "H", "=pre"));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "H", "=snippet"));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "H", "=attribute"));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "H", "=url"));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "p", ""));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "u", ""));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "U", "=query"));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "U", "=html"));
+  EXPECT_TRUE(CheckXSSAlternative("h", "", "I", "=html"));
+
+  // But h is not an alternative to H=attribute and I=html,
+  // nor is json_escape an alternative to h.
+  EXPECT_FALSE(CheckXSSAlternative("H", "=attribute", "h", ""));
+  EXPECT_FALSE(CheckXSSAlternative("I", "=html", "h", ""));
+  EXPECT_FALSE(CheckXSSAlternative("h", "", "json_escape", ""));
+
+  // H=snippet and H=attribute are alternatives to H=pre
+  // But H=pre is not an alternative to H=attribute.
+  EXPECT_TRUE(CheckXSSAlternative("H", "=pre", "H", "=snippet"));
+  EXPECT_TRUE(CheckXSSAlternative("H", "=pre", "H", "=attribute"));
+  EXPECT_FALSE(CheckXSSAlternative("H", "=attribute", "H", "=pre"));
+
+  // javascript_escape is an alternative to json_escape and vice versa
+  EXPECT_TRUE(CheckXSSAlternative("json_escape", "", "javascript_escape", ""));
+  EXPECT_TRUE(CheckXSSAlternative("javascript_escape", "", "json_escape", ""));
+
+  // I=javascript is an alternative to :j and :U=javascript but not
+  // vice versa
+  EXPECT_TRUE(CheckXSSAlternative("javascript_escape", "", "I", "=javascript"));
+  EXPECT_TRUE(CheckXSSAlternative("U", "=javascript", "I", "=javascript"));
+  EXPECT_FALSE(CheckXSSAlternative("I", "=javascript", "javascript_escape", ""));
+  EXPECT_FALSE(CheckXSSAlternative("I", "=javascript", "U", "=javascript"));
+
+  // U=css and I=css are alternatives to :c but not vice versa
+  EXPECT_TRUE(CheckXSSAlternative("c", "", "U", "=css"));
+  EXPECT_TRUE(CheckXSSAlternative("c", "", "I", "=css"));
+  EXPECT_FALSE(CheckXSSAlternative("U", "=css", "c", ""));
+  EXPECT_FALSE(CheckXSSAlternative("I", "=css", "c", ""));
+
+  // Extended modifier should not match any other except itself.
+  EXPECT_FALSE(CheckXSSAlternative("x-bla", "", "x-foo", ""));
+}
+
+// This is a basic sanity check for the GetDefaultModifierForXXX() functions.
+// More testing happens in AutoEscaper code which uses them.
+TEST(TemplateModifiers, DefaultModifiersForContext) {
+  const GOOGLE_NAMESPACE::ModifierAndValue* modval;
+  string print_mods;
+
+  const vector<const GOOGLE_NAMESPACE::ModifierAndValue*> modvals_html =
+      GOOGLE_NAMESPACE::GetDefaultModifierForHtml();
+  EXPECT_EQ(1, modvals_html.size());
+  print_mods = GOOGLE_NAMESPACE::PrettyPrintModifiers(modvals_html, ";");
+  EXPECT_STREQ(":h", print_mods.c_str());
+  modval = modvals_html.front();
+  EXPECT_EQ(modval->modifier_info->modifier, &GOOGLE_NAMESPACE::html_escape);
+
+  const vector<const GOOGLE_NAMESPACE::ModifierAndValue*> modvals_js =
+      GOOGLE_NAMESPACE::GetDefaultModifierForJs();
+  EXPECT_EQ(1, modvals_js.size());
+  print_mods = GOOGLE_NAMESPACE::PrettyPrintModifiers(modvals_js, ";");
+  EXPECT_STREQ(":j", print_mods.c_str());
+  modval = modvals_js.front();
+  EXPECT_EQ(modval->modifier_info->modifier, &GOOGLE_NAMESPACE::javascript_escape);
+
+  const vector<const GOOGLE_NAMESPACE::ModifierAndValue*> modvals_xml =
+      GOOGLE_NAMESPACE::GetDefaultModifierForXml();
+  EXPECT_EQ(1, modvals_xml.size());
+  print_mods = GOOGLE_NAMESPACE::PrettyPrintModifiers(modvals_xml, ";");
+  EXPECT_STREQ(":xml_escape", print_mods.c_str());
+  modval = modvals_xml.front();
+  EXPECT_EQ(modval->modifier_info->modifier, &GOOGLE_NAMESPACE::xml_escape);
+
+  const vector<const GOOGLE_NAMESPACE::ModifierAndValue*> modvals_json =
+      GOOGLE_NAMESPACE::GetDefaultModifierForJson();
+  EXPECT_EQ(1, modvals_json.size());
+  print_mods = GOOGLE_NAMESPACE::PrettyPrintModifiers(modvals_json, ";");
+  EXPECT_STREQ(":j", print_mods.c_str());
+  modval = modvals_json.front();
+  EXPECT_EQ(modval->modifier_info->modifier, &GOOGLE_NAMESPACE::javascript_escape);
+}
+
+// This tests for a bug we had where we were returning a pointer into
+// a vector that became invalid after the vector was resized.
+TEST(TemplateModifiers, ManyUnknownModifiers) {
+  string tpl_str1 = "{{from_name:x-test=4}} sent you a message";
+  const GOOGLE_NAMESPACE::Template* tpl1 = GOOGLE_NAMESPACE::Template::StringToTemplate(
+      tpl_str1, GOOGLE_NAMESPACE::DO_NOT_STRIP);
+
+  string tpl_str2 = "{{from_name:x-test=4}} sent you a message:";
+  string expected_out = "me sent you a message:";
+  // All those new unknown varnames should cause g_unknown_modifiers
+  // to resize.  1111 is an arbitrary large number.
+  for (int i = 0; i < 1111; i++) {
+    tpl_str2.append("{{from_name:x-" + string(i, 't') + "=4}}");
+    expected_out.append("me");
+  }
+  const GOOGLE_NAMESPACE::Template* tpl2 = GOOGLE_NAMESPACE::Template::StringToTemplate(
+      tpl_str2, GOOGLE_NAMESPACE::DO_NOT_STRIP);
+
+  // Even after the resizing, the references to the unknown
+  // modifiers in tpl1 and tpl2 should still be valid.
+  GOOGLE_NAMESPACE::TemplateDictionary dict("test");
+  dict.SetValue("from_name", "me");
+  string out;
+
+  out.clear();
+  tpl1->Expand(&out, &dict);
+  EXPECT_STREQ("me sent you a message", out.c_str());
+  delete tpl1;
+
+  out.clear();
+  tpl2->Expand(&out, &dict);
+  EXPECT_STREQ(expected_out.c_str(), out.c_str());
+  delete tpl2;
+}
+
+
+int main(int argc, char** argv) {
+
+  return RUN_ALL_TESTS();
+}
diff --git a/src/tests/template_regtest.cc b/src/tests/template_regtest.cc
new file mode 100644
index 0000000..bc1297f
--- /dev/null
+++ b/src/tests/template_regtest.cc
@@ -0,0 +1,498 @@
+// Copyright (c) 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: csilvers@google.com (Craig Silverstein)
+//
+// This test consists of creating a pretty complicated
+// dictionary, and then applying it to a bunch of templates
+// (specified in the testdata dir) and making sure the output
+// is as expected.  We actually support testing with multiple
+// dictionaries.  We glob the testdat dir, so it's possible to
+// add a new test just by creating a template and expected-output
+// file in the testdata directory.  Files are named
+//    template_unittest_testXX.in
+//    template_unittest_testXX_dictYY.out
+// YY should start with 01 (not 00).  XX can be an arbitrary string.
+
+#include "config_for_unittests.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>       // for opendir() etc
+#else
+# define dirent direct
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif       // for opendir() etc
+#include <algorithm>      // for sort() and stable_partition
+#include <string>
+#include <vector>
+#include <ctemplate/template.h>
+#include <ctemplate/template_dictionary.h>
+#include <ctemplate/template_modifiers.h>
+#include <ctemplate/template_pathops.h>
+#include "base/util.h"
+
+using std::vector;
+using std::string;
+using std::sort;
+
+using GOOGLE_NAMESPACE::DO_NOT_STRIP;
+using GOOGLE_NAMESPACE::PerExpandData;
+using GOOGLE_NAMESPACE::STRIP_BLANK_LINES;
+using GOOGLE_NAMESPACE::STRIP_WHITESPACE;
+using GOOGLE_NAMESPACE::TC_HTML;
+using GOOGLE_NAMESPACE::TC_MANUAL;
+using GOOGLE_NAMESPACE::Template;
+using GOOGLE_NAMESPACE::TemplateDictionary;
+
+#define ASSERT(cond)  do {                                      \
+  if (!(cond)) {                                                \
+    printf("%s: %d: ASSERT FAILED: %s\n", __FILE__, __LINE__,   \
+           #cond);                                              \
+    assert(cond);                                               \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+#define ASSERT_STRING_EQ(a, b)   do {                   \
+    assert(StringEq(a, b, __FILE__, __LINE__, #a, #b)); \
+} while (0)
+
+bool StringEq(const string& a, const string& b,
+                    const char* filename, int lineno,
+                    const char* namea, const char* nameb) {
+  if (a != b) {
+    printf("%s: %d: ASSERT FAILED: %s == %s:\n", filename, lineno,
+           namea, nameb);
+    printf("EXPECTED:\n%s\n", a.c_str());
+    printf("ACTUAL:\n%s\n", b.c_str());
+    return false;
+  }
+  return true;
+}
+
+#define ASSERT_STREQ_EXCEPT(a, b, except)  ASSERT(StreqExcept(a, b, except))
+#define ASSERT_STREQ(a, b)                 ASSERT(strcmp(a, b) == 0)
+#define ASSERT_NOT_STREQ(a, b)             ASSERT(strcmp(a, b) != 0)
+
+// First, (conceptually) remove all chars in "except" from both a and b.
+// Then return true iff munged_a == munged_b.
+bool StreqExcept(const char* a, const char* b, const char* except) {
+  const char* pa = a, *pb = b;
+  const size_t exceptlen = strlen(except);
+  while (1) {
+    // Use memchr instead of strchr because strchr(foo, '\0') always fails
+    while (memchr(except, *pa, exceptlen))  pa++;   // ignore "except" chars in a
+    while (memchr(except, *pb, exceptlen))  pb++;   // ignore "except" chars in b
+    if ((*pa == '\0') && (*pb == '\0'))
+      return true;
+    if (*pa++ != *pb++)                  // includes case where one is at \0
+      return false;
+  }
+}
+
+
+RegisterTemplateFilename(VALID1_FN, "template_unittest_test_valid1.in");
+RegisterTemplateFilename(INVALID1_FN, "template_unittest_test_invalid1.in");
+RegisterTemplateFilename(INVALID2_FN, "template_unittest_test_invalid2.in");
+RegisterTemplateFilename(NONEXISTENT_FN, "nonexistent__file.tpl");
+
+struct Testdata {
+  string input_template_name;   // the filename of the input template
+  string input_template;        // the contents of the input template
+  vector<string> output;        // entry i is the output of using dict i.
+  vector<string> annotated_output;  // used to test annotations
+};
+
+static void ReadToString(const string& filename, string* s) {
+  const int bufsize = 8092;
+  char buffer[bufsize];
+  size_t n;
+  FILE* fp = fopen(filename.c_str(), "rb");
+  if (!fp)  PFATAL(filename.c_str());
+  while ((n=fread(buffer, 1, bufsize, fp)) > 0) {
+    if (ferror(fp))  PFATAL(filename.c_str());
+    s->append(string(buffer, n));
+  }
+  fclose(fp);
+}
+
+static bool EndsWith(const string& s, const string& suffix) {
+  return (s.length() >= suffix.length() &&
+          s.substr(s.length() - suffix.length()) == suffix);
+}
+
+#ifndef USING_PORT_CC  /* windows defines its own version in windows/port.cc */
+static void GetNamelist(const char* testdata_dir, vector<string>* namelist) {
+  DIR* dir = opendir(testdata_dir);
+  struct dirent* dir_entry;
+  if (dir == NULL) PFATAL("opendir");
+  while ( (dir_entry=readdir(dir)) != NULL ) {
+    if (!strncmp(dir_entry->d_name, "template_unittest_test",
+                 sizeof("template_unittest_test")-1)) {
+      namelist->push_back(dir_entry->d_name);    // collect test files
+    }
+  }
+  if (closedir(dir) != 0) PFATAL("closedir");
+}
+#endif
+
+// expensive to resize this vector and copy it and all, but that's ok
+static vector<Testdata> ReadDataFiles(const char* testdata_dir) {
+  vector<Testdata> retval;
+  vector<string> namelist;
+
+  GetNamelist(testdata_dir, &namelist);
+  sort(namelist.begin(), namelist.end());
+
+  for (vector<string>::const_iterator it = namelist.begin();
+       it != namelist.end(); ++it) {
+    vector<string>* new_output = NULL;
+    const string fname = string(testdata_dir) + "/" + it->c_str();
+    if (EndsWith(fname, ".in")) {
+      retval.push_back(Testdata());
+      retval.back().input_template_name = *it;
+      ReadToString(fname, &retval.back().input_template);
+    } else if (EndsWith(fname, ".out")) {
+      new_output = &retval.back().output;
+    } else if (EndsWith(fname, ".anno_out")) {
+      new_output = &retval.back().annotated_output;
+    } else {
+      ASSERT(false);  // Filename must end in either .in, .out, or .anno_out.
+    }
+    if (new_output) {            // the .out and .anno_out cases
+      ASSERT(!retval.empty());   // an .out without any corresponding .in?
+      ASSERT(it->length() > retval.back().input_template_name.length() + 4);
+      // input file is foo.in, and output is foo_dictYY.out.  This gets to YY.
+      const char* dictnum_pos = (it->c_str() +
+                                 retval.back().input_template_name.length() + 2);
+      int dictnum = atoi32(dictnum_pos);   // just ignore chars after the YY
+      ASSERT(dictnum);                   // dictnums should start with 01
+      while (new_output->size() <
+             static_cast<vector<string>::size_type>(dictnum))
+        new_output->push_back(string());
+      ReadToString(fname, &((*new_output)[dictnum-1]));
+    }
+  }
+  return retval;
+}
+
+
+// Creates a complicated dictionary, using every TemplateDictionary
+// command under the sun.  Returns a pointer to the new dictionary-root.
+// Should be freed by the caller.
+static TemplateDictionary* MakeDict1() {
+  TemplateDictionary* dict = new TemplateDictionary("dict1", NULL);
+  dict->SetFilename("just used for debugging, so doesn't matter.txt");
+
+  // --- These are used by template_unittest_test_simple.in
+  dict->SetValue("HEAD", "   This is the head   ");
+  // We leave BODY undefined, to make sure that expansion works properly.
+
+  // --- These are used by template_unittest_test_footer.in
+  TemplateDictionary* fbt = dict->AddSectionDictionary("FOOTER_BAR_TEXT");
+  fbt->SetValue("BODY", "Should never be shown");  // this is part of simple
+  fbt->SetEscapedValue("HOME_LINK", "<b>Time to go home!</b>",
+                       GOOGLE_NAMESPACE::html_escape);
+  // Note: you should never have code like this in real life!  The <b>
+  // and </b> should be part of the template proper.
+  fbt->SetFormattedValue("ADVERTISE_LINK", "<b>Be advertiser #%d</b>", 2);
+  fbt->SetValue("ABOUT_GOOGLE_LINK", "<A HREF=/>About Google!</A>");
+
+  // We show PROMO_LICENSING_SECTION in the main dict, even though
+  // it's defined in the fbt subsection.  This will still work: section
+  // showing goes to the parent dict if not found in the current dict.
+  dict->ShowSection("PROMO_LICENSING_SECTION");
+  dict->SetValue("PROMO_LICENSING_LINK", "<A HREF='foo'>");
+
+  // We don't show the TRIM_LINE section, so these vars shouldn't be seen
+  dict->SetValue("TRIM_LINE_COLOR", "Who cares?");
+  dict->SetIntValue("TRIM_LINE_HEIGHT", 10);
+
+  dict->SetIntValue("MODIFIED_BY_GOOGLE", 2005);
+  dict->SetValue("MSG_copyright", "&copy; Google Inc. (all rights reserved)");
+  // We don't set ODP_ATTRIBUTION, so this include is ignored.
+
+  dict->ShowSection("CLOSING_DIV_SECTION");
+
+  // We won't set any of the includes that follow, just to keep things simple
+
+  // First, call SetValueAndShowSection on a non-existence section, should noop
+  dict->SetValueAndShowSection("LATENCY_PREFETCH_URL", "/huh?",
+                               "UNUSED_SECTION_NAME");
+  // Now try the real URL
+  dict->SetValueAndShowSection("LATENCY_PREFETCH_URL", string("/latency"),
+                               "LATENCY_PREFETCH");
+
+  // JAVASCRIPT_FOOTER_SECTION was meant to be either shown or hidden, but
+  // hey, let's try showing it several times, each with a different include.
+  // And let's include each one several times.
+  TemplateDictionary* jfs1 = dict->AddSectionDictionary(
+      "JAVASCRIPT_FOOTER_SECTION");
+  // This first dictionary should have an empty HEAD and BODY
+  TemplateDictionary* inc1a = jfs1->AddIncludeDictionary("FAST_NEXT_JAVASCRIPT");
+  inc1a->SetFilename("template_unittest_test_simple.in");
+  // For the second dict, let's set an illegal filename: should be ignored
+  TemplateDictionary* inc1b = jfs1->AddIncludeDictionary("FAST_NEXT_JAVASCRIPT");
+  inc1b->SetFilename(INVALID1_FN);
+  // For the third dict, let's do the same as the first, but with a HEAD
+  TemplateDictionary* inc1c = jfs1->AddIncludeDictionary("FAST_NEXT_JAVASCRIPT");
+  inc1c->SetFilename("template_unittest_test_simple.in");
+  inc1c->SetValue("HEAD", "head");
+
+  // Let's expand the section again with two different includes, and again a
+  // third template not meant to be expanded (in this case, don't set filename)
+  TemplateDictionary* jfs2 = dict->AddSectionDictionary(
+      "JAVASCRIPT_FOOTER_SECTION");
+  TemplateDictionary* inc2a = jfs2->AddIncludeDictionary("FAST_NEXT_JAVASCRIPT");
+  inc2a->SetFilename("template_unittest_test_simple.in");
+  inc2a->SetValue("HEAD", "include-head");
+  inc2a->SetEscapedFormattedValue("BODY", GOOGLE_NAMESPACE::html_escape,
+                                  "<b>%s</b>: %.4f", "<A HREF=/>", 1.0/3);
+  inc2a->SetValue("BI_NEWLINE", "");   // override the global default
+  TemplateDictionary* inc2b = jfs2->AddIncludeDictionary("FAST_NEXT_JAVASCRIPT");
+  inc2b->SetFilename("template_unittest_test_html.in");
+  inc2b->SetValue("HEAD", "should be ignored");
+  jfs2->AddIncludeDictionary("FAST_NEXT_JAVASCRIPT");   // ignored: no filename
+
+  // --- These are used by template_unittest_test_html.in
+
+  // This should returns in NO_MOUSEOVER_FUNCTIONS remaining hidden
+  dict->SetValueAndShowSection("DUMMY", "", "NO_MOUSEOVER_FUNCTIONS");
+
+  dict->ShowSection("MOUSEOVER_FUNCTIONS");
+  TemplateDictionary* foo = dict->AddIncludeDictionary("MOUSEOVER_JAVASCRIPT");
+  foo->SetFilename(string("not_a_template"));
+  foo->SetValue("BI_NEWLINE", "not gonna matter");
+
+  dict->SetEscapedValue("GOTO_MESSAGE", "print \"Go home\"",
+                        GOOGLE_NAMESPACE::javascript_escape);
+
+  dict->SetEscapedValue("UPDATE", "monday & tuesday",
+                                      GOOGLE_NAMESPACE::html_escape);
+  dict->ShowSection("UPDATE_SECTION");
+
+  dict->SetValue("ALIGNMENT", "\"right\"");   // all results sections see this
+  for (int i = 0; i < 3; ++i) {   // we'll do three results
+    TemplateDictionary* result = dict->AddSectionDictionary("RESULTS");
+    if (i % 2 == 0)
+      result->ShowSection("WHITE_BG");  // gives us striped results!
+    const char* res = "<&>\"result\" #%d'&'";
+    result->SetFormattedValue("RESULT", res, i);
+    result->SetEscapedFormattedValue("XML_RESULT",
+                                     GOOGLE_NAMESPACE::xml_escape,
+                                     res, i);
+    result->SetIntValue("GOODNESS", i + 5);
+  }
+
+  // For testing auto-escape.
+  dict->SetValue("AE_TITLE_GOOD", "Hello World!");
+  dict->SetValue("AE_TITLE_BAD", "Hello <script>alert(1)</script> World!");
+  dict->SetValue("AE_URL_GOOD", "http://www.google.com/");
+  dict->SetValue("AE_URL_BAD", "javascript:alert(1)");
+  dict->SetValue("AE_BG_COLOR_GOOD", "red");
+  dict->SetValue("AE_BG_COLOR_BAD", "evil! &");
+  dict->SetValue("AE_JS_GOOD", "your text here");
+  dict->SetValue("AE_JS_BAD", "your text'is clever'thanks");
+  dict->SetValue("AE_USERNAME_GOOD", "Mr. Nice");
+  dict->SetValue("AE_USERNAME_BAD", "Doctor<script>alert(2)</script>Evil");
+  dict->SetValue("AE_START_EDGE", "left");
+  dict->SetValue("AE_END_EDGE", ";:center()$$");  // Some invalid chars.
+  dict->SetValue("AE_FONT_SIZE_PC", "120%");
+  dict->SetValue("AE_FONT_SIZE_PT", "12pt");
+  dict->SetValue("AE_MAUVE_RGB", "#FF7BD5");
+  dict->SetValue("AE_ITALIC", "italic");
+
+  // This won't see any of the vars *we* set
+  TemplateDictionary* footer_dict = dict->AddIncludeDictionary("FOOTER");
+  footer_dict->SetFilename("template_unittest_test_footer.in");
+
+  // --- These are used by template_unittest_test_modifiers.in
+
+  // UPDATE and UPDATE_SECTION we inherit from test_html.in
+  TemplateDictionary* inc_simple = dict->AddIncludeDictionary("SIMPLE");
+  inc_simple->SetFilename("template_unittest_test_simple.in");
+
+  return dict;
+}
+
+
+// Quite opposite of dict1, dict2 is a dictionary that has nothing in it
+static TemplateDictionary* MakeDict2() {
+  return new TemplateDictionary("dict2");
+}
+
+
+// dict3 tests just the handling of whitespace
+static TemplateDictionary* MakeDict3() {
+  TemplateDictionary* dict = new TemplateDictionary("dict3");
+
+  dict->SetValue("HEAD", "   ");
+  dict->SetValue("BODY", "\r\n");
+  return dict;
+}
+
+static TemplateDictionary* MakeDictionary(int i) {
+  switch (i) {
+    case 1: return MakeDict1();
+    case 2: return MakeDict2();
+    case 3: return MakeDict3();
+    default: ASSERT(false);  // No dictionary with this number yet.
+  }
+  return NULL;
+}
+
+
+static void TestExpand(const vector<Testdata>::const_iterator& begin,
+                       const vector<Testdata>::const_iterator& end) {
+  for (vector<Testdata>::const_iterator one_test = begin;
+       one_test != end; ++one_test) {
+    Template* tpl_none = Template::GetTemplate(one_test->input_template_name,
+                                               DO_NOT_STRIP);
+    Template* tpl_lines = Template::GetTemplate(one_test->input_template_name,
+                                                STRIP_BLANK_LINES);
+    Template* tpl_ws = Template::GetTemplate(one_test->input_template_name,
+                                             STRIP_WHITESPACE);
+
+    // Test TemplateToString while we're at it.
+    Template* tplstr_none = Template::StringToTemplate(
+        one_test->input_template, DO_NOT_STRIP);
+    Template* tplstr_lines = Template::StringToTemplate(
+        one_test->input_template, STRIP_BLANK_LINES);
+    Template* tplstr_ws = Template::StringToTemplate(
+        one_test->input_template, STRIP_WHITESPACE);
+
+    for (vector<string>::const_iterator out = one_test->output.begin();
+         out != one_test->output.end(); ++out) {
+      int dictnum = out - one_test->output.begin() + 1;  // first dict is 01
+      // If output is the empty string, we assume the file does not exist
+      if (out->empty())
+        continue;
+
+      printf("Testing template %s on dict #%d\n",
+             one_test->input_template_name.c_str(), dictnum);
+      // If we're expecting output, the template better not have had an error
+      ASSERT(tpl_none && tpl_lines && tpl_ws);
+      ASSERT(tplstr_none && tplstr_lines && tplstr_ws);
+
+      TemplateDictionary* dict = MakeDictionary(dictnum);
+
+      string stroutput_none, stroutput_lines, stroutput_ws;
+      string stroutput_strnone, stroutput_strlines, stroutput_strws;
+
+      tpl_none->Expand(&stroutput_none, dict);
+      tpl_lines->Expand(&stroutput_lines, dict);
+      tpl_ws->Expand(&stroutput_ws, dict);
+      tplstr_none->Expand(&stroutput_strnone, dict);
+      tplstr_lines->Expand(&stroutput_strlines, dict);
+      tplstr_ws->Expand(&stroutput_strws, dict);
+
+      // "out" is the output for STRIP_WHITESPACE mode.
+      ASSERT_STRING_EQ(*out, stroutput_ws);
+
+      // Now compare the variants against each other.
+      // NONE and STRIP_LINES may actually be the same on simple inputs
+      //ASSERT(output_none != output_lines);
+      ASSERT(stroutput_none != stroutput_ws);
+      ASSERT(stroutput_lines != stroutput_ws);
+      ASSERT_STREQ_EXCEPT(stroutput_none.c_str(), stroutput_lines.c_str(),
+                          " \t\v\f\r\n");
+      ASSERT_STREQ_EXCEPT(stroutput_none.c_str(), stroutput_ws.c_str(),
+                          " \t\v\f\r\n");
+
+      // It shouldn't matter if we read stuff from a file or a string.
+      ASSERT(stroutput_none == stroutput_strnone);
+      ASSERT(stroutput_lines == stroutput_strlines);
+      ASSERT(stroutput_ws == stroutput_strws);
+
+
+      delete dict;          // it's our responsibility
+    }
+
+    // The annotation test is a bit simpler; we only strip one way
+    for (vector<string>::const_iterator out = one_test->annotated_output.begin();
+         out != one_test->annotated_output.end(); ++out) {
+      int dictnum = out - one_test->annotated_output.begin() + 1;
+      // If output is the empty string, we assume the file does not exist
+      if (out->empty())
+        continue;
+
+      printf("Testing template %s on dict #%d (annotated)\n",
+             one_test->input_template_name.c_str(), dictnum);
+
+      TemplateDictionary* dict = MakeDictionary(dictnum);
+      PerExpandData per_expand_data;
+      per_expand_data.SetAnnotateOutput("template_unittest_test");
+      string output;
+      tpl_lines->ExpandWithData(&output, dict, &per_expand_data);
+      ASSERT_STREQ_EXCEPT(out->c_str(), output.c_str(), "\r\n");
+      delete dict;   // it's our responsibility
+    }
+    delete tplstr_none;   // these are our responsibility too
+    delete tplstr_lines;
+    delete tplstr_ws;
+  }
+}
+
+
+int main(int argc, char** argv) {
+  // If TEMPLATE_ROOTDIR is set in the environment, it overrides the
+  // default of ".".  We use an env-var rather than argv because
+  // that's what automake supports most easily.
+  const char* template_rootdir = getenv("TEMPLATE_ROOTDIR");
+  if (template_rootdir == NULL)
+    template_rootdir = DEFAULT_TEMPLATE_ROOTDIR;   // probably "."
+  string rootdir = GOOGLE_NAMESPACE::PathJoin(template_rootdir, "src");
+  rootdir = GOOGLE_NAMESPACE::PathJoin(rootdir, "tests");
+  Template::SetTemplateRootDirectory(rootdir);
+
+  vector<Testdata> testdata = ReadDataFiles(
+      Template::template_root_directory().c_str());
+  if (testdata.empty()) {
+    printf("FATAL ERROR: No test files found for template_regtest\n");
+    return 1;
+  }
+
+  TestExpand(testdata.begin(), testdata.end());
+
+  printf("DONE\n");
+  return 0;
+}
diff --git a/src/tests/template_setglobals_unittest.cc b/src/tests/template_setglobals_unittest.cc
new file mode 100644
index 0000000..4d59e22
--- /dev/null
+++ b/src/tests/template_setglobals_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2002, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+
+#include "config_for_unittests.h"
+#include <assert.h>
+#include <stdio.h>
+#include <ctemplate/template.h>
+#include <ctemplate/template_dictionary.h>
+#include "tests/template_test_util.h"
+#include "base/util.h"
+TEST_INIT   // defines RUN_ALL_TESTS()
+
+using GOOGLE_NAMESPACE::Template;
+using GOOGLE_NAMESPACE::TemplateDictionary;
+
+TEST(SetGlobalValue, TemplateDictionary) {
+  // Test to see that the global dictionary object gets created when you
+  // first call the static function TemplateDictionary::SetGlobalValue().
+  TemplateDictionary::SetGlobalValue("TEST_GLOBAL_VAR", "test_value");
+  TemplateDictionary tpl("empty");
+  GOOGLE_NAMESPACE::TemplateDictionaryPeer peer(&tpl);
+  EXPECT_STREQ(peer.GetSectionValue("TEST_GLOBAL_VAR"),
+               "test_value");
+
+}
+
+TEST(SetGlobalValue, SetRootDirectory) {
+  // Test to see that the Template static variables get created when you
+  // first call the static function Template::SetRootDirectory().
+  Template::SetTemplateRootDirectory("/some/directory/path");
+  // We don't know if we appended a / or a \, so we test indirectly
+  EXPECT_EQ(strlen("/some/directory/path")+1,   // assert they added a char
+            Template::template_root_directory().size());
+  EXPECT_EQ(0, memcmp(Template::template_root_directory().c_str(),
+                      "/some/directory/path",
+                      strlen("/some/directory/path")));
+}
+
+int main(int argc, char **argv) {
+
+  return RUN_ALL_TESTS();
+}
diff --git a/src/tests/template_test_util.cc b/src/tests/template_test_util.cc
new file mode 100644
index 0000000..57f7f91
--- /dev/null
+++ b/src/tests/template_test_util.cc
@@ -0,0 +1,309 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+
+#include "config_for_unittests.h"
+#include "base/mutex.h"  // must come first, for _XOPEN_SOURCE
+#include "tests/template_test_util.h"
+#include <assert.h>      // for assert()
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>       // for opendir() etc
+#else
+# define dirent direct
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif      // for DIR, dirent, closedir(), opendir(), etc
+#include <stdio.h>       // for printf(), FILE, fclose(), fopen(), etc
+#include <stdlib.h>      // for exit()
+#include <string.h>      // for strcmp(), strcpy(), strstr()
+#include <sys/stat.h>    // for mkdir()
+#include <sys/types.h>   // for mode_t
+#include <time.h>        // for time_t
+#ifdef HAVE_UTIME_H
+# include <utime.h>
+#endif       // for utime()
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif      // for unlink()
+#include <vector>        // for vector<>, vector<>::size_type
+#include <ctemplate/template.h>  // for Template
+#include <ctemplate/template_dictionary.h>  // for TemplateDictionary
+#include <ctemplate/template_dictionary_interface.h>
+#include <ctemplate/template_enums.h>  // for Strip
+#include <ctemplate/template_namelist.h>  // for TemplateNamelist, etc
+#include <ctemplate/template_pathops.h>  // for PathJoin()
+#include "base/util.h"   // for down_cast()
+
+using std::string;
+using std::vector;
+
+#ifdef ASSERT
+# undef ASSERT
+#endif
+#define ASSERT(cond)  do {                                      \
+  if (!(cond)) {                                                \
+    printf("ASSERT FAILED, line %d: %s\n", __LINE__, #cond);    \
+    assert(cond);                                               \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+namespace ctemplate {
+
+// Deletes all files named *template* in dir, and sets up dir as the
+// place where StringToTemplate writes.
+static char* g_tmpdir = NULL;
+
+#ifndef USING_PORT_CC  /* windows defines its own version in windows/port.cc */
+void CreateOrCleanTestDir(const string& dirname) {
+  DIR* dir = opendir(dirname.c_str());
+  if (!dir) {   // directory doesn't exist or something like that
+    mkdir(dirname.c_str(), 0755);   // make the dir if we can
+    return;
+  }
+  while (struct dirent* d = readdir(dir)) {
+    if (strstr(d->d_name, "template"))
+      unlink(PathJoin(dirname, d->d_name).c_str());
+  }
+  closedir(dir);
+}
+
+static string TmpFile(const char* basename) {
+  return string("/tmp/") + basename;
+}
+
+#endif  // #ifndef USING_PORT_CC
+
+void CreateOrCleanTestDirAndSetAsTmpdir(const string& dirname) {
+  CreateOrCleanTestDir(dirname);
+  delete[] g_tmpdir;
+  g_tmpdir = new char[dirname.length() + 1];
+  strcpy(g_tmpdir, dirname.c_str());
+}
+
+const string FLAGS_test_tmpdir(TmpFile("template_unittest_dir"));
+
+// This writes s to the given file.  We want to make sure that every
+// time we create a file, it has a different mtime (just like would
+// be the case in real life), so we use a mock clock.
+static Mutex g_time_mutex(base::LINKER_INITIALIZED);
+static time_t mock_time = 946713600;   // jan 1, 2000, in california
+
+void StringToFile(const string& s, const string& filename) {
+  FILE* fp = fopen(filename.c_str(), "wb");
+  ASSERT(fp);
+  size_t r = fwrite(s.data(), 1, s.length(), fp);
+  ASSERT(r == s.length());
+  fclose(fp);
+
+  g_time_mutex.Lock();
+  const time_t file_time = mock_time++;
+  g_time_mutex.Unlock();
+  struct utimbuf timbuf = { file_time, file_time };
+  utime(filename.c_str(), &timbuf);
+}
+
+time_t Now() {
+  g_time_mutex.Lock();
+  const time_t now = mock_time;
+  g_time_mutex.Unlock();
+  return now;
+}
+
+// This writes s to a file and returns the filename.
+string StringToTemplateFile(const string& s) {
+  static int filenum = 0;
+  char buf[16];
+  snprintf(buf, sizeof(buf), "%03d", ++filenum);
+  string filename = PathJoin(g_tmpdir ? g_tmpdir : "",
+                             string("template.") + buf);
+  StringToFile(s, filename);
+  return filename;
+}
+
+// This writes s to a file and then loads it into a template object.
+Template* StringToTemplate(const string& s, Strip strip) {
+  return Template::GetTemplate(StringToTemplateFile(s), strip);
+}
+
+// This is esp. useful for calling from within gdb.
+// The gdb nice-ness is balanced by the need for the caller to delete the buf.
+
+const char* ExpandIs(const Template* tpl, const TemplateDictionary *dict,
+                     PerExpandData* per_expand_data, bool expected) {
+  string outstring;
+  if (per_expand_data)
+    ASSERT(expected == tpl->ExpandWithData(&outstring, dict, per_expand_data));
+  else
+    ASSERT(expected == tpl->Expand(&outstring, dict));
+
+
+  char* buf = new char[outstring.size()+1];
+  strcpy(buf, outstring.c_str());
+  return buf;
+}
+
+const char* ExpandWithCacheIs(TemplateCache* cache,
+                              const string& filename, Strip strip,
+                              const TemplateDictionary *dict,
+                              PerExpandData* per_expand_data, bool expected) {
+  string outstring;
+  ASSERT(expected == cache->ExpandWithData(filename, strip, dict,
+                                           per_expand_data, &outstring));
+
+
+  char* buf = new char[outstring.size()+1];
+  strcpy(buf, outstring.c_str());
+  return buf;
+}
+
+void AssertExpandWithDataIs(const Template* tpl,
+                            const TemplateDictionary *dict,
+                            PerExpandData* per_expand_data,
+                            const string& is, bool expected) {
+  const char* buf = ExpandIs(tpl, dict, per_expand_data, expected);
+  if (strcmp(buf, is.c_str())) {
+    printf("expected = '%s'\n", is.c_str());
+    printf("actual   = '%s'\n", buf);
+  }
+  ASSERT(string(buf) == is);
+  delete [] buf;
+}
+
+void AssertExpandIs(const Template* tpl, const TemplateDictionary *dict,
+                    const string& is, bool expected) {
+  AssertExpandWithDataIs(tpl, dict, NULL, is, expected);
+}
+
+void AssertExpandWithCacheIs(TemplateCache* cache,
+                             const string& filename, Strip strip,
+                             const TemplateDictionary *dict,
+                             PerExpandData* per_expand_data,
+                             const string& is, bool expected) {
+  const char* buf = ExpandWithCacheIs(cache, filename, strip, dict,
+                                      per_expand_data, expected);
+  if (strcmp(buf, is.c_str())) {
+    printf("expected = '%s'\n", is.c_str());
+    printf("actual   = '%s'\n", buf);
+  }
+  ASSERT(string(buf) == is);
+  delete [] buf;
+}
+
+TemporaryRegisterTemplate::TemporaryRegisterTemplate(const char* name) {
+  old_namelist_ = TemplateNamelist::namelist_;
+  if (old_namelist_) {
+    namelist_ = *old_namelist_;
+  }
+
+  namelist_.insert(name);
+  TemplateNamelist::namelist_ = &namelist_;
+}
+
+TemporaryRegisterTemplate::~TemporaryRegisterTemplate() {
+  TemplateNamelist::namelist_ = old_namelist_;
+}
+
+const char* TemplateDictionaryPeer::GetSectionValue(
+    const TemplateString& variable)
+    const {
+  // Luckily, TemplateDictionary stores all values with a trailing NUL.
+  return dict_->GetValue(variable).data();
+}
+
+bool TemplateDictionaryPeer::ValueIs(const TemplateString& variable,
+                                     const TemplateString& expected) const {
+  return dict_->GetValue(variable) == expected;
+}
+
+bool TemplateDictionaryPeer::IsHiddenSection(
+    const TemplateString& name) const {
+  return dict_->IsHiddenSection(name);
+}
+
+bool TemplateDictionaryPeer::IsUnhiddenSection(
+    const TemplateString& name) const {
+  return dict_->IsUnhiddenSection(name);
+}
+
+bool TemplateDictionaryPeer::IsHiddenTemplate(
+    const TemplateString& name) const {
+  return dict_->IsHiddenTemplate(name);
+}
+
+int TemplateDictionaryPeer::GetSectionDictionaries(
+    const TemplateString& section_name,
+    vector<const TemplateDictionary*>* dicts) const {
+  dicts->clear();
+  if (dict_->IsHiddenSection(section_name))
+    return 0;
+
+  TemplateDictionaryInterface::Iterator* di =
+      dict_->CreateSectionIterator(section_name);
+  while (di->HasNext())
+    dicts->push_back(down_cast<const TemplateDictionary*>(&di->Next()));
+  delete di;
+
+  return static_cast<int>(dicts->size());
+}
+
+int TemplateDictionaryPeer::GetIncludeDictionaries(
+    const TemplateString& section_name,
+    vector<const TemplateDictionary*>* dicts) const {
+  dicts->clear();
+  if (dict_->IsHiddenTemplate(section_name))
+    return 0;
+
+  TemplateDictionaryInterface::Iterator* di =
+      dict_->CreateTemplateIterator(section_name);
+  while (di->HasNext())
+    dicts->push_back(down_cast<const TemplateDictionary*>(&di->Next()));
+  delete di;
+
+  return static_cast<int>(dicts->size());
+}
+
+const char* TemplateDictionaryPeer::GetIncludeTemplateName(
+    const TemplateString& variable, int dictnum) const {
+  return dict_->GetIncludeTemplateName(variable, dictnum);
+}
+
+const char* TemplateDictionaryPeer::GetFilename() const {
+  return dict_->filename_;
+}
+
+}
diff --git a/src/tests/template_test_util.h b/src/tests/template_test_util.h
new file mode 100644
index 0000000..ec3cc84
--- /dev/null
+++ b/src/tests/template_test_util.h
@@ -0,0 +1,283 @@
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+//
+// Intended usage of TemplateDictionaryPeer:
+//    Use this class if you need to TEST that a dictionary has certain
+//    expected contents.  This should be fairly uncommon outside the
+//    template directory.
+//
+
+
+#ifndef TEMPLATE_TEMPLATE_TEST_UTIL_H_
+#define TEMPLATE_TEMPLATE_TEST_UTIL_H_
+
+#include "config_for_unittests.h"
+#include <time.h>        // for time_t
+#include <string>        // for string
+#include <vector>        // for vector<>
+#include HASH_MAP_H      // UNUSED
+#include <ctemplate/template.h>        // for Template::num_deletes_
+#include <ctemplate/template_cache.h>  // for TemplateCache
+#include <ctemplate/template_dictionary.h>  // for TemplateDictionary
+#include <ctemplate/template_dictionary_interface.h>
+#include <ctemplate/template_enums.h>  // for Strip
+#include <ctemplate/template_namelist.h>
+#include <ctemplate/template_string.h>  // for TemplateString, TemplateId
+
+namespace ctemplate {
+
+using std::string;
+
+class PerExpandData;
+class TemplateCache;
+class TemplateDictionary;
+
+inline TemplateId GlobalIdForTest(const char* ptr, int len) {
+  return TemplateString(ptr, len).GetGlobalId();
+}
+
+// Call this to create a StaticTemplateString for testing when the ptr is
+// not guaranteed to be allocated for the entire length of the test.
+#define STS_INIT_FOR_TEST(ptr, len, arena) \
+  { { arena->Memdup(ptr, len), len, GOOGLE_NAMESPACE::GlobalIdForTest(ptr, len) } };
+
+extern const std::string FLAGS_test_tmpdir;
+
+// These are routines that are useful for creating template files for testing.
+
+// Deletes all files named *template* in dir.
+void CreateOrCleanTestDir(const string& dirname);
+// This delets all files named *template*, and also sets dirname to be
+// the directory that all future StringToFile calls will place their
+// templates.
+void CreateOrCleanTestDirAndSetAsTmpdir(const string& dirname);
+
+// This writes s to the given file.  We want to make sure that every
+// time we create a file, it has a different mtime (just like would
+// be the case in real life), so we use a mock clock.  Filenames created
+// by this routine will all have an mtime of around Jan 1, 2000.
+void StringToFile(const string& s, const string& filename);
+
+// This is the (mock) time used when creating the last file in StringToFile.
+time_t Now();
+
+// This writes s to a file and returns the filename.
+string StringToTemplateFile(const string& s);
+
+// This writes s to a file and then loads it into a template object.
+Template* StringToTemplate(const string& s, Strip strip);
+
+// This is esp. useful for calling from within gdb.
+// The gdb nice-ness is balanced by the need for the caller to delete the buf.
+const char* ExpandIs(const Template* tpl, const TemplateDictionary *dict,
+                     PerExpandData* per_expand_data, bool expected);
+
+void AssertExpandWithDataIs(const Template* tpl,
+                            const TemplateDictionary *dict,
+                            PerExpandData* per_expand_data,
+                            const string& is, bool expected);
+
+void AssertExpandIs(const Template* tpl, const TemplateDictionary *dict,
+                    const string& is, bool expected);
+
+void AssertExpandWithCacheIs(TemplateCache* cache,
+                             const string& filename, Strip strip,
+                             const TemplateDictionary *dict,
+                             PerExpandData* per_expand_data,
+                             const string& is, bool expected);
+
+class TemporaryRegisterTemplate {
+ public:
+  explicit TemporaryRegisterTemplate(const char* name);
+  ~TemporaryRegisterTemplate();
+ private:
+  GOOGLE_NAMESPACE::TemplateNamelist::NameListType* old_namelist_;
+  GOOGLE_NAMESPACE::TemplateNamelist::NameListType namelist_;
+
+  // disallow copy constructor and assignment
+  TemporaryRegisterTemplate(const TemporaryRegisterTemplate&);
+  void operator=(const TemporaryRegisterTemplate&);
+};
+
+// For friendship reasons, we make this a top-level class rather
+// than a nested class.  It's used only in TemplateDictionaryPeer.
+// We take ownership of the iterator passed to us.  To make sure that
+// isn't a problem, we make this class not-copyable.
+class TemplateDictionaryPeerIterator {
+ public:
+  explicit TemplateDictionaryPeerIterator(
+      TemplateDictionaryInterface::Iterator* it) : it_(it) { }
+  ~TemplateDictionaryPeerIterator() { delete it_; }
+  bool HasNext() const { return it_->HasNext(); }
+  const TemplateDictionaryInterface& Next() { return it_->Next(); }
+ private:
+  TemplateDictionaryInterface::Iterator* it_;
+  TemplateDictionaryPeerIterator(const TemplateDictionaryPeerIterator&);
+  TemplateDictionaryPeerIterator& operator=(
+      const TemplateDictionaryPeerIterator&);
+};
+
+// This class is meant for use in unittests.  This class wraps the
+// TemplateDictionary and provides access to internal data that should
+// not be used in production code.  If you need this kind of
+// functionality in production, use TemplateDictionaryWrapper or
+// TemplateDictionaryInterface; see top of file for details.
+//
+// Example Usage:
+//  TemplateDictionary dict("test dictionary");
+//  FillDictionaryValues(&dict);
+//
+//  TemplateDictionaryPeer peer(&dict);
+//  EXPECT_EQ("5", peer.GetSectionValue("width"));
+class TemplateDictionaryPeer {
+ public:
+  explicit TemplateDictionaryPeer(const TemplateDictionary* dict)
+      : dict_(dict) {}
+
+  // Returns whether the named variable has value equal to "expected".
+  bool ValueIs(const TemplateString& variable,
+               const TemplateString& expected) const;
+
+  // DEPRECATED: Returns the value of the named variable.  Does not
+  // deal properly with values that have an internal NUL.  Use ValueIs
+  // for new code.
+  const char* GetSectionValue(const TemplateString& variable) const;
+
+  // Returns true if the named section is hidden.
+  bool IsHiddenSection(const TemplateString& name) const;
+
+  // IsUnhiddenSection
+  //   Returns true if the section has been marked visible and false otherwise.
+  bool IsUnhiddenSection(const TemplateString& name) const;
+
+  // Returns true if the named sub-template is hidden.
+  bool IsHiddenTemplate(const TemplateString& name) const;
+
+  // Retrieves TemplateDictionary instances for the given section name.  The
+  // caller does not assume ownership of the returned TemplateDictionary
+  // instances.  The number of instances is returned.  All prior entries in
+  // the dicts vector are cleared.
+  //
+  // NOTE: This method assumes that old-style template dictionaries are not in
+  // use.  That is, it assumes that all section dictionaries have been added
+  // with AddSectionDictionary rather than AddOldstyleSectionDictionary.
+  int GetSectionDictionaries(const TemplateString& section_name,
+                             std::vector<const TemplateDictionary*>* dicts)
+      const;
+
+  // Retrieves included TemplateDictionary instances for the given name.  The
+  // caller does not assume ownership of the returned TemplateDictionary
+  // instances.  The number of instances is returned.  All prior entries in
+  // the dicts vector are cleared.
+  //
+  // NOTE: This method assumes that old-style template dictionaries are not in
+  // use.  That is, it assumes that all section dictionaries have been added
+  // with AddIncludeDictionary rather than AddOldstyleIncludeDictionary.
+  int GetIncludeDictionaries(const TemplateString& section_name,
+                             std::vector<const TemplateDictionary*>* dicts)
+      const;
+
+  const char* GetIncludeTemplateName(const TemplateString& variable,
+                                     int dictnum) const;
+
+  typedef TemplateDictionaryPeerIterator Iterator;
+
+  Iterator* CreateTemplateIterator(const TemplateString& section)
+      const {
+    return new Iterator(dict_->CreateTemplateIterator(section));
+  }
+
+  Iterator* CreateSectionIterator(const TemplateString& section)
+      const {
+    return new Iterator(dict_->CreateSectionIterator(section));
+  }
+
+  // Returns the filename associated with the TemplateDictionary.
+  const char* GetFilename() const;
+
+ private:
+  const TemplateDictionary* dict_;  // Not owned.
+
+  // disallow copy constructor and assignment
+  TemplateDictionaryPeer(const TemplateDictionaryPeer&);
+  void operator=(const TemplateDictionaryPeer&);
+};
+
+class TemplateCachePeer {
+ public:
+  TemplateCachePeer(TemplateCache* cache)
+      : cache_(cache) {}
+
+  struct TemplateCacheKey : public TemplateCache::TemplateCacheKey {
+    TemplateCacheKey(const string& key, int strip) {
+      this->first = GlobalIdForTest(key.data(), key.length());
+      this->second = strip;
+    }
+  };
+
+  TemplateCache::TemplateMap* parsed_template_cache() {
+    return cache_->parsed_template_cache_;
+  }
+
+  bool TemplateIsCached(const TemplateCacheKey key) const {
+    return cache_->TemplateIsCached(key);
+  }
+
+  const Template* GetTemplate(const TemplateString& key, Strip strip) const {
+    return cache_->GetTemplate(key, strip);
+  }
+
+  int Refcount(const TemplateCacheKey key) const {
+    return cache_->Refcount(key);
+  }
+
+  void DoneWithGetTemplatePtrs() {
+    cache_->DoneWithGetTemplatePtrs();
+  }
+  void ClearCache() {
+    cache_->ClearCache();
+  }
+
+  static int NumTotalTemplateDeletes() {
+    return Template::num_deletes();
+  }
+
+ private:
+  TemplateCache* cache_;  // Not owned.
+
+  // Don't allow copying
+  TemplateCachePeer(const TemplateCachePeer&);
+  void operator=(const TemplateCachePeer&);
+};
+
+}
+
+#endif  // TEMPLATE_TEMPLATE_TEST_UTIL_H_
diff --git a/src/tests/template_test_util_test.cc b/src/tests/template_test_util_test.cc
new file mode 100644
index 0000000..190ac95
--- /dev/null
+++ b/src/tests/template_test_util_test.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "config_for_unittests.h"
+#include "tests/template_test_util.h"
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "base/arena.h"
+#include <ctemplate/template_dictionary.h>
+#include <ctemplate/template_string.h>
+#include "base/util.h"
+TEST_INIT   // defines RUN_ALL_TESTS()
+
+#define ASSERT_EQ(a, b)  EXPECT_EQ(a, b)
+
+using std::vector;
+using std::string;
+using GOOGLE_NAMESPACE::UnsafeArena;
+
+using GOOGLE_NAMESPACE::TemplateDictionary;
+using GOOGLE_NAMESPACE::TemplateDictionaryPeer;
+using GOOGLE_NAMESPACE::TemplateString;
+using GOOGLE_NAMESPACE::StaticTemplateString;
+
+namespace {
+
+TEST(TemplateTestUtilTest, GetSectionValue) {
+  TemplateDictionary dict("test_GetSectionValue");
+  dict.SetValue("VALUE", "value");
+
+  TemplateDictionaryPeer peer(&dict);
+  EXPECT_STREQ("value", peer.GetSectionValue("VALUE"));
+}
+
+TEST(TemplateTestUtilTest, IsHiddenSection) {
+  TemplateDictionary dict("test_IsHiddenSection");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    EXPECT_TRUE(peer.IsHiddenSection("SECTION"));
+  }
+
+  dict.AddSectionDictionary("SECTION");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    EXPECT_FALSE(peer.IsHiddenSection("SECTION"));
+  }
+}
+
+TEST(TemplateTestUtilTest, GetSectionDictionaries) {
+  TemplateDictionary dict("test_GetSectionDictionaries");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    // Add some dummy value into the vector to confirm that the call to
+    // GetSectionDictionaries will correctly clear the vector.
+    dicts.push_back(NULL);
+    EXPECT_EQ(0, peer.GetSectionDictionaries("SECTION", &dicts));
+    EXPECT_TRUE(dicts.empty());
+  }
+
+  dict.AddSectionDictionary("SECTION")->SetValue("SECTION_VALUE", "0");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    ASSERT_EQ(1, peer.GetSectionDictionaries("SECTION", &dicts));
+
+    TemplateDictionaryPeer peer_section(dicts[0]);
+    EXPECT_STREQ("0", peer_section.GetSectionValue("SECTION_VALUE"));
+  }
+
+  dict.AddSectionDictionary("SECTION")->SetValue("SECTION_VALUE", "1");
+  dict.AddSectionDictionary("ANOTHER_SECTION")->SetValue("ANOTHER_VALUE", "2");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    ASSERT_EQ(2, peer.GetSectionDictionaries("SECTION", &dicts));
+
+    TemplateDictionaryPeer peer_section0(dicts[0]);
+    EXPECT_STREQ("0", peer_section0.GetSectionValue("SECTION_VALUE"));
+
+    TemplateDictionaryPeer peer_section1(dicts[1]);
+    EXPECT_STREQ("1", peer_section1.GetSectionValue("SECTION_VALUE"));
+  }
+}
+
+TEST(TemplateTestUtilTest, GetIncludeDictionaries) {
+  TemplateDictionary dict("test_GetIncludeDictionaries");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    // Add some dummy value into the vector to confirm that the call to
+    // GetSectionDictionaries will correctly clear the vector.
+    dicts.push_back(NULL);
+    EXPECT_EQ(0, peer.GetIncludeDictionaries("SECTION", &dicts));
+    EXPECT_TRUE(dicts.empty());
+  }
+
+  dict.AddIncludeDictionary("SECTION")->SetValue("SECTION_VALUE", "0");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    ASSERT_EQ(1, peer.GetIncludeDictionaries("SECTION", &dicts));
+
+    TemplateDictionaryPeer peer_section(dicts[0]);
+    EXPECT_STREQ("0", peer_section.GetSectionValue("SECTION_VALUE"));
+  }
+
+  dict.AddIncludeDictionary("SECTION")->SetValue("SECTION_VALUE", "1");
+  dict.AddIncludeDictionary("ANOTHER_SECTION")->SetValue("ANOTHER_VALUE", "2");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    ASSERT_EQ(2, peer.GetIncludeDictionaries("SECTION", &dicts));
+
+    TemplateDictionaryPeer peer_section0(dicts[0]);
+    EXPECT_STREQ("0", peer_section0.GetSectionValue("SECTION_VALUE"));
+
+    TemplateDictionaryPeer peer_section1(dicts[1]);
+    EXPECT_STREQ("1", peer_section1.GetSectionValue("SECTION_VALUE"));
+  }
+}
+
+TEST(TemplateTestUtilTest, GetIncludeAndSectionDictionaries) {
+  TemplateDictionary dict("test_GetIncludeAndSectionDictionaries");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    EXPECT_EQ(0, peer.GetIncludeDictionaries("SECTION", &dicts));
+    EXPECT_EQ(0, peer.GetSectionDictionaries("SECTION", &dicts));
+  }
+
+  dict.AddIncludeDictionary("SECTION")->SetValue("SECTION_VALUE", "0");
+  dict.AddSectionDictionary("SECTION")->SetValue("SECTION_VALUE", "1");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> include_dicts;
+    ASSERT_EQ(1, peer.GetIncludeDictionaries("SECTION", &include_dicts));
+
+    TemplateDictionaryPeer include_peer(include_dicts[0]);
+    EXPECT_STREQ("0", include_peer.GetSectionValue("SECTION_VALUE"));
+
+    vector<const TemplateDictionary*> section_dicts;
+    ASSERT_EQ(1, peer.GetSectionDictionaries("SECTION", &section_dicts));
+
+    TemplateDictionaryPeer section_peer(section_dicts[0]);
+    EXPECT_STREQ("1", section_peer.GetSectionValue("SECTION_VALUE"));
+  }
+
+  dict.AddIncludeDictionary("SECTION")->SetValue("SECTION_VALUE", "2");
+  dict.AddIncludeDictionary("ANOTHER_SECTION")->SetValue("ANOTHER_VALUE", "3");
+
+  dict.AddSectionDictionary("SECTION")->SetValue("SECTION_VALUE", "4");
+  dict.AddSectionDictionary("ONE_MORE_SECTION")->SetValue("ANOTHER_VALUE", "5");
+
+  {
+    TemplateDictionaryPeer peer(&dict);
+    vector<const TemplateDictionary*> dicts;
+    ASSERT_EQ(2, peer.GetIncludeDictionaries("SECTION", &dicts));
+
+    TemplateDictionaryPeer include_peer0(dicts[0]);
+    EXPECT_STREQ("0", include_peer0.GetSectionValue("SECTION_VALUE"));
+
+    TemplateDictionaryPeer include_peer1(dicts[1]);
+    EXPECT_STREQ("2", include_peer1.GetSectionValue("SECTION_VALUE"));
+
+    EXPECT_EQ(1, peer.GetIncludeDictionaries("ANOTHER_SECTION", &dicts));
+    EXPECT_EQ(0, peer.GetIncludeDictionaries("ONE_MORE_SECTION", &dicts));
+
+    vector<const TemplateDictionary*> section_dicts;
+    ASSERT_EQ(2, peer.GetSectionDictionaries("SECTION", &section_dicts));
+
+    TemplateDictionaryPeer section_peer0(section_dicts[0]);
+    EXPECT_STREQ("1", section_peer0.GetSectionValue("SECTION_VALUE"));
+
+    TemplateDictionaryPeer section_peer1(section_dicts[1]);
+    EXPECT_STREQ("4", section_peer1.GetSectionValue("SECTION_VALUE"));
+
+    EXPECT_EQ(0, peer.GetSectionDictionaries("ANOTHER_SECTION", &dicts));
+    EXPECT_EQ(1, peer.GetSectionDictionaries("ONE_MORE_SECTION", &dicts));
+  }
+}
+
+TEST(TemplateTestUtilTest, GetFilename) {
+  TemplateDictionary parent("test_GetFilename");
+  TemplateDictionary* child = parent.AddIncludeDictionary("INCLUDE_marker");
+  child->SetFilename("included_filename");
+
+  TemplateDictionaryPeer parent_peer(&parent);
+  EXPECT_EQ(NULL, parent_peer.GetFilename());
+
+  TemplateDictionaryPeer child_peer(child);
+  EXPECT_STREQ("included_filename", child_peer.GetFilename());
+}
+
+StaticTemplateString GetTestTemplateString(UnsafeArena* arena) {
+  string will_go_out_of_scope("VALUE");
+  // We want to ensure that the STS_INIT_FOR_TEST macro:
+  // - Can produce a StaticTemplateString (guard again its format changing).
+  // - Produces a StaticTemplateString that is still valid after the string
+  //   used to initialize it goes out-of-scope.
+  StaticTemplateString sts = STS_INIT_FOR_TEST(will_go_out_of_scope.c_str(),
+                                               will_go_out_of_scope.length(),
+                                               arena);
+  return sts;
+}
+
+TEST(TemplateUtilTest, InitStaticTemplateStringForTest) {
+  UnsafeArena arena(1024);
+  StaticTemplateString kValue = GetTestTemplateString(&arena);
+
+  TemplateDictionary dict("test_GetSectionValue");
+  dict.SetValue(kValue, "value");
+
+  TemplateDictionaryPeer peer(&dict);
+  EXPECT_STREQ("value", peer.GetSectionValue(kValue));
+}
+
+}  // namespace anonymous
+
+int main(int argc, char **argv) {
+
+  return RUN_ALL_TESTS();
+}
diff --git a/src/tests/template_unittest.cc b/src/tests/template_unittest.cc
new file mode 100644
index 0000000..8d615c0
--- /dev/null
+++ b/src/tests/template_unittest.cc
@@ -0,0 +1,2149 @@
+// Copyright (c) 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// ---
+// Author: csilvers@google.com (Craig Silverstein)
+
+#include "config_for_unittests.h"
+#include <ctemplate/template.h>
+#include <assert.h>      // for assert()
+#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
+# include <pthread.h>
+#endif     // for pthread_t, pthread_create(), etc
+#include <stddef.h>      // for size_t
+#include <stdio.h>       // for printf(), FILE, snprintf(), fclose(), etc
+#include <stdlib.h>      // for exit()
+#include <string.h>      // for strcmp(), memchr(), strlen(), strstr()
+#include <sys/types.h>   // for mode_t
+#include <time.h>        // for time_t, time()
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif      // for link(), unlink()
+#include <list>          // for list<>::size_type
+#include <vector>        // for vector<>
+#include <ctemplate/per_expand_data.h>  // for PerExpandData
+#include <ctemplate/template_annotator.h>  // for TextTemplateAnnotator
+#include <ctemplate/template_dictionary.h>  // for TemplateDictionary
+#include <ctemplate/template_emitter.h>  // for ExpandEmitter
+#include <ctemplate/template_enums.h>  // for STRIP_WHITESPACE, Strip, etc
+#include <ctemplate/template_modifiers.h>  // for AddModifier(), HtmlEscape, etc
+#include <ctemplate/template_namelist.h>  // for TemplateNamelist, etc
+#include <ctemplate/template_pathops.h>  // for PathJoin(), IsAbspath(), etc
+#include <ctemplate/template_string.h>  // for TemplateString, StringHash, etc
+#include "tests/template_test_util.h"  // for StringToTemplate(), etc
+#include "base/util.h"
+TEST_INIT   // defines RUN_ALL_TESTS()
+
+using std::vector;
+using std::string;
+using GOOGLE_NAMESPACE::FLAGS_test_tmpdir;
+
+using GOOGLE_NAMESPACE::AssertExpandIs;
+using GOOGLE_NAMESPACE::AssertExpandWithDataIs;
+using GOOGLE_NAMESPACE::CreateOrCleanTestDir;
+using GOOGLE_NAMESPACE::CreateOrCleanTestDirAndSetAsTmpdir;
+using GOOGLE_NAMESPACE::DO_NOT_STRIP;
+using GOOGLE_NAMESPACE::ExpandEmitter;
+using GOOGLE_NAMESPACE::IsAbspath;
+using GOOGLE_NAMESPACE::Now;
+using GOOGLE_NAMESPACE::PathJoin;
+using GOOGLE_NAMESPACE::PerExpandData;
+using GOOGLE_NAMESPACE::STRIP_BLANK_LINES;
+using GOOGLE_NAMESPACE::STRIP_WHITESPACE;
+using GOOGLE_NAMESPACE::StaticTemplateString;
+using GOOGLE_NAMESPACE::StringToFile;
+using GOOGLE_NAMESPACE::StringToTemplate;
+using GOOGLE_NAMESPACE::StringToTemplateFile;
+using GOOGLE_NAMESPACE::Strip;
+using GOOGLE_NAMESPACE::TC_CSS;
+using GOOGLE_NAMESPACE::TC_HTML;
+using GOOGLE_NAMESPACE::TC_JS;
+using GOOGLE_NAMESPACE::TC_JSON;
+using GOOGLE_NAMESPACE::TC_MANUAL;
+using GOOGLE_NAMESPACE::TC_UNUSED;
+using GOOGLE_NAMESPACE::TC_XML;
+using GOOGLE_NAMESPACE::Template;
+using GOOGLE_NAMESPACE::TemplateContext;
+using GOOGLE_NAMESPACE::TemplateDictionary;
+using GOOGLE_NAMESPACE::TemplateNamelist;
+using GOOGLE_NAMESPACE::TemplateString;
+using GOOGLE_NAMESPACE::kRootdir;
+
+using GOOGLE_NAMESPACE::ExpandTemplate;
+using GOOGLE_NAMESPACE::ExpandWithData;
+using GOOGLE_NAMESPACE::StringToTemplateCache;
+
+static const StaticTemplateString kHello = STS_INIT(kHello, "Hello");
+static const StaticTemplateString kWorld = STS_INIT(kWorld, "World");
+
+static const char* kPragmaHtml = "{{%AUTOESCAPE context=\"HTML\"}}\n";
+static const char* kPragmaJs   = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}\n";
+static const char* kPragmaCss  = "{{%AUTOESCAPE context=\"CSS\"}}\n";
+static const char* kPragmaXml  = "{{%AUTOESCAPE context=\"XML\"}}\n";
+static const char* kPragmaJson = "{{%AUTOESCAPE context=\"JSON\"}}\n";
+
+// How many threads to use for our threading test.
+// This is a #define instead of a const int so we can use it in array-sizes
+// even on c++ compilers that don't support var-length arrays.
+#define kNumThreads  10
+
+#define PFATAL(s)  do { perror(s); exit(1); } while (0)
+
+// TODO(csilvers): rewrite to be more gunit-like: use expectations
+// instead of asserts, and move assert-checking out of helper routines
+// and into tests proper.  Ideally, replace AssertExpandIs() with
+// VerifyExpandIs().
+#define ASSERT(cond)  do {                                      \
+  if (!(cond)) {                                                \
+    printf("ASSERT FAILED, line %d: %s\n", __LINE__, #cond);    \
+    assert(cond);                                               \
+    exit(1);                                                    \
+  }                                                             \
+} while (0)
+
+#define ASSERT_STREQ_EXCEPT(a, b, except)  ASSERT(StreqExcept(a, b, except))
+#define ASSERT_STREQ(a, b)   ASSERT(strcmp(a, b) == 0)
+#define ASSERT_NOT_STREQ(a, b)             ASSERT(strcmp(a, b) != 0)
+#define ASSERT_STREQ_VERBOSE(a, b, c)      ASSERT(StrEqVerbose(a, b, c))
+#define ASSERT_INTEQ(a, b)                 ASSERT(IntEqVerbose(a, b))
+
+namespace {
+
+// First, (conceptually) remove all chars in "except" from both a and b.
+// Then return true iff munged_a == munged_b.
+bool StreqExcept(const char* a, const char* b, const char* except) {
+  const char* pa = a, *pb = b;
+  const size_t exceptlen = strlen(except);
+  while (1) {
+    // Use memchr isntead of strchr because memchr(foo, '\0') always fails
+    while (memchr(except, *pa, exceptlen))  pa++;  // ignore "except" chars in a
+    while (memchr(except, *pb, exceptlen))  pb++;  // ignore "except" chars in b
+    if ((*pa == '\0') && (*pb == '\0'))
+      return true;
+    if (*pa++ != *pb++)                  // includes case where one is at \0
+      return false;
+  }
+}
+
+// If a and b do not match, print their values and that of text
+// and return false.
+bool StrEqVerbose(const string& a, const string& b,
+                         const string& text) {
+  if (a != b) {
+    printf("EXPECTED: %s\n", a.c_str());
+    printf("ACTUAL: %s\n", b.c_str());
+    printf("TEXT: %s\n", text.c_str());
+    return false;
+  }
+  return true;
+}
+
+bool IntEqVerbose(int a, int b) {
+  if (a != b) {
+    printf("EXPECTED: %d\n", a);
+    printf("ACTUAL: %d\n", b);
+    return false;
+  }
+  return true;
+}
+
+// This test emitter writes to a string, but writes X's of the right
+// length, rather than the actual content passed in.
+class SizeofEmitter : public ExpandEmitter {
+  string* const outbuf_;
+ public:
+  SizeofEmitter(string* outbuf) : outbuf_(outbuf) {}
+  virtual void Emit(char c) { Emit(&c, 1); }
+  virtual void Emit(const string& s) { Emit(s.data(), s.length()); }
+  virtual void Emit(const char* s) { Emit(s, strlen(s)); }
+  virtual void Emit(const char*, size_t slen) { outbuf_->append(slen, 'X'); }
+};
+
+}  // unnamed namespace
+
+RegisterTemplateFilename(VALID1_FN, "template_unittest_test_valid1.in");
+RegisterTemplateFilename(INVALID1_FN, "template_unittest_test_invalid1.in");
+RegisterTemplateFilename(INVALID2_FN, "template_unittest_test_invalid2.in");
+RegisterTemplateFilename(NONEXISTENT_FN, "nonexistent__file.tpl");
+
+// Returns the proper AUTOESCAPE pragma that corresponds to the
+// given TemplateContext.
+static string GetPragmaForContext(TemplateContext context) {
+  switch(context) {
+    case TC_HTML:
+      return kPragmaHtml;
+    case TC_JS:
+      return kPragmaJs;
+    case TC_CSS:
+      return kPragmaCss;
+    case TC_JSON:
+      return kPragmaJson;
+    case TC_XML:
+      return kPragmaXml;
+    case TC_MANUAL:
+      return "";  // No AUTOESCAPE pragma.
+    case TC_UNUSED:
+      ASSERT(false);  // Developer error, this TC is not to be used.
+  }
+  ASSERT(false);  // Developer error - invalid TemplateContext.
+  return "";
+}
+
+// This writes s to a file with the AUTOESCAPE pragma corresponding
+// to the given TemplateContext and then loads it into a template object.
+static Template* StringToTemplateWithAutoEscaping(const string& s,
+                                                  Strip strip,
+                                                  TemplateContext context) {
+  string text = GetPragmaForContext(context) + s;
+  return Template::GetTemplate(StringToTemplateFile(text), strip);
+}
+
+// A helper method used by TestCorrectModifiersForAutoEscape.
+// Populates out with lines of the form:
+// VARNAME:mod1[=val1][:mod2[=val2]]...\n from the dump of the template
+// and compares against the expected string.
+static void AssertCorrectModifiersInTemplate(Template* tpl,
+                                             const string& text,
+                                             const string& expected_out) {
+  ASSERT(tpl);
+  string dump_out, out;
+  tpl->DumpToString("bogus_filename", &dump_out);
+  string::size_type i, j;
+  i = 0;
+  while ((i = dump_out.find("Variable Node: ", i)) != string::npos) {
+    i += strlen("Variable Node: ");
+    j = dump_out.find("\n", i);
+    out.append(dump_out.substr(i, j - i));   // should be safe.
+    out.append("\n");
+  }
+  ASSERT_STREQ_VERBOSE(expected_out, out, text);
+}
+
+// Wrapper on top of AssertCorrectModifiersInTemplate which first
+// obtains a template from the given contents and template context.
+static void AssertCorrectModifiers(TemplateContext template_type,
+                                   const string& text,
+                                   const string& expected_out) {
+  Strip strip = STRIP_WHITESPACE;
+  Template *tpl = StringToTemplateWithAutoEscaping(text, strip, template_type);
+  AssertCorrectModifiersInTemplate(tpl, text, expected_out);
+}
+
+// A helper method used by TestCorrectModifiersForAutoEscape.
+// Initializes the template in the Auto Escape mode with the
+// given TemplateContext, expands it with the given dictionary
+// and checks that the output matches the expected value.
+static void AssertCorrectEscaping(TemplateContext template_type,
+                                  const TemplateDictionary& dict,
+                                  const string& text,
+                                  const string& expected_out) {
+  Strip strip = STRIP_WHITESPACE;
+  Template *tpl = StringToTemplateWithAutoEscaping(text, strip, template_type);
+  string outstring;
+  tpl->Expand(&outstring, &dict);
+  ASSERT_STREQ_VERBOSE(expected_out, outstring, text);
+}
+
+class DynamicModifier : public GOOGLE_NAMESPACE::TemplateModifier {
+ public:
+  void Modify(const char* in, size_t inlen,
+              const PerExpandData* per_expand_data,
+              ExpandEmitter* outbuf, const string& arg) const {
+    assert(arg.empty());    // we don't take an argument
+    assert(per_expand_data);
+    const char* value = per_expand_data->LookupForModifiersAsString("value");
+    if (value)
+      outbuf->Emit(value);
+  }
+};
+
+class EmphasizeTemplateModifier : public GOOGLE_NAMESPACE::TemplateModifier {
+ public:
+  EmphasizeTemplateModifier(const string& match)
+      : match_(match) {
+  }
+
+  bool MightModify(const PerExpandData* per_expand_data,
+                   const string& arg) const {
+    return strstr(arg.c_str(), match_.c_str());
+  }
+
+  void Modify(const char* in, size_t inlen,
+              const PerExpandData* per_expand_data,
+              ExpandEmitter* outbuf, const string& arg) const {
+    outbuf->Emit(">>");
+    outbuf->Emit(in, inlen);
+    outbuf->Emit("<<");
+  }
+
+ private:
+  string match_;
+};
+
+// This is used by TestAnnotation().  It behaves like
+// TextTemplateAnnotator but just to test our ability to customize
+// annotation, and with stateful one, it prefixes each text annotation
+// with an event (call) count.
+class CustomTestAnnotator : public GOOGLE_NAMESPACE::TextTemplateAnnotator {
+ public:
+  CustomTestAnnotator() : event_count_(0) { }
+  void Reset() { event_count_ = 0; }
+
+  virtual void EmitOpenInclude(ExpandEmitter* emitter, const string& value) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenInclude(emitter, value);
+  }
+  virtual void EmitCloseInclude(ExpandEmitter* emitter) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseInclude(emitter);
+  }
+  virtual void EmitOpenFile(ExpandEmitter* emitter, const string& value) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenFile(emitter, value);
+  }
+  virtual void EmitCloseFile(ExpandEmitter* emitter) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseFile(emitter);
+  }
+  virtual void EmitOpenSection(ExpandEmitter* emitter, const string& value) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenSection(emitter, value);
+  }
+  virtual void EmitCloseSection(ExpandEmitter* emitter) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseSection(emitter);
+  }
+  virtual void EmitOpenVariable(ExpandEmitter* emitter, const string& value) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitOpenVariable(emitter, value);
+  }
+  virtual void EmitCloseVariable(ExpandEmitter* emitter) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitCloseVariable(emitter);
+  }
+  virtual void EmitFileIsMissing(ExpandEmitter* emitter,
+                                    const string& value) {
+    EmitTestPrefix(emitter);
+    GOOGLE_NAMESPACE::TextTemplateAnnotator::EmitFileIsMissing(emitter, value);
+  }
+
+ private:
+  void EmitTestPrefix(ExpandEmitter* emitter) {
+    char buf[128];
+    snprintf(buf, sizeof(buf), "{{EVENT=%d}}", ++event_count_);
+    emitter->Emit(buf);
+  }
+  int event_count_;
+  DISALLOW_COPY_AND_ASSIGN(CustomTestAnnotator);
+};
+
+class TemplateForTest : public Template {
+ public:
+  using Template::kSafeWhitelistedVariables;
+  using Template::kNumSafeWhitelistedVariables;
+ private:
+  // This quiets gcc3, which otherwise complains: "base `Template'
+  // with only non-default constructor in class without a constructor".
+  TemplateForTest();
+};
+
+// Tests annotation, in particular inheriting annotation among children
+// This should be called first, so the filenames don't change as we add
+// more tests.
+static void TestAnnotation() {
+  string incname = StringToTemplateFile("include {{#ISEC}}file{{/ISEC}}\n");
+  string incname2 = StringToTemplateFile("include #2\n");
+  Template* tpl = StringToTemplate(
+      "boo!\n{{>INC}}\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar "
+      "{{VAR:x-foo}}",
+      DO_NOT_STRIP);
+  TemplateDictionary dict("dict");
+  PerExpandData per_expand_data;
+
+  dict.ShowSection("SEC");
+  TemplateDictionary* incdict = dict.AddIncludeDictionary("INC");
+  incdict->SetFilename(incname);
+  incdict->ShowSection("ISEC");
+  dict.AddIncludeDictionary("INC")->SetFilename(incname2);
+  dict.SetValue("VAR", "var");
+
+  // This string is equivalent to "/template." (at least on unix)
+  string slash_tpl(PathJoin(kRootdir, "template."));
+  per_expand_data.SetAnnotateOutput("");
+  char expected[10240];           // 10k should be big enough!
+  snprintf(expected, sizeof(expected),
+           "{{#FILE=%s003}}{{#SEC=__{{MAIN}}__}}boo!\n"
+           "{{#INC=INC}}{{#FILE=%s001}}"
+           "{{#SEC=__{{MAIN}}__}}include {{#SEC=ISEC}}file{{/SEC}}\n"
+           "{{/SEC}}{{/FILE}}{{/INC}}"
+           "{{#INC=INC}}{{#FILE=%s002}}"
+           "{{#SEC=__{{MAIN}}__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}"
+           "\nhi {{#SEC=SEC}}lo{{/SEC}} bar "
+           "{{#VAR=VAR:x-foo<not registered>}}var{{/VAR}}{{/SEC}}{{/FILE}}",
+           (FLAGS_test_tmpdir + slash_tpl).c_str(),
+           (FLAGS_test_tmpdir + slash_tpl).c_str(),
+           (FLAGS_test_tmpdir + slash_tpl).c_str());
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data, expected, true);
+
+  // Test ability to set custom annotator.
+  CustomTestAnnotator custom_annotator;
+  per_expand_data.SetAnnotator(&custom_annotator);
+  snprintf(expected, sizeof(expected),
+           "{{EVENT=1}}{{#FILE=%s003}}"
+           "{{EVENT=2}}{{#SEC=__{{MAIN}}__}}boo!\n"
+           "{{EVENT=3}}{{#INC=INC}}"
+           "{{EVENT=4}}{{#FILE=%s001}}"
+           "{{EVENT=5}}{{#SEC=__{{MAIN}}__}}include "
+           "{{EVENT=6}}{{#SEC=ISEC}}file"
+           "{{EVENT=7}}{{/SEC}}\n"
+           "{{EVENT=8}}{{/SEC}}"
+           "{{EVENT=9}}{{/FILE}}"
+           "{{EVENT=10}}{{/INC}}"
+           "{{EVENT=11}}{{#INC=INC}}"
+           "{{EVENT=12}}{{#FILE=%s002}}"
+           "{{EVENT=13}}{{#SEC=__{{MAIN}}__}}include #2\n"
+           "{{EVENT=14}}{{/SEC}}"
+           "{{EVENT=15}}{{/FILE}}"
+           "{{EVENT=16}}{{/INC}}\nhi "
+           "{{EVENT=17}}{{#SEC=SEC}}lo"
+           "{{EVENT=18}}{{/SEC}} bar "
+           "{{EVENT=19}}{{#VAR=VAR:x-foo<not registered>}}var"
+           "{{EVENT=20}}{{/VAR}}"
+           "{{EVENT=21}}{{/SEC}}"
+           "{{EVENT=22}}{{/FILE}}",
+           (FLAGS_test_tmpdir + slash_tpl).c_str(),
+           (FLAGS_test_tmpdir + slash_tpl).c_str(),
+           (FLAGS_test_tmpdir + slash_tpl).c_str());
+  // We can't use AssertExpandWithDataIs() on our deliberately stateful
+  // test annotator because it internally does a second expansion
+  // assuming no state change between calls.
+  string custom_outstring;
+  ASSERT(tpl->ExpandWithData(&custom_outstring, &dict, &per_expand_data));
+  ASSERT_STREQ(custom_outstring.c_str(), expected);
+
+  // Unset annotator and continue with next test as test of ability
+  // to revert to built-in annotator.
+  per_expand_data.SetAnnotator(NULL);
+
+  per_expand_data.SetAnnotateOutput(slash_tpl.c_str());
+  snprintf(expected, sizeof(expected),
+           "{{#FILE=%s003}}{{#SEC=__{{MAIN}}__}}boo!\n"
+           "{{#INC=INC}}{{#FILE=%s001}}"
+           "{{#SEC=__{{MAIN}}__}}include {{#SEC=ISEC}}file{{/SEC}}\n"
+           "{{/SEC}}{{/FILE}}{{/INC}}"
+           "{{#INC=INC}}{{#FILE=%s002}}"
+           "{{#SEC=__{{MAIN}}__}}include #2\n{{/SEC}}{{/FILE}}{{/INC}}"
+           "\nhi {{#SEC=SEC}}lo{{/SEC}} bar "
+           "{{#VAR=VAR:x-foo<not registered>}}var{{/VAR}}{{/SEC}}{{/FILE}}",
+           (slash_tpl).c_str(),
+           (slash_tpl).c_str(),
+           (slash_tpl).c_str());
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data, expected, true);
+
+  per_expand_data.SetAnnotateOutput(NULL);   // should turn off annotations
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
+                         "boo!\ninclude file\ninclude #2\n\nhi lo bar var",
+                         true);
+
+  // Test that even if we set an annotator we shouldn't get annotation
+  // if it is not turned on with SetAnnotateOutput().
+  per_expand_data.SetAnnotator(&custom_annotator);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
+                         "boo!\ninclude file\ninclude #2\n\nhi lo bar var",
+                         true);
+
+  // Test annotation of "missing include" condition.
+  Template* one_inc_tpl =
+      StringToTemplate("File contents: {{>INC}}\n", DO_NOT_STRIP);
+  TemplateDictionary dict_missing_file("dict_with_missing_file");
+  dict_missing_file.AddIncludeDictionary("INC")->SetFilename("missing.tpl");
+
+  per_expand_data.SetAnnotateOutput("");
+  per_expand_data.SetAnnotator(NULL);
+  snprintf(expected, sizeof(expected),
+           "{{#FILE=%s004}}{{#SEC=__{{MAIN}}__}}File contents: "
+           "{{#INC=INC}}{{MISSING_FILE=missing.tpl}}{{/INC}}\n"
+           "{{/SEC}}{{/FILE}}",
+           (FLAGS_test_tmpdir + slash_tpl).c_str());
+  // We expect a false return value because of the missing file.
+  AssertExpandWithDataIs(one_inc_tpl, &dict_missing_file, &per_expand_data,
+                         expected, false);
+
+  // Same missing include test with custom annotator
+  custom_annotator.Reset();
+  per_expand_data.SetAnnotator(&custom_annotator);
+  snprintf(expected, sizeof(expected),
+           "{{EVENT=1}}{{#FILE=%s004}}"
+           "{{EVENT=2}}{{#SEC=__{{MAIN}}__}}File contents: "
+           "{{EVENT=3}}{{#INC=INC}}"
+           "{{EVENT=4}}{{MISSING_FILE=missing.tpl}}"
+           "{{EVENT=5}}{{/INC}}\n"
+           "{{EVENT=6}}{{/SEC}}"
+           "{{EVENT=7}}{{/FILE}}",
+           (FLAGS_test_tmpdir + slash_tpl).c_str());
+  // See comment above on why we can't use AssertExpandWithDataIs() for
+  // our stateful test annotator.
+  custom_outstring.clear();
+  ASSERT(!one_inc_tpl->ExpandWithData(&custom_outstring,
+                                      &dict_missing_file,
+                                      &per_expand_data));
+  ASSERT_STREQ(custom_outstring.c_str(), expected);
+}
+
+TEST(Template, CheckWhitelistedVariablesSorted) {
+  // NOTE(williasr): kSafeWhitelistedVariables must be sorted, it's accessed
+  // using binary search.
+  for (size_t i = 1; i < TemplateForTest::kNumSafeWhitelistedVariables; i++) {
+    assert(strcmp(TemplateForTest::kSafeWhitelistedVariables[i-1],
+                  TemplateForTest::kSafeWhitelistedVariables[i]) < 0);
+  }
+}
+
+
+// The following tests test various aspects of how Expand() should behave.
+TEST(Template, WeirdSyntax) {
+  TemplateDictionary dict("dict");
+
+  // When we see {{{, we should match the second {{, not the first.
+  Template* tpl1 = StringToTemplate("hi {{{! VAR {{!VAR} }} lo",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl1, &dict, "hi { lo", true);
+
+  // Likewise for }}}
+  Template* tpl2 = StringToTemplate("fn(){{{BI_NEWLINE}} x=4;{{BI_NEWLINE}}}",
+                                    DO_NOT_STRIP);
+  AssertExpandIs(tpl2, &dict, "fn(){\n x=4;\n}", true);
+
+  // Try lots of {'s!
+  Template* tpl3 = StringToTemplate("{{{{{{VAR}}}}}}}}", DO_NOT_STRIP);
+  AssertExpandIs(tpl3, &dict, "{{{{}}}}}}", true);
+}
+
+TEST(Template, Comment) {
+  TemplateDictionary dict("dict");
+  Template* tpl1 = StringToTemplate("hi {{!VAR}} lo",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl1, &dict, "hi  lo", true);
+
+  Template* tpl2 = StringToTemplate("hi {{!VAR {VAR} }} lo",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl2, &dict, "hi  lo", true);
+
+  Template* tpl3 = StringToTemplate("hi {{! VAR {{!VAR} }} lo",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl3, &dict, "hi  lo", true);
+}
+
+TEST(Template, SetMarkerDelimiters) {
+  TemplateDictionary dict("dict");
+  dict.SetValue("VAR", "yo");
+  Template* tpl1 = StringToTemplate("{{=| |=}}\nhi |VAR| {{lo}}",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl1, &dict, "hi yo {{lo}}", true);
+
+  Template* tpl2 = StringToTemplate("{{=| |=}}hi |VAR| {{lo}}",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl2, &dict, "hi yo {{lo}}", true);
+
+  Template* tpl3 = StringToTemplate("{{=| ||=}}hi ||VAR|||VAR|| {{lo}}",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl3, &dict, "hi |yoyo {{lo}}", true);
+
+  Template* tpl4 = StringToTemplate("{{=< >=}}hi <<VAR>> {{lo}}",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl4, &dict, "hi <yo> {{lo}}", true);
+
+  Template* tpl4b = StringToTemplate("{{=<< >>=}}hi <<VAR>> {{lo}}",
+                                     STRIP_WHITESPACE);
+  AssertExpandIs(tpl4b, &dict, "hi yo {{lo}}", true);
+
+  Template* tpl4c = StringToTemplate("{{=<< <<=}}hi <<VAR<< {{lo}}",
+                                     STRIP_WHITESPACE);
+  AssertExpandIs(tpl4c, &dict, "hi yo {{lo}}", true);
+
+  Template* tpl5 = StringToTemplate("hi {{VAR}} lo\n{{=< >=}}\n"
+                                    "hi {{VAR}} lo\n"
+                                    "hi <VAR> lo\n<={ }=>\n"
+                                    "hi {{VAR}} lo\n{={{ }}=}\n"
+                                    "hi {{VAR}} lo\n",
+                                    STRIP_WHITESPACE);
+  AssertExpandIs(tpl5, &dict,
+                 "hi yo lohi {{VAR}} lohi yo lohi {yo} lohi yo lo",
+                 true);
+
+  Template* tpl6 = StringToTemplate("hi {{VAR}} lo\n{{=< >}}\n",
+                                    STRIP_WHITESPACE);
+  ASSERT(tpl6 == NULL);
+
+  Template* tpl7 = StringToTemplate("hi {{VAR}} lo\n{{=<>}}\n",
+                                    STRIP_WHITESPACE);
+  ASSERT(tpl7 == NULL);
+
+  Template* tpl8 = StringToTemplate("hi {{VAR}} lo\n{{=<  >=}}\n",
+                                    STRIP_WHITESPACE);
+  ASSERT(tpl8 == NULL);
+
+  Template* tpl9 = StringToTemplate("hi {{VAR}} lo\n{{==}}\n",
+                                    STRIP_WHITESPACE);
+  ASSERT(tpl9 == NULL);
+
+  Template* tpl10 = StringToTemplate("hi {{VAR}} lo\n{{=}}\n",
+                                     STRIP_WHITESPACE);
+  ASSERT(tpl10 == NULL);
+
+  // Test that {{= =}} is a "removable" marker.
+  Template* tpl11 = StringToTemplate("line\n  {{=| |=}} \nhi |VAR| {{lo}}\n",
+                                     STRIP_BLANK_LINES);
+  AssertExpandIs(tpl11, &dict, "line\nhi yo {{lo}}\n", true);
+
+  // Test that "removable" markers survive marker-modification.
+  Template* tpl12 = StringToTemplate("  {{#SEC1}}  \n"
+                                     "{{=| |=}}    |VAR|\n"
+                                     "  |/SEC1|\ntada! |VAR|\n"
+                                     "hello|=<< >>=|\n"
+                                     "   <<! a blank line>>  \n"
+                                     "done",
+                                     STRIP_BLANK_LINES);
+  AssertExpandIs(tpl12, &dict, "tada! yo\nhello\ndone", true);
+}
+
+TEST(Template, Variable) {
+  Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+  AssertExpandIs(tpl, &dict, "hi  lo", true);
+  dict.SetValue("VAR", "yo");
+  AssertExpandIs(tpl, &dict, "hi yo lo", true);
+  dict.SetValue("VAR", "yoyo");
+  AssertExpandIs(tpl, &dict, "hi yoyo lo", true);
+  dict.SetValue("VA", "noyo");
+  dict.SetValue("VAR ", "noyo2");
+  dict.SetValue("var", "noyo3");
+  AssertExpandIs(tpl, &dict, "hi yoyo lo", true);
+
+  // Sanity check string template behaviour while we're at it.
+  Template* tpl2 = Template::StringToTemplate("hi {{VAR}} lo",
+                                              STRIP_WHITESPACE);
+  TemplateDictionary dict2("dict");
+  AssertExpandIs(tpl2, &dict2, "hi  lo", true);
+  dict2.SetValue("VAR", "yo");
+  AssertExpandIs(tpl2, &dict2, "hi yo lo", true);
+  dict2.SetValue("VAR", "yoyo");
+  AssertExpandIs(tpl2, &dict2, "hi yoyo lo", true);
+  dict2.SetValue("VA", "noyo");
+  dict2.SetValue("VAR ", "noyo2");
+  dict2.SetValue("var", "noyo3");
+  AssertExpandIs(tpl2, &dict2, "hi yoyo lo", true);
+  delete tpl2;   // You have to delete StringToTemplate strings
+}
+
+TEST(Template, VariableWithModifiers) {
+  Template* tpl = StringToTemplate("hi {{VAR:html_escape}} lo",
+                                   STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+
+  // Test with no modifiers.
+  dict.SetValue("VAR", "yo");
+  AssertExpandIs(tpl, &dict, "hi yo lo", true);
+  dict.SetValue("VAR", "yo&yo");
+  AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
+
+  // Test with URL escaping.
+  tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u}}\">",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%26yo\">", true);
+  tpl = StringToTemplate("<a href='/servlet?param={{VAR:url_query_escape}}'>",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "<a href='/servlet?param=yo%26yo'>", true);
+
+  // Test with multiple URL escaping.
+  tpl = StringToTemplate("<a href=\"/servlet?param={{VAR:u:u}}\">",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "<a href=\"/servlet?param=yo%2526yo\">", true);
+
+  // Test HTML escaping.
+  tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
+
+  tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo&amp;amp;yo lo", true);
+
+  // Test special HTML escaping
+  dict.SetValue("URL_VAR", "javascript:void");
+  dict.SetValue("SNIPPET_VAR", "<b>foo & bar</b>");
+  tpl = StringToTemplate("hi {{VAR:H=attribute}} {{URL_VAR:H=url}} "
+                         "{{SNIPPET_VAR:H=snippet}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo_yo # <b>foo & bar</b> lo", true);
+
+  // Test with custom modifiers [regular or XssSafe should not matter].
+  ASSERT(GOOGLE_NAMESPACE::AddModifier("x-test",
+                                &GOOGLE_NAMESPACE::html_escape));
+  ASSERT(GOOGLE_NAMESPACE::AddModifier("x-test-arg=",
+                                &GOOGLE_NAMESPACE::html_escape));
+  ASSERT(GOOGLE_NAMESPACE::AddXssSafeModifier("x-test-arg=snippet",
+                                       &GOOGLE_NAMESPACE::snippet_escape));
+
+  tpl = StringToTemplate("hi {{VAR:x-test}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo&amp;yo lo", true);
+  tpl = StringToTemplate("hi {{SNIPPET_VAR:x-test-arg=snippet}} lo",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi <b>foo & bar</b> lo", true);
+  tpl = StringToTemplate("hi {{VAR:x-unknown}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo&yo lo", true);
+
+  // Test with a modifier taking per-expand data
+  DynamicModifier dynamic_modifier;
+  ASSERT(GOOGLE_NAMESPACE::AddModifier("x-dynamic", &dynamic_modifier));
+  PerExpandData per_expand_data;
+  tpl = StringToTemplate("hi {{VAR:x-dynamic}} lo", STRIP_WHITESPACE);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi  lo", true);
+  per_expand_data.InsertForModifiers("value", "foo");
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi foo lo", true);
+  per_expand_data.InsertForModifiers("value", "bar");
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi bar lo", true);
+  per_expand_data.InsertForModifiers("value", NULL);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data, "hi  lo", true);
+
+  // Test with no modifiers.
+  tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo&yo lo", true);
+
+  // Check that ordering is right
+  dict.SetValue("VAR", "yo\nyo");
+  tpl = StringToTemplate("hi {{VAR:h}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo yo lo", true);
+  tpl = StringToTemplate("hi {{VAR:p}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo\nyo lo", true);
+  tpl = StringToTemplate("hi {{VAR:j}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
+  tpl = StringToTemplate("hi {{VAR:h:j}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo yo lo", true);
+  tpl = StringToTemplate("hi {{VAR:j:h}} lo", STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
+
+  // Check more complicated modifiers using fullname
+  tpl = StringToTemplate("hi {{VAR:javascript_escape:h}} lo",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
+  tpl = StringToTemplate("hi {{VAR:j:html_escape}} lo",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
+  tpl = StringToTemplate("hi {{VAR:pre_escape:j}} lo",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict, "hi yo\\nyo lo", true);
+
+  // Check that illegal modifiers are rejected
+  tpl = StringToTemplate("hi {{VAR:j:h2}} lo", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+  tpl = StringToTemplate("hi {{VAR:html_ecap}} lo", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+  tpl = StringToTemplate("hi {{VAR:javascript_escaper}} lo",
+                         STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+  tpl = StringToTemplate("hi {{VAR:js:j}} lo", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+  tpl = StringToTemplate("hi {{VAR:}} lo", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+
+  // Check we reject modifier-values when we ought to
+  tpl = StringToTemplate("hi {{VAR:j=4}} lo", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+  tpl = StringToTemplate("hi {{VAR:html_escape=yes}} lo", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+  tpl = StringToTemplate("hi {{VAR:url_query_escape=wombats}} lo",
+                         STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+
+  // Check we don't allow modifiers on sections
+  tpl = StringToTemplate("hi {{#VAR:h}} lo {{/VAR}}", STRIP_WHITESPACE);
+  ASSERT(tpl == NULL);
+
+  // Test when expanded grows by more than 12% per modifier.
+  dict.SetValue("VAR", "http://a.com?b=c&d=e&f=g&q=a>b");
+  tpl = StringToTemplate("{{VAR:u:j:h}}",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict,
+                 "http%3A//a.com%3Fb%3Dc%26d%3De%26f%3Dg%26q%3Da%3Eb",
+                 true);
+
+  // As above with 4 modifiers.
+  dict.SetValue("VAR", "http://a.com?b=c&d=e&f=g&q=a>b");
+  tpl = StringToTemplate("{{VAR:u:j:h:h}}",
+                         STRIP_WHITESPACE);
+  AssertExpandIs(tpl, &dict,
+                 "http%3A//a.com%3Fb%3Dc%26d%3De%26f%3Dg%26q%3Da%3Eb",
+                 true);
+}
+
+TEST(Template, Section) {
+  Template* tpl = StringToTemplate(
+      "boo!\nhi {{#SEC}}lo{{#SUBSEC}}jo{{/SUBSEC}}{{/SEC}} bar",
+      STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+  AssertExpandIs(tpl, &dict, "boo!hi  bar", true);
+  dict.ShowSection("SEC");
+  AssertExpandIs(tpl, &dict, "boo!hi lo bar", true);
+  dict.ShowSection("SEC");
+  AssertExpandIs(tpl, &dict, "boo!hi lo bar", true);
+  // This should work even though subsec isn't a child of the main dict
+  dict.ShowSection("SUBSEC");
+  AssertExpandIs(tpl, &dict, "boo!hi lojo bar", true);
+
+  TemplateDictionary dict2("dict2");
+  dict2.AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict2, "boo!hi lo bar", true);
+  dict2.AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true);
+  dict2.AddSectionDictionary("sec");
+  AssertExpandIs(tpl, &dict2, "boo!hi lolo bar", true);
+  dict2.ShowSection("SUBSEC");
+  AssertExpandIs(tpl, &dict2, "boo!hi lojolojo bar", true);
+}
+
+
+TEST(Template, SectionSeparator) {
+  Template* tpl = StringToTemplate(
+      "hi {{#SEC}}lo{{#SEC_separator}}jo{{JO}}{{/SEC_separator}}{{/SEC}} bar",
+      STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+  AssertExpandIs(tpl, &dict, "hi  bar", true);
+  // Since SEC is only expanded once, the separator section shouldn't show.
+  dict.ShowSection("SEC");
+  AssertExpandIs(tpl, &dict, "hi lo bar", true);
+  dict.ShowSection("SEC");
+  AssertExpandIs(tpl, &dict, "hi lo bar", true);
+  // This should work even though SEC_separator isn't a child of the
+  // main dict.  It verifies SEC_separator is just a normal section, too.
+  dict.ShowSection("SEC_separator");
+  AssertExpandIs(tpl, &dict, "hi lojo bar", true);
+
+  TemplateDictionary dict2("dict2");
+  dict2.AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict2, "hi lo bar", true);
+  dict2.AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict2, "hi lojolo bar", true);
+  // This is a weird case: using separator and specifying manually.
+  dict2.ShowSection("SEC_separator");
+  AssertExpandIs(tpl, &dict2, "hi lojojolojo bar", true);
+
+  TemplateDictionary dict3("dict3");
+  TemplateDictionary* sec1 = dict3.AddSectionDictionary("SEC");
+  TemplateDictionary* sec2 = dict3.AddSectionDictionary("SEC");
+  TemplateDictionary* sec3 = dict3.AddSectionDictionary("SEC");
+  dict3.SetValue("JO", "J");
+  AssertExpandIs(tpl, &dict3, "hi lojoJlojoJlo bar", true);
+  sec1->SetValue("JO", "JO");
+  AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJlo bar", true);
+  sec2->SetValue("JO", "JOO");
+  AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlo bar", true);
+  dict3.AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlojoJlo bar", true);
+  sec3->AddSectionDictionary("SEC_separator");
+  AssertExpandIs(tpl, &dict3, "hi lojoJOlojoJOOlojoJjoJlo bar", true);
+
+  // Make sure we don't do anything special with var or include names
+  Template* tpl2 = StringToTemplate(
+      "hi {{#SEC}}lo{{>SEC_separator}}{{/SEC}} bar",
+      STRIP_WHITESPACE);
+  AssertExpandIs(tpl2, &dict2, "hi lolo bar", true);
+
+  Template* tpl3 = StringToTemplate(
+      "hi {{#SEC}}lo{{SEC_separator}}{{/SEC}} bar",
+      STRIP_WHITESPACE);
+  dict2.SetValue("SEC_separator", "-");
+  AssertExpandIs(tpl3, &dict2, "hi lo-lo- bar", true);
+}
+
+TEST(Template, Include) {
+  string incname = StringToTemplateFile("include file\n");
+  string incname2 = StringToTemplateFile("inc2a\ninc2b\n");
+  string incname_bad = StringToTemplateFile("{{syntax_error");
+  Template* tpl = StringToTemplate("hi {{>INC}} bar\n", STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+  AssertExpandIs(tpl, &dict, "hi  bar", true);
+  dict.AddIncludeDictionary("INC");
+  AssertExpandIs(tpl, &dict, "hi  bar", true);   // noop: no filename was set
+  dict.AddIncludeDictionary("INC")->SetFilename("/notarealfile ");
+  AssertExpandIs(tpl, &dict, "hi  bar", false);   // noop: illegal filename
+  dict.AddIncludeDictionary("INC")->SetFilename(incname);
+  AssertExpandIs(tpl, &dict, "hi include file bar", false);
+  dict.AddIncludeDictionary("INC")->SetFilename(incname_bad);
+  AssertExpandIs(tpl, &dict, "hi include file bar",
+                 false);  // noop: syntax error
+  dict.AddIncludeDictionary("INC")->SetFilename(incname);
+  AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false);
+  dict.AddIncludeDictionary("inc")->SetFilename(incname);
+  AssertExpandIs(tpl, &dict, "hi include fileinclude file bar", false);
+  dict.AddIncludeDictionary("INC")->SetFilename(incname2);
+  AssertExpandIs(tpl, &dict,
+                 "hi include fileinclude fileinc2ainc2b bar", false);
+
+  // Now test that includes preserve Strip
+  Template* tpl2 = StringToTemplate("hi {{>INC}} bar", DO_NOT_STRIP);
+  AssertExpandIs(tpl2, &dict,
+                 "hi include file\ninclude file\ninc2a\ninc2b\n bar", false);
+
+  // Test that if we indent the include, every line on the include
+  // is indented.
+  Template* tpl3 = StringToTemplate("hi\n  {{>INC}} bar", DO_NOT_STRIP);
+  AssertExpandIs(tpl3, &dict,
+                 "hi\n  include file\n  include file\n"
+                 "  inc2a\n  inc2b\n   bar",
+                 false);
+  // But obviously, if we strip leading whitespace, no indentation.
+  Template* tpl4 = StringToTemplate("hi\n  {{>INC}} bar", STRIP_WHITESPACE);
+  AssertExpandIs(tpl4, &dict,
+                 "hiinclude fileinclude fileinc2ainc2b bar", false);
+  // And if it's not a whitespace indent, we don't indent either.
+  Template* tpl5 = StringToTemplate("hi\n - {{>INC}} bar", DO_NOT_STRIP);
+  AssertExpandIs(tpl5, &dict,
+                 "hi\n - include file\ninclude file\n"
+                 "inc2a\ninc2b\n bar",
+                 false);
+  // Make sure we indent properly at the beginning.
+  Template* tpl6 = StringToTemplate("  {{>INC}}\nbar", DO_NOT_STRIP);
+  AssertExpandIs(tpl6, &dict,
+                 "  include file\n  include file\n"
+                 "  inc2a\n  inc2b\n  \nbar",
+                 false);
+  // And deal correctly when we include twice in a row.
+  Template* tpl7 = StringToTemplate("  {{>INC}}-{{>INC}}", DO_NOT_STRIP);
+  AssertExpandIs(tpl7, &dict,
+                 "  include file\n  include file\n  inc2a\n  inc2b\n  "
+                 "-include file\ninclude file\ninc2a\ninc2b\n",
+                 false);
+}
+
+TEST(Template, IncludeWithModifiers) {
+  string incname = StringToTemplateFile("include & print file\n");
+  string incname2 = StringToTemplateFile("inc2\n");
+  string incname3 = StringToTemplateFile("yo&yo");
+  // Note this also tests that html-escape, but not javascript-escape or
+  // pre-escape, escapes \n to <space>
+  Template* tpl1 = StringToTemplate("hi {{>INC:h}} bar\n", DO_NOT_STRIP);
+  Template* tpl2 = StringToTemplate("hi {{>INC:javascript_escape}} bar\n",
+                                    DO_NOT_STRIP);
+  Template* tpl3 = StringToTemplate("hi {{>INC:pre_escape}} bar\n",
+                                    DO_NOT_STRIP);
+  Template* tpl4 = StringToTemplate("hi {{>INC:u}} bar\n", DO_NOT_STRIP);
+  // Test that if we include the same template twice, once with a modifer
+  // and once without, they each get applied properly.
+  Template* tpl5 = StringToTemplate("hi {{>INC:h}} bar {{>INC}} baz\n",
+                                    DO_NOT_STRIP);
+
+  TemplateDictionary dict("dict");
+  AssertExpandIs(tpl1, &dict, "hi  bar\n", true);
+  dict.AddIncludeDictionary("INC")->SetFilename(incname);
+  AssertExpandIs(tpl1, &dict, "hi include &amp; print file  bar\n", true);
+  dict.AddIncludeDictionary("INC")->SetFilename(incname2);
+  AssertExpandIs(tpl1, &dict, "hi include &amp; print file inc2  bar\n",
+                 true);
+  AssertExpandIs(tpl2, &dict, "hi include \\x26 print file\\ninc2\\n bar\n",
+                 true);
+  AssertExpandIs(tpl3, &dict, "hi include &amp; print file\ninc2\n bar\n",
+                 true);
+  dict.AddIncludeDictionary("INC")->SetFilename(incname3);
+  AssertExpandIs(tpl4, &dict,
+                 "hi include+%26+print+file%0Ainc2%0Ayo%26yo bar\n",
+                 true);
+  AssertExpandIs(tpl5, &dict,
+                 "hi include &amp; print file inc2 yo&amp;yo bar "
+                 "include & print file\ninc2\nyo&yo baz\n",
+                 true);
+
+  // Don't test modifier syntax here; that's in TestVariableWithModifiers()
+}
+
+// Make sure we don't deadlock when a template includes itself.
+// This also tests we handle recursive indentation properly.
+TEST(Template, RecursiveInclude) {
+  string incname = StringToTemplateFile("hi {{>INC}} bar\n  {{>INC}}!");
+  Template* tpl = Template::GetTemplate(incname, DO_NOT_STRIP);
+  TemplateDictionary dict("dict");
+  dict.AddIncludeDictionary("INC")->SetFilename(incname);
+  // Note the last line is indented 4 spaces instead of 2.  This is
+  // because the last sub-include is indented.
+  AssertExpandIs(tpl, &dict, "hi hi  bar\n  ! bar\n  hi  bar\n    !!", true);
+}
+
+// Tests that vars inherit/override their parents properly
+TEST(Template, Inheritence) {
+  Template* tpl = StringToTemplate("{{FOO}}{{#SEC}}{{FOO}}{{#SEC}}{{FOO}}{{/SEC}}{{/SEC}}",
+                                   STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+  dict.SetValue("FOO", "foo");
+  dict.ShowSection("SEC");
+  AssertExpandIs(tpl, &dict, "foofoofoo", true);
+
+  TemplateDictionary dict2("dict2");
+  dict2.SetValue("FOO", "foo");
+  TemplateDictionary* sec = dict2.AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict2, "foofoofoo", true);
+  sec->SetValue("FOO", "bar");
+  AssertExpandIs(tpl, &dict2, "foobarbar", true);
+  TemplateDictionary* sec2 = sec->AddSectionDictionary("SEC");
+  AssertExpandIs(tpl, &dict2, "foobarbar", true);
+  sec2->SetValue("FOO", "baz");
+  AssertExpandIs(tpl, &dict2, "foobarbaz", true);
+
+  // Now test an include template, which shouldn't inherit from its parents
+  tpl = StringToTemplate("{{FOO}}{{#SEC}}hi{{/SEC}}\n{{>INC}}",
+                         STRIP_WHITESPACE);
+  string incname = StringToTemplateFile(
+      "include {{FOO}}{{#SEC}}invisible{{/SEC}}file\n");
+  TemplateDictionary incdict("dict");
+  incdict.ShowSection("SEC");
+  incdict.SetValue("FOO", "foo");
+  incdict.AddIncludeDictionary("INC")->SetFilename(incname);
+  AssertExpandIs(tpl, &incdict, "foohiinclude file", true);
+}
+
+TEST(Template, TemplateString) {
+  // Make sure using TemplateString and StaticTemplateString for the
+  // dictionary expands the same as using char*'s.
+  Template* tpl = StringToTemplate("hi {{VAR}} lo", STRIP_WHITESPACE);
+  TemplateDictionary dict("dict");
+  dict.SetValue("VAR", TemplateString("short-lived", strlen("short")));
+  AssertExpandIs(tpl, &dict, "hi short lo", true);
+  dict.SetValue("VAR", kHello);
+  AssertExpandIs(tpl, &dict, "hi Hello lo", true);
+}
+
+// Tests that we append to the output string, rather than overwrite
+TEST(Template, Expand) {
+  Template* tpl = StringToTemplate("hi", STRIP_WHITESPACE);
+  TemplateDictionary dict("test_expand");
+  string output("premade");
+  ASSERT(tpl->Expand(&output, &dict));
+  ASSERT_STREQ(output.c_str(), "premadehi");
+
+  tpl = StringToTemplate("   lo   ", STRIP_WHITESPACE);
+  ASSERT(tpl->Expand(&output, &dict));
+  ASSERT_STREQ(output.c_str(), "premadehilo");
+}
+
+TEST(Template, ExpandTemplate) {
+  string filename = StringToTemplateFile("  hi {{THERE}}");
+  TemplateDictionary dict("test_expand");
+  dict.SetValue("THERE", "test");
+  string output;
+  ASSERT(ExpandTemplate(filename, STRIP_WHITESPACE, &dict, &output));
+  ASSERT_STREQ(output.c_str(), "hi test");
+
+  // This will append to output, so we see both together.
+  ASSERT(ExpandWithData(filename, DO_NOT_STRIP, &dict, NULL, &output));
+  ASSERT_STREQ(output.c_str(), "hi test  hi test");
+
+  ASSERT(!ExpandTemplate(filename + " not found", DO_NOT_STRIP, &dict,
+                         &output));
+}
+
+TEST(Template, ExpandWithCustomEmitter) {
+  Template* tpl = StringToTemplate("{{VAR}} {{VAR}}", STRIP_WHITESPACE);
+  TemplateDictionary dict("test_expand");
+  dict.SetValue("VAR", "this song is just six words long");
+  string output;
+  SizeofEmitter e(&output);
+  ASSERT(tpl->Expand(&e, &dict));
+  ASSERT_STREQ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+               output.c_str());
+}
+
+TEST(Template, TemplateExpansionModifier) {
+  string parent_tpl_name = StringToTemplateFile("before {{>INC}} after");
+  string child_tpl_name1 = StringToTemplateFile("child1");
+  string child_tpl_name2 = StringToTemplateFile("child2");
+  Template* tpl = Template::GetTemplate(parent_tpl_name, DO_NOT_STRIP);
+
+  TemplateDictionary dict("parent dict");
+  dict.AddIncludeDictionary("INC")->SetFilename(child_tpl_name1);
+  dict.AddIncludeDictionary("INC")->SetFilename(child_tpl_name2);
+
+  PerExpandData per_expand_data;
+
+  EmphasizeTemplateModifier modifier1(child_tpl_name1);
+  per_expand_data.SetTemplateExpansionModifier(&modifier1);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
+                         "before >>child1<<child2 after", true);
+
+  EmphasizeTemplateModifier modifier2(child_tpl_name2);
+  per_expand_data.SetTemplateExpansionModifier(&modifier2);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
+                         "before child1>>child2<< after", true);
+
+  EmphasizeTemplateModifier modifier3(parent_tpl_name);
+  per_expand_data.SetTemplateExpansionModifier(&modifier3);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
+                         ">>before child1child2 after<<", true);
+
+  per_expand_data.SetTemplateExpansionModifier(NULL);
+  AssertExpandWithDataIs(tpl, &dict, &per_expand_data,
+                         "before child1child2 after", true);
+}
+
+TEST(Template, GetTemplate) {
+  // Tests the cache
+  string filename = StringToTemplateFile("{This is perfectly valid} yay!");
+  Template* tpl1 = Template::GetTemplate(filename, DO_NOT_STRIP);
+  Template* tpl2 = Template::GetTemplate(filename.c_str(), DO_NOT_STRIP);
+  Template* tpl3 = Template::GetTemplate(filename, STRIP_WHITESPACE);
+  ASSERT(tpl1 && tpl2 && tpl3);
+  ASSERT(tpl1 == tpl2);
+  ASSERT(tpl1 != tpl3);
+
+  // Tests that a nonexistent template returns NULL
+  Template* tpl4 = Template::GetTemplate("/yakakak", STRIP_WHITESPACE);
+  ASSERT(!tpl4);
+
+  // Tests that syntax errors cause us to return NULL
+  Template* tpl5 = StringToTemplate("{{This has spaces in it}}", DO_NOT_STRIP);
+  ASSERT(!tpl5);
+  Template* tpl6 = StringToTemplate("{{#SEC}}foo", DO_NOT_STRIP);
+  ASSERT(!tpl6);
+  Template* tpl7 = StringToTemplate("{{#S1}}foo{{/S2}}", DO_NOT_STRIP);
+  ASSERT(!tpl7);
+  Template* tpl8 = StringToTemplate("{{#S1}}foo{{#S2}}bar{{/S1}{{/S2}",
+                                    DO_NOT_STRIP);
+  ASSERT(!tpl8);
+  Template* tpl9 = StringToTemplate("{{noend", DO_NOT_STRIP);
+  ASSERT(!tpl9);
+}
+
+TEST(Template, StringCacheKey) {
+  // If you use these same cache keys somewhere else,
+  // call Template::ClearCache first.
+  const string cache_key_a = "cache key a";
+  const string text = "Test template 1";
+  TemplateDictionary empty_dict("dict");
+
+  // When a string template is registered via StringToTemplateCache,
+  // we can use GetTemplate for that same cache-key under any other
+  // Strip because we cache the contents.
+  Template *tpl1, *tpl2;
+  ASSERT(Template::StringToTemplateCache(cache_key_a, text));
+  tpl1 = Template::GetTemplate(cache_key_a, DO_NOT_STRIP);
+  AssertExpandIs(tpl1, &empty_dict, text, true);
+
+  // Different strip.
+  ASSERT(tpl2 = Template::GetTemplate(cache_key_a, STRIP_BLANK_LINES));
+  ASSERT(tpl2 != tpl1);
+  AssertExpandIs(tpl2, &empty_dict, text, true);
+
+  Template::ClearCache();
+}
+
+TEST(Template, StringGetTemplate) {
+  TemplateDictionary dict("dict");
+
+  // Test cache lookups
+  const char* const tpltext = "{This is perfectly valid} yay!";
+  ASSERT(Template::StringToTemplateCache("tgt", tpltext));
+
+  Template* tpl1 = Template::GetTemplate("tgt", DO_NOT_STRIP);
+  Template* tpl2 = Template::GetTemplate("tgt", STRIP_WHITESPACE);
+  ASSERT(tpl1 && tpl2);
+  ASSERT(tpl1 != tpl2);
+  AssertExpandIs(tpl1, &dict, tpltext, true);
+  AssertExpandIs(tpl2, &dict, tpltext, true);
+
+  // If we register a new string under the same text, it should be
+  // ignored.
+  ASSERT(!Template::StringToTemplateCache("tgt", tpltext));
+  ASSERT(!Template::StringToTemplateCache("tgt", "new text"));
+  Template* tpl3 = Template::GetTemplate("tgt", DO_NOT_STRIP);
+  ASSERT(tpl3 == tpl1);
+  AssertExpandIs(tpl3, &dict, tpltext, true);
+
+  // Tests that syntax errors cause us to return NULL
+  ASSERT(!Template::StringToTemplateCache("tgt2", "{{This has spaces}}"));
+  ASSERT(!Template::StringToTemplateCache("tgt3", "{{#SEC}}foo"));
+  ASSERT(!Template::StringToTemplateCache("tgt4", "{{#S1}}foo{{/S2}}"));
+  ASSERT(!Template::StringToTemplateCache("tgt5",
+                                          "{{#S1}}foo{{#S2}}bar{{/S1}{{/S2}"));
+  ASSERT(!Template::StringToTemplateCache("tgt6", "{{noend"));
+  // And that we didn't cache them by mistake
+  ASSERT(!Template::GetTemplate("tgt2", STRIP_WHITESPACE));
+
+  Template::ClearCache();
+}
+
+TEST(Template, StringTemplateInclude) {
+  Template::ClearCache();   // just for exercise.
+  const string cache_key = "TestStringTemplateInclude";
+  const string cache_key_inc = "TestStringTemplateInclude-inc";
+  const string cache_key_indent = "TestStringTemplateInclude-indent";
+  const string text = "<html>{{>INC}}</html>";
+  const string text_inc = "<div>\n<p>\nUser {{USER}}\n</div>";
+  const string text_indent = "<html>\n  {{>INC}}</html>";
+
+  ASSERT(Template::StringToTemplateCache(cache_key, text));
+  ASSERT(Template::StringToTemplateCache(cache_key_inc, text_inc));
+  ASSERT(Template::StringToTemplateCache(cache_key_indent, text_indent));
+
+  Template *tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
+  ASSERT(tpl);
+
+  TemplateDictionary dict("dict");
+  TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC");
+  sub_dict->SetFilename(cache_key_inc);
+
+  sub_dict->SetValue("USER", "John<>Doe");
+  string expected = "<html><div>\n<p>\nUser John<>Doe\n</div></html>";
+  AssertExpandIs(tpl, &dict, expected, true);
+
+  // Repeat the same except that now the parent has a template-level
+  // directive (by way of the automatic-line-indenter).
+  tpl = Template::GetTemplate(cache_key_indent, DO_NOT_STRIP);
+  ASSERT(tpl);
+  expected =
+      "<html>\n"
+      "  <div>\n"
+      "  <p>\n"
+      "  User John<>Doe\n"
+      "  </div>"
+      "</html>";
+  AssertExpandIs(tpl, &dict, expected, true);
+
+  Template::ClearCache();
+}
+
+TEST(Template, TemplateSearchPath) {
+  const string pathA = PathJoin(FLAGS_test_tmpdir, "a/");
+  const string pathB = PathJoin(FLAGS_test_tmpdir, "b/");
+  CreateOrCleanTestDir(pathA);
+  CreateOrCleanTestDir(pathB);
+
+  TemplateDictionary dict("");
+  Template::SetTemplateRootDirectory(pathA);
+  Template::AddAlternateTemplateRootDirectory(pathB);
+
+  // 1. Show that a template in the secondary path can be found.
+  const string path_b_bar = PathJoin(pathB, "template_bar");
+  StringToFile("b/template_bar", path_b_bar);
+  ASSERT_STREQ(path_b_bar.c_str(),
+               Template::FindTemplateFilename("template_bar").c_str());
+  Template* b_bar = Template::GetTemplate("template_bar", DO_NOT_STRIP);
+  ASSERT(b_bar);
+  AssertExpandIs(b_bar, &dict, "b/template_bar", true);
+
+  // 2. Show that the search stops once the first match is found.
+  //    Create two templates in separate directories with the same name.
+  const string path_a_foo = PathJoin(pathA, "template_foo");
+  StringToFile("a/template_foo", path_a_foo);
+  StringToFile("b/template_foo", PathJoin(pathB, "template_foo"));
+  ASSERT_STREQ(path_a_foo.c_str(),
+               Template::FindTemplateFilename("template_foo").c_str());
+  Template* a_foo = Template::GetTemplate("template_foo", DO_NOT_STRIP);
+  ASSERT(a_foo);
+  AssertExpandIs(a_foo, &dict, "a/template_foo", true);
+
+  // 3. Show that attempting to find a non-existent template gives an
+  //    empty path.
+  ASSERT(Template::FindTemplateFilename("baz").empty());
+
+  CreateOrCleanTestDir(pathA);
+  CreateOrCleanTestDir(pathB);
+}
+
+TEST(Template, RemoveStringFromTemplateCache) {
+  Template::ClearCache();   // just for exercise.
+  const string cache_key = "TestRemoveStringFromTemplateCache";
+  const string text = "<html>here today...</html>";
+
+  TemplateDictionary dict("test");
+  ASSERT(Template::StringToTemplateCache(cache_key, text));
+  Template* tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
+  ASSERT(tpl);
+  AssertExpandIs(tpl, &dict, text, true);
+  tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE);
+  ASSERT(tpl);
+  AssertExpandIs(tpl, &dict, text, true);
+
+  Template::RemoveStringFromTemplateCache(cache_key);
+  tpl = Template::GetTemplate(cache_key, DO_NOT_STRIP);
+  ASSERT(!tpl);
+  tpl = Template::GetTemplate(cache_key, STRIP_WHITESPACE);
+  ASSERT(!tpl);
+  tpl = Template::GetTemplate(cache_key, STRIP_BLANK_LINES);
+  ASSERT(!tpl);
+}
+
+TEST(Template, TemplateCache) {
+  const string filename_a = StringToTemplateFile("Test template 1");
+  const string filename_b = StringToTemplateFile("Test template 2.");
+
+  Template *tpl, *tpl2;
+  ASSERT(tpl = Template::GetTemplate(filename_a, DO_NOT_STRIP));
+
+  ASSERT(tpl2 = Template::GetTemplate(filename_b, DO_NOT_STRIP));
+  ASSERT(tpl2 != tpl);  // different filenames.
+  ASSERT(tpl2 = Template::GetTemplate(filename_a, STRIP_BLANK_LINES));
+  ASSERT(tpl2 != tpl);  // different strip.
+  ASSERT(tpl2 = Template::GetTemplate(filename_b, STRIP_BLANK_LINES));
+  ASSERT(tpl2 != tpl);  // different filenames and strip.
+  ASSERT(tpl2 = Template::GetTemplate(filename_a, DO_NOT_STRIP));
+  ASSERT(tpl2 == tpl);  // same filename and strip.
+}
+
+// Tests that the various strip values all do the expected thing.
+TEST(Template, Strip) {
+  TemplateDictionary dict("dict");
+  dict.SetValue("FOO", "foo");
+
+  const char* tests[][4] = {  // 0: in, 1: do-not-strip, 2: blanklines, 3: ws
+    {"hi!\n", "hi!\n", "hi!\n", "hi!"},
+    {"hi!", "hi!", "hi!", "hi!"},
+    // These test strip-blank-lines, primarily
+    {"{{FOO}}\n\n{{FOO}}", "foo\n\nfoo", "foo\nfoo", "foofoo"},
+    {"{{FOO}}\r\n\r\n{{FOO}}", "foo\r\n\r\nfoo", "foo\r\nfoo", "foofoo"},
+    {"{{FOO}}\n   \n{{FOO}}\n", "foo\n   \nfoo\n", "foo\nfoo\n", "foofoo"},
+    {"{{FOO}}\n{{BI_NEWLINE}}\nb", "foo\n\n\nb", "foo\n\n\nb", "foo\nb"},
+    {"{{FOO}}\n{{!comment}}\nb", "foo\n\nb", "foo\nb", "foob"},
+    {"{{FOO}}\n{{!comment}}{{!comment2}}\nb", "foo\n\nb", "foo\n\nb", "foob"},
+    {"{{FOO}}\n{{>ONE_INC}}\nb", "foo\n\nb", "foo\nb", "foob"},
+    {"{{FOO}}\n\t{{>ONE_INC}}  \nb", "foo\n\t  \nb", "foo\nb", "foob"},
+    {"{{FOO}}\n{{>ONE_INC}}{{>TWO_INC}}\nb", "foo\n\nb", "foo\n\nb", "foob"},
+    {"{{FOO}}\n  {{#SEC}}\ntext \n  {{/SEC}}\n", "foo\n  \n", "foo\n", "foo"},
+    {"{{%AUTOESCAPE context=\"HTML\"}}\nBLA", "\nBLA", "BLA", "BLA"},
+    // These test strip-whitespace
+    {"foo\nbar\n", "foo\nbar\n", "foo\nbar\n", "foobar"},
+    {"{{FOO}}\nbar\n", "foo\nbar\n", "foo\nbar\n", "foobar"},
+    {"  {{FOO}}  {{!comment}}\nb", "  foo  \nb", "  foo  \nb", "foo  b"},
+    {"  {{FOO}}  {{BI_SPACE}}\n", "  foo   \n", "  foo   \n", "foo   "},
+    {"  \t \f\v  \n\r\n  ", "  \t \f\v  \n\r\n  ", "", ""},
+  };
+
+  for (int i = 0; i < sizeof(tests)/sizeof(*tests); ++i) {
+    Template* tpl1 = StringToTemplate(tests[i][0], DO_NOT_STRIP);
+    Template* tpl2 = StringToTemplate(tests[i][0], STRIP_BLANK_LINES);
+    Template* tpl3 = StringToTemplate(tests[i][0], STRIP_WHITESPACE);
+    AssertExpandIs(tpl1, &dict, tests[i][1], true);
+    AssertExpandIs(tpl2, &dict, tests[i][2], true);
+    AssertExpandIs(tpl3, &dict, tests[i][3], true);
+  }
+}
+
+TEST(Template, TemplateRootDirectory) {
+  string filename = StringToTemplateFile("Test template");
+  ASSERT(IsAbspath(filename));
+  Template* tpl1 = Template::GetTemplate(filename, DO_NOT_STRIP);
+  Template::SetTemplateRootDirectory(kRootdir);  // "/"
+  // template-root shouldn't matter for absolute directories
+  Template* tpl2 = Template::GetTemplate(filename, DO_NOT_STRIP);
+  Template::SetTemplateRootDirectory("/sadfadsf/waerfsa/safdg");
+  Template* tpl3 = Template::GetTemplate(filename, DO_NOT_STRIP);
+  ASSERT(tpl1 != NULL);
+  ASSERT(tpl1 == tpl2);
+  ASSERT(tpl1 == tpl3);
+
+  // Now test it actually works by breaking the abspath in various places.
+  // We do it twice, since we don't know if the path-sep is "/" or "\".
+  // NOTE: this depends on filename not using "/" or "\" except as a
+  //       directory separator (so nothing like "/var/tmp/foo\a/weirdfile").
+  const char* const kPathSeps = "/\\";
+  for (const char* path_sep = kPathSeps; *path_sep; path_sep++) {
+    for (string::size_type sep_pos = filename.find(*path_sep, 0);
+         sep_pos != string::npos;
+         sep_pos = filename.find(*path_sep, sep_pos + 1)) {
+      Template::SetTemplateRootDirectory(filename.substr(0, sep_pos + 1));
+      Template* tpl = Template::GetTemplate(filename.substr(sep_pos + 1),
+                                            DO_NOT_STRIP);
+      ASSERT(string(tpl->template_file()) == tpl1->template_file());
+    }
+  }
+}
+
+#if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
+struct ThreadReturn {
+  Template* file_template;
+  bool string_to_template_cache_return;
+  Template* string_template;
+};
+
+// RunThread returns a ThreadReturn* that should be deleted.
+static void* RunThread(void* vfilename) {
+  const char* filename = reinterpret_cast<const char*>(vfilename);
+  ThreadReturn* ret = new ThreadReturn;
+  ret->file_template = Template::GetTemplate(filename, DO_NOT_STRIP);
+  ASSERT(ret->file_template != NULL);
+  const char* const key = "RunThread key";
+  ret->string_to_template_cache_return =
+      StringToTemplateCache(key, " RunThread text ", STRIP_WHITESPACE);
+  ret->string_template = Template::GetTemplate(key, STRIP_WHITESPACE);
+  ASSERT(ret->string_template != NULL);
+  return ret;
+}
+
+TEST(Template, ThreadSafety) {
+  string filename = StringToTemplateFile("(testing thread-safety)");
+
+  // GetTemplate() is the most thread-contended routine.  We get a
+  // template in many threads, and assert we get the same template
+  // from each.
+  pthread_t thread_ids[kNumThreads];
+  for (int i = 0; i < kNumThreads; ++i) {
+    ASSERT(pthread_create(thread_ids+i, NULL, RunThread,
+                          (void*)filename.c_str())
+           == 0);
+  }
+
+  // Wait for all the threads to terminate (should be very quick!)
+  ThreadReturn* first_thread_return = NULL;
+  int num_times_string_to_template_cache_returned_true = 0;
+  for (int i = 0; i < kNumThreads; ++i) {
+    void* vthread_return;
+    ASSERT(pthread_join(thread_ids[i], &vthread_return) == 0);
+    ThreadReturn* thread_return =
+        reinterpret_cast<ThreadReturn*>(vthread_return);
+    if (thread_return->string_to_template_cache_return) {
+      ++num_times_string_to_template_cache_returned_true;
+    }
+    if (first_thread_return == NULL) {   // we're the first thread
+      first_thread_return = thread_return;
+    } else {
+      ASSERT(thread_return->file_template ==
+             first_thread_return->file_template);
+      ASSERT(thread_return->string_template ==
+             first_thread_return->string_template);
+      delete thread_return;
+    }
+  }
+  delete first_thread_return;
+  ASSERT_INTEQ(1, num_times_string_to_template_cache_returned_true);
+  Template::ClearCache();
+}
+#endif  // #if defined(HAVE_PTHREAD) && !defined(NO_THREADS)
+
+// Tests all the static methods in TemplateNamelist
+TEST(Template, TemplateNamelist) {
+  time_t before_time = Now();   // in template_test_util.cc
+  string f1 = StringToTemplateFile("{{This has spaces in it}}");
+  string f2 = StringToTemplateFile("{{#SEC}}foo");
+  string f3 = StringToTemplateFile("{This is ok");
+  // Where we'll copy f1 - f3 to: these are names known at compile-time
+  string f1_copy = PathJoin(FLAGS_test_tmpdir, INVALID1_FN);
+  string f2_copy = PathJoin(FLAGS_test_tmpdir, INVALID2_FN);
+  string f3_copy = PathJoin(FLAGS_test_tmpdir, VALID1_FN);
+  Template::SetTemplateRootDirectory(FLAGS_test_tmpdir);
+  time_t after_time = Now();   // f1, f2, f3 all written by now
+
+  TemplateNamelist::NameListType names = TemplateNamelist::GetList();
+  ASSERT(names.size() == 4);
+  ASSERT(names.count(NONEXISTENT_FN));
+  ASSERT(names.count(INVALID1_FN));
+  ASSERT(names.count(INVALID2_FN));
+  ASSERT(names.count(VALID1_FN));
+
+  // Before creating the files INVALID1_FN, etc., all should be missing.
+  for (int i = 0; i < 3; ++i) {   // should be consistent all 3 times
+    const TemplateNamelist::MissingListType& missing =
+        TemplateNamelist::GetMissingList(false);
+    ASSERT(missing.size() == 4);
+  }
+  // Everyone is missing, but nobody should have bad syntax
+  ASSERT(!TemplateNamelist::AllDoExist());
+  ASSERT(TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP));
+
+  // Now create those files
+  ASSERT(link(f1.c_str(), f1_copy.c_str()) == 0);
+  ASSERT(link(f2.c_str(), f2_copy.c_str()) == 0);
+  ASSERT(link(f3.c_str(), f3_copy.c_str()) == 0);
+  // We also have to clear the template cache, since we created a new file.
+  // ReloadAllIfChanged() would probably work, too.
+  Template::ClearCache();
+
+  // When GetMissingList is false, we don't reload, so you still get all-gone
+  TemplateNamelist::MissingListType missing =
+      TemplateNamelist::GetMissingList(false);
+  ASSERT(missing.size() == 4);
+  // But with true, we should have a different story
+  missing = TemplateNamelist::GetMissingList(true);
+  ASSERT(missing.size() == 1);
+  missing = TemplateNamelist::GetMissingList(false);
+  ASSERT(missing.size() == 1);
+  ASSERT(missing[0] == NONEXISTENT_FN);
+  ASSERT(!TemplateNamelist::AllDoExist());
+
+  // IsAllSyntaxOK did a badsyntax check, before the files were created.
+  // So with a false arg, should still say everything is ok
+  TemplateNamelist::SyntaxListType badsyntax =
+      TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP);
+  ASSERT(badsyntax.size() == 0);
+  // But IsAllSyntaxOK forces a refresh
+  ASSERT(!TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP));
+  badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP);
+  ASSERT(badsyntax.size() == 2);
+  ASSERT(badsyntax[0] == INVALID1_FN || badsyntax[1] == INVALID1_FN);
+  ASSERT(badsyntax[0] == INVALID2_FN || badsyntax[1] == INVALID2_FN);
+  ASSERT(!TemplateNamelist::IsAllSyntaxOkay(DO_NOT_STRIP));
+  badsyntax = TemplateNamelist::GetBadSyntaxList(true, DO_NOT_STRIP);
+  ASSERT(badsyntax.size() == 2);
+
+  time_t modtime = TemplateNamelist::GetLastmodTime();
+  ASSERT(modtime >= before_time && modtime <= after_time);
+  // Now update a file and make sure lastmod time is updated.
+  // Note that since TemplateToFile uses "fake" timestamps way
+  // in the past, this append should definitely give a time
+  // that's after after_time.
+  FILE* fp = fopen(f1_copy.c_str(), "ab");
+  ASSERT(fp);
+  fwrite("\n", 1, 1, fp);
+  fclose(fp);
+  modtime = TemplateNamelist::GetLastmodTime();
+  ASSERT(modtime > after_time);
+
+  // Checking if we can register templates at run time.
+  string f4 = StringToTemplateFile("{{ONE_GOOD_TEMPLATE}}");
+  TemplateNamelist::RegisterTemplate(f4.c_str());
+  names = TemplateNamelist::GetList();
+  ASSERT(names.size() == 5);
+
+  string f5 = StringToTemplateFile("{{ONE BAD TEMPLATE}}");
+  TemplateNamelist::RegisterTemplate(f5.c_str());
+  names = TemplateNamelist::GetList();
+  ASSERT(names.size() == 6);
+  badsyntax = TemplateNamelist::GetBadSyntaxList(false, DO_NOT_STRIP);
+  ASSERT(badsyntax.size() == 2);  // we did not refresh the bad syntax list
+  badsyntax = TemplateNamelist::GetBadSyntaxList(true, DO_NOT_STRIP);
+  // After refresh, the file we just registerd also added in bad syntax list
+  ASSERT(badsyntax.size() == 3);
+
+  TemplateNamelist::RegisterTemplate("A_non_existant_file.tpl");
+  names = TemplateNamelist::GetList();
+  ASSERT(names.size() == 7);
+  missing = TemplateNamelist::GetMissingList(false);
+  ASSERT(missing.size() == 1);  // we did not refresh the missing list
+  missing = TemplateNamelist::GetMissingList(true);
+  // After refresh, the file we just registerd also added in missing list
+  ASSERT(missing.size() == 2);
+}
+
+// This test is not "end-to-end", it doesn't use a dictionary
+// and only outputs what the template system thinks is the
+// correct modifier for variables.
+TEST(Template, CorrectModifiersForAutoEscape) {
+  string text, expected_out;
+
+  // template with no variable, nothing to emit.
+  text = "Static template.";
+  AssertCorrectModifiers(TC_HTML, text, "");
+
+  // Simple templates with one variable substitution.
+
+  // 1. No in-template modifiers. Auto Escaper sets correct ones.
+  text = "Hello {{USER}}";
+  AssertCorrectModifiers(TC_HTML, text, "USER:h\n");
+
+  // Complete URLs in different attributes that take URLs.
+  text = "<a href=\"{{URL}}\">bla</a>";
+  AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
+  text = "<script src=\"{{URL}}\"></script>";
+  AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
+  text = "<img src=\"{{URL}}\">";
+  AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
+  // URL fragment only so just html_escape.
+  text = "<img src=\"/bla?q={{QUERY}}\">";
+  AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n");
+  // URL fragment not quoted, so url_escape.
+  text = "<img src=/bla?q={{QUERY}}>";
+  AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n");
+
+  text = "<br class=\"{{CLASS}}\">";
+  AssertCorrectModifiers(TC_HTML, text, "CLASS:h\n");
+  text = "<br class={{CLASS}}>";
+  AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n");
+  text = "<br {{CLASS}}>";   // CLASS here is name/value pair.
+  AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\n");
+  text = "<br style=\"display:{{DISPLAY}}\">";   // Style attribute.
+  AssertCorrectModifiers(TC_HTML, text, "DISPLAY:c\n");
+
+  // Content inside a style tag should have :c regardless of quoting.
+  text = "<style>color:{{COLOR}}; font:\"{{FONT}}\"</style>";
+  AssertCorrectModifiers(TC_HTML, text, "COLOR:c\nFONT:c\n");
+
+  // onMouseEvent and onKeyUp accept javascript.
+  text = "<a href=\"url\" onkeyup=\"doX('{{ID}}');\">";  // ID quoted
+  AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
+  text = "<a href=\"url\" onclick=\"doX({{ID}});\">";    // ID not quoted
+  AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
+  text = "<a href=\"url\" onclick=\"'{{ID}}'\">";        // not common
+  AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
+  // If ID is javascript code, J=number  will break it, for good and bad.
+  text = "<a href=\"url\" onclick=\"{{ID}}\">";
+  AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
+
+  // Target just needs html escaping.
+  text = "<a href=\"url\" target=\"{{TARGET}}\">";
+  AssertCorrectModifiers(TC_HTML, text, "TARGET:h\n");
+
+  // Test a parsing corner case which uses TemplateDirective
+  // call in the parser to change state properly. To reproduce
+  // both variables should be unquoted and the first should
+  // have no value except the variable substitution.
+  text = "<img class={{CLASS}} src=/bla?q={{QUERY}}>";
+  AssertCorrectModifiers(TC_HTML, text, "CLASS:H=attribute\nQUERY:u\n");
+
+  // TODO(jad): Once we have a fix for it in code, fix me.
+  // Javascript URL is not properly supported, we currently
+  // apply :h which is not sufficient.
+  text = "<a href=\"javascript:foo('{{VAR}}')>bla</a>";
+  AssertCorrectModifiers(TC_HTML, text, "VAR:h\n");
+
+  // Special handling for BI_SPACE and BI_NEWLINE.
+  text = "{{BI_SPACE}}";
+  AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\n");      // Untouched.
+  text = "{{BI_NEWLINE}}";
+  AssertCorrectModifiers(TC_HTML, text, "BI_NEWLINE\n");    // Untouched.
+  // Check that the parser is parsing BI_SPACE, if not, it would have failed.
+  text = "<a href=/bla{{BI_SPACE}}style=\"{{VAR}}\">text</a>";
+  AssertCorrectModifiers(TC_HTML, text, "BI_SPACE\nVAR:c\n");
+
+
+  // XML and JSON modes.
+  text = "<PARAM name=\"{{VAL}}\">{{DATA}}";
+  AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\nDATA:xml_escape\n");
+  text = "{ x = \"{{VAL}}\"}";
+  AssertCorrectModifiers(TC_JSON, text, "VAL:j\n");
+
+  // 2. Escaping modifiers were set, handle them.
+
+  // 2a: Modifier :none is honored whether the escaping is correct or not.
+  text = "Hello {{USER:none}}";                   // :none on its own.
+  AssertCorrectModifiers(TC_HTML, text, "USER:none\n");
+  text = "Hello {{USER:h:none}}";                 // correct escaping.
+  AssertCorrectModifiers(TC_HTML, text, "USER:h:none\n");
+  text = "Hello {{USER:j:none}}";                 // incorrect escaping.
+  AssertCorrectModifiers(TC_HTML, text, "USER:j:none\n");
+  text = "<a href=\"url\" onkeyup=\"doX('{{ID:none}}');\">";
+  AssertCorrectModifiers(TC_HTML, text, "ID:none\n");
+
+  // 2b: Correct modifiers, nothing to change.
+  text = "Hello {{USER:h}}";
+  AssertCorrectModifiers(TC_HTML, text, "USER:h\n");
+  text = "Hello {{USER:U=html}}";  // :U=html is a valid replacement for .h
+  AssertCorrectModifiers(TC_HTML, text, "USER:U=html\n");
+  text = "Hello {{USER:H=url}}";   // :H=url (a.k.a. U=html) is valid too
+  AssertCorrectModifiers(TC_HTML, text, "USER:H=url\n");
+  text = "Hello {{USER:h:j}}";   // Extra :j, honor it.
+  AssertCorrectModifiers(TC_HTML, text, "USER:h:j\n");
+  text = "<a href=\"{{URL:U=html}}\">bla</a>";
+  AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
+  text = "<a href=\"/bla?q={{QUERY:h}}\">bla</a>";  // :h is valid.
+  AssertCorrectModifiers(TC_HTML, text, "QUERY:h\n");
+  text = "<a href=\"/bla?q={{QUERY:u}}\">bla</a>";  // so is :u.
+  AssertCorrectModifiers(TC_HTML, text, "QUERY:u\n");
+  text = "<a href=\"url\" onclick=\"doX('{{ID:j}}');\">";
+  AssertCorrectModifiers(TC_HTML, text, "ID:j\n");
+  text = "<a href=\"url\" onclick=\"doX({{ID:J=number}});\">";
+  AssertCorrectModifiers(TC_HTML, text, "ID:J=number\n");
+  text = "<style>@import url(\"{{URL:U=css}}\")</style>";  // correct :U=css
+  AssertCorrectModifiers(TC_HTML, text, "URL:U=css\n");
+
+  // 2c: Incorrect modifiers, add our own.
+  text = "Hello {{USER:j}}";                          // Missing :h
+  AssertCorrectModifiers(TC_HTML, text, "USER:j:h\n");
+  text = "Hello {{USER:c:c:c:c:c:j}}";                // Still missing :h
+  AssertCorrectModifiers(TC_HTML, text, "USER:c:c:c:c:c:j:h\n");
+  text = "<script>var a = \"{{VAR:h}}\";</script>";   // Missing :j
+  AssertCorrectModifiers(TC_HTML, text, "VAR:h:j\n");
+  text = "<script>var a = \"{{VAR:j:h:j}}\";</script>";   // Extra :h:j
+  AssertCorrectModifiers(TC_HTML, text, "VAR:j:h:j\n");
+  text = "<a href=\"url\" onclick=\"doX({{ID:j}});\">";   // Unquoted
+  AssertCorrectModifiers(TC_HTML, text, "ID:j:J=number\n");
+
+  // 2d: Custom modifiers are maintained.
+  text = "Hello {{USER:x-bla}}";                  // Missing :h
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h\n");
+  text = "Hello {{USER:x-bla:h}}";                // Correct, accept it.
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h\n");
+  text = "Hello {{USER:x-bla:x-foo}}";            // Missing :h
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:x-foo:h\n");
+  text = "Hello {{USER:x-bla:none}}";             // Complete due to :none
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:none\n");
+  text = "Hello {{USER:h:x-bla}}";                // Still missing :h.
+  AssertCorrectModifiers(TC_HTML, text, "USER:h:x-bla:h\n");
+  text = "Hello {{USER:x-bla:h:x-foo}}";          // Still missing :h
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h:x-foo:h\n");
+  text = "Hello {{USER:x-bla:h:x-foo:h}}";        // Valid, accept it.
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-bla:h:x-foo:h\n");
+
+  // 2e: Equivalent modifiers are honored. All HTML Escapes.
+  text = "Hello {{USER:p}}";
+  AssertCorrectModifiers(TC_HTML, text, "USER:p\n");
+  text = "Hello {{USER:H=attribute}}";
+  AssertCorrectModifiers(TC_HTML, text, "USER:H=attribute\n");
+  text = "Hello {{USER:H=snippet}}";
+  AssertCorrectModifiers(TC_HTML, text, "USER:H=snippet\n");
+  text = "Hello {{USER:H=pre}}";
+  AssertCorrectModifiers(TC_HTML, text, "USER:H=pre\n");
+  // All URL + HTML Escapes.
+  text = "<a href=\"{{URL:H=url}}\">bla</a>";
+  AssertCorrectModifiers(TC_HTML, text, "URL:H=url\n");
+  text = "<a href=\"{{URL:U=html}}\">bla</a>";
+  AssertCorrectModifiers(TC_HTML, text, "URL:U=html\n");
+
+  // 2f: Initialize template in Javascript Context.
+  text = "var a = '{{VAR}}'";                     // Escaping not given.
+  AssertCorrectModifiers(TC_JS, text, "VAR:j\n");
+  text = "var a = '{{VAR:none}}'";                // Variable safe.
+  AssertCorrectModifiers(TC_JS, text, "VAR:none\n");
+  text = "var a = '{{VAR:j}}'";                   // Escaping correct.
+  AssertCorrectModifiers(TC_JS, text, "VAR:j\n");
+  text = "var a = '{{VAR:h}}'";                   // Escaping incorrect.
+  AssertCorrectModifiers(TC_JS, text, "VAR:h:j\n");
+  text = "var a = '{{VAR:J=number}}'";            // Not considered equiv.
+  AssertCorrectModifiers(TC_JS, text, "VAR:J=number:j\n");
+
+  // 2g: Honor any modifiers for BI_SPACE and BI_NEWLINE.
+  text = "{{BI_NEWLINE:j}}";     // An invalid modifier for the context.
+  AssertCorrectModifiers(TC_HTML, text, "BI_NEWLINE:j\n");
+  text = "{{BI_SPACE:h}}";       // An otherwise valid modifier.
+  AssertCorrectModifiers(TC_HTML, text, "BI_SPACE:h\n");
+  text = "{{BI_SPACE:x-bla}}";   // Also support custom modifiers.
+  AssertCorrectModifiers(TC_HTML, text, "BI_SPACE:x-bla\n");
+
+  // 2h: TC_CSS, TC_XML and TC_JSON
+  text = "H1{margin-{{START_EDGE}}:0;\n text-align:{{END_EDGE}}\n}";
+  AssertCorrectModifiers(TC_CSS, text, "START_EDGE:c\nEND_EDGE:c\n");
+  text = "body{background:url('{{URL:U=css}}')}";  // :U=css valid substitute
+  AssertCorrectModifiers(TC_CSS, text, "URL:U=css\n");
+  text = "body{background:url('{{URL:U=html}}')}";  // Not valid, will add :c.
+  AssertCorrectModifiers(TC_CSS, text, "URL:U=html:c\n");
+  text = "<PARAM name=\"{{VAL:xml_escape}}\">";   // Correct escaping
+  AssertCorrectModifiers(TC_XML, text, "VAL:xml_escape\n");
+  text = "<PARAM name=\"{{VAL:H=attribute}}\">";   // XSS equivalent
+  AssertCorrectModifiers(TC_XML, text, "VAL:H=attribute\n");
+  text = "<PARAM name=\"{{VAL:h}}\">";   // XSS equivalent
+  AssertCorrectModifiers(TC_XML, text, "VAL:h\n");
+  text = "<PARAM name=\"{{VAL:H=pre}}\">";   // Not XSS equivalent
+  AssertCorrectModifiers(TC_XML, text, "VAL:H=pre:xml_escape\n");
+  text = "<PARAM name=\"{{VAL:c}}\">";   // Not XSS equivalent
+  AssertCorrectModifiers(TC_XML, text, "VAL:c:xml_escape\n");
+  text = "{user={{USER:j}}";   // Correct escaping
+  AssertCorrectModifiers(TC_JSON, text, "USER:j\n");
+  text = "{user={{USER:o}}";   // json_escape is XSS equivalent
+  AssertCorrectModifiers(TC_JSON, text, "USER:o\n");
+  text = "{user={{USER:h}}";   // but html_escape is not
+  AssertCorrectModifiers(TC_JSON, text, "USER:h:j\n");
+
+  // 2i: Variables with XssSafe Custom modifiers are untouched.
+  ASSERT(GOOGLE_NAMESPACE::AddXssSafeModifier("x-test-cm",
+                                       &GOOGLE_NAMESPACE::html_escape));
+  text = "Hello {{USER:x-test-cm}}";              // Missing :h
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm\n");
+  text = "Hello {{USER:x-test-cm:j}}";            // Extra :j
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:j\n");
+  text = "Hello {{USER:x-test-cm:x-foo}}";        // Non-safe modifier
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:x-foo\n");
+  text = "Hello {{USER:x-foo:x-test-cm}}";        // Non-safe modifier
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-foo:x-test-cm\n");
+  text = "Hello {{USER:x-test-cm:none}}";         // Complete due to :none
+  AssertCorrectModifiers(TC_HTML, text, "USER:x-test-cm:none\n");
+  text = "Hello {{USER:h:x-test-cm}}";            // Prior escaping
+  AssertCorrectModifiers(TC_HTML, text, "USER:h:x-test-cm\n");
+
+  // 3. Larger test with close to every escaping case.
+
+  text = "<html><head>\n"
+      "<style>\n"
+      "@import url(\"{{CSS_URL:U=css}}\");\n"
+      "color:{{COLOR}}</style></head><body>\n"
+      "<h1>{{TITLE}}</h1>\n"
+      "<img src=\"{{IMG_URL}}\">\n"
+      "<form action=\"/search\">\n"
+      "  <input name=\"hl\" value={{HL}}>\n"
+      "  <input name=\"m\" value=\"{{FORM_MSG}}\">\n"
+      "</form>\n"
+      "<div style=\"background:{{BG_COLOR}}\">\n"
+      "</div>\n"
+      "<script>\n"
+      "  var msg_text = '{{MSG_TEXT}}';\n"
+      "</script>\n"
+      "<a href=\"url\" onmouseover=\"'{{MOUSE}}'\">bla</a>\n"
+      "Goodbye friend {{USER}}!\n</body></html>\n";
+  expected_out = "CSS_URL:U=css\n"
+      "COLOR:c\n"
+      "TITLE:h\n"
+      "IMG_URL:U=html\n"
+      "HL:H=attribute\n"
+      "FORM_MSG:h\n"
+      "BG_COLOR:c\n"
+      "MSG_TEXT:j\n"
+      "MOUSE:j\n"   // :j also escapes html entities
+      "USER:h\n";
+  AssertCorrectModifiers(TC_HTML, text, expected_out);
+}
+
+// More "end-to-end" test to ensure that variables are
+// escaped as expected with auto-escape mode enabled.
+// Obviously there is a lot more we can test.
+TEST(Template, VariableWithAutoEscape) {
+  string text, expected_out;
+  TemplateDictionary dict("dict");
+  string good_url("http://www.google.com/");
+  string bad_url("javascript:alert();");
+
+  text = "hi {{VAR}} lo";
+  dict.SetValue("VAR", "<bad>yo");
+  AssertCorrectEscaping(TC_HTML, dict, text, "hi &lt;bad&gt;yo lo");
+
+  text = "<a href=\"{{URL}}\">bla</a>";
+  dict.SetValue("URL", good_url);
+  expected_out = "<a href=\"" + good_url + "\">bla</a>";
+  AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
+  dict.SetValue("URL", bad_url);
+  expected_out = "<a href=\"#\">bla</a>";
+  AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
+
+  text = "<br style=\"display:{{DISPLAY}}\">";
+  dict.SetValue("DISPLAY", "none");
+  expected_out = "<br style=\"display:none\">";
+  AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
+  // Bad characters are simply removed in CleanseCss.
+  dict.SetValue("URL", "!#none_ ");
+  expected_out = "<br style=\"display:none\">";
+  AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
+
+  text = "<a href=\"url\" onkeyup=\"'{{EVENT}}'\">";
+  dict.SetValue("EVENT", "safe");
+  expected_out = "<a href=\"url\" onkeyup=\"'safe'\">";
+  AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
+  dict.SetValue("EVENT", "f = 'y';");
+  expected_out = "<a href=\"url\" onkeyup=\"'f \\x3d \\x27y\\x27;'\">";
+
+  // Check special handling of BI_SPACE and BI_NEWLINE.
+  text = "Hello\n{{BI_SPACE}}bla{{BI_NEWLINE}}foo.";
+  expected_out = "Hello bla\nfoo.";
+  AssertCorrectEscaping(TC_HTML, dict, text, expected_out);
+
+  // TC_CSS
+  text = "H1{margin-{{EDGE}}:0; text-align:{{BAD_EDGE}}}";
+  dict.SetValue("EDGE", "left");
+  dict.SetValue("BAD_EDGE", "$$center()!!");  // Bad chars are removed.
+  AssertCorrectEscaping(TC_CSS, dict, text,
+                        "H1{margin-left:0; text-align:center!!}");
+
+  // TC_XML and TC_JSON
+  text = "<Q>{{DATA}}</Q>";
+  dict.SetValue("DATA", "good-data");
+  AssertCorrectEscaping(TC_XML, dict, text, "<Q>good-data</Q>");
+  dict.SetValue("DATA", "<BAD>FOO</BAD>");
+  AssertCorrectEscaping(TC_XML, dict, text,
+                        "<Q>&lt;BAD&gt;FOO&lt;/BAD&gt;</Q>");
+  text = "{user = \"{{USER}}\"}";
+  dict.SetValue("USER", "good-user");
+  AssertCorrectEscaping(TC_JSON, dict, text, "{user = \"good-user\"}");
+  dict.SetValue("USER", "evil'<>\"");
+  AssertCorrectEscaping(TC_JSON, dict, text,
+                        "{user = \"evil\\x27\\x3c\\x3e\\x22\"}");
+}
+
+// Test that the template initialization fails in auto-escape
+// mode if the parser failed to parse.
+TEST(Template, FailedInitWithAutoEscape) {
+  Strip strip = STRIP_WHITESPACE;
+  // Taken from HTML Parser test suite.
+  string bad_html = "<a href='http://www.google.com' ''>\n";
+  ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
+
+  // Missing quotes around URL, not accepted in URL-taking attributes.
+  bad_html = "<a href={{URL}}>bla</a>";
+  ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
+
+  // Missing quotes around STYLE, not accepted in style-taking attributes.
+  bad_html = "<div style={{STYLE}}>";
+  ASSERT(NULL == StringToTemplateWithAutoEscaping(bad_html, strip, TC_HTML));
+}
+
+TEST(Template, AutoEscaping) {
+  Strip strip = STRIP_WHITESPACE;
+  Template *tpl;
+  string filename;
+  string text;
+  string user = "John<>Doe";
+  string user_esc = "John&lt;&gt;Doe";
+
+  // Positive test cases -- template initialization succeeds.
+  // We also check that modifiers that were missing or given incorrect
+  // have been updated as expected.
+  // TODO(jad): Cut-down redundancy by merging with
+  //            TestCorrectModifiersForAutoEscape.
+  text = "{{%AUTOESCAPE context=\"HTML\"}}"         // HTML
+      "{{USER:o}}<a href=\"{{URL}}\" class={{CLASS:h}}</a>";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  string expected_mods = "USER:o:h\nURL:U=html\nCLASS:h:H=attribute\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"HTML\" state=\"IN_TAG\"}}"  // HTML in tag
+      "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\"";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"HTML\" state=\"in_tag\"}}"  // lowercase ok
+      "href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\"";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  // Repeat the test with trailing HTML that closes the tag. This is
+  // undefined behavior. We test it to ensure the parser does not choke.
+  text += ">Hello</a><span>Some text</span></body></html>";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"JAVASCRIPT\"}}"   // JAVASCRIPT
+      "var a = {{A}}; var b = '{{B:h}}';";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "A:J=number\nB:h:j\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"CSS\"}}"          // CSS
+      "body {color:\"{{COLOR}}\"; font-size:{{SIZE:j}}";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "COLOR:c\nSIZE:j:c\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"JSON\"}}"         // JSON
+      "{ 'id': {{ID:j}}, 'value': {{VALUE:h}} }";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "ID:j\nVALUE:h:j\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"XML\"}}"          // XML
+      "<PARAM name=\"{{VAL}}\">{{DATA:h}}";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "VAL:xml_escape\nDATA:h\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{%AUTOESCAPE context=\"xml\"}}"          // lower-case XML
+      "<PARAM name=\"{{VAL}}\">{{DATA:h}}";
+  ASSERT(tpl = StringToTemplate(text, strip));
+  expected_mods = "VAL:xml_escape\nDATA:h\n";
+  AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
+
+  text = "{{!bla}}{{%AUTOESCAPE context=\"HTML\"}}";  // after comment
+  ASSERT(tpl = StringToTemplate(text, strip));
+  text = "{{%AUTOESCAPE context=\"HTML\" state=\"default\"}}";
+  ASSERT(tpl = StringToTemplate(text, strip));        // adding state
+
+  // Negative test cases - template initialization fails due to errors
+  // in the marker. Also checks that our parsing is defensive.
+  text = "{{%AUTOESCAPE}}";                                 // missing context
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPER context=\"HTML\"}}";               // invalid id
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%}}";                                           // missing id
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{% }}";                                          // missing id
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{% =}}";                                         // missing id
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE =\"HTML\"}}";                       // missing name
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE foo=\"HTML\"}}";                    // bogus name
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE =}}";                               // lone '='
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=HTML}}";                    // val not quoted
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"HTML}}";                  // no end quotes
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"\\\"HTML\"}}";            // Unescape val
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"\\\"HT\\\"\\\"ML\\\"\"}}";   // more complex
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"\"HTML\"}}";              // Unescape val
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"JAVASCRIPT\" bla}}";      // extra attr
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"JAVASCRIPT\"bla}}";       // invalid value
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"JAVASCRIPT\" foo=bla}}";  // extra attr/val
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE  context=\"HTML\"}}";               // extra whitesp
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context =\"HTML\"}}";               // extra whitesp
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context= \"HTML\"}}";               // extra whitesp
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"HTML\" }}";               // extra whitesp
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"Xml\"}}";                 // mixed-case xml
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"HTML\" state=\"tag\"}}";  // bad state
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{%AUTOESCAPE context=\"CSS\" state=\"IN_TAG\"}}";  // invalid state
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "Hello{{%AUTOESCAPE context=\"HTML\"}}";           // after text
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{USER}}{{%AUTOESCAPE context=\"HTML\"}}";        // after variable
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+  text = "{{#SEC}}{{%AUTOESCAPE context=\"HTML\"}}{{/SEC}}";  // not in MAIN
+  ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
+
+  string kAutoescapeHtmlPragma = "{{%AUTOESCAPE context=\"HTML\"}}";
+
+  // Check that Selective Auto-Escape does not auto-escape included templates
+  // unless these are also marked for auto-escape. To attest that,
+  // we check that when no escaping was given in the included template, none
+  // will be applied to it. USER will not get html-escaped.
+  text = kAutoescapeHtmlPragma + "{{>INC}}";
+  tpl = StringToTemplate(text, strip);
+  ASSERT(tpl);
+  string inc_text = "{{USER}}";    // missing :h escaping.
+  TemplateDictionary dict("dict");
+  TemplateDictionary *inc_dict = dict.AddIncludeDictionary("INC");
+  inc_dict->SetFilename(StringToTemplateFile(inc_text));
+  inc_dict->SetValue("USER", user);
+  AssertExpandIs(tpl, &dict, user, true);
+
+  // Add AUTOESCAPE pragma to included template and check that it works.
+  inc_text = kAutoescapeHtmlPragma + inc_text;
+  filename = StringToTemplateFile(inc_text);
+  inc_dict->SetFilename(filename);
+  AssertExpandIs(tpl, &dict, user_esc, true);
+
+  // Check that Selective Auto-Escape works with Template::StringToTemplate.
+  tpl = Template::StringToTemplate(inc_text, strip);
+  ASSERT(tpl);
+  TemplateDictionary dict2("dict2");
+  dict2.SetValue("USER", user);
+  AssertExpandIs(tpl, &dict2, user_esc, true);
+  delete tpl;
+
+  // Test that Selective AutoEscape follows included templates: Included
+  // templates 2 and 4 are registered for auto-escaping but not included
+  // templates 1 and 3. Check that only templates 2 and 4 get escaped.
+  text = "Parent: {{USER}}; {{>INCONE}}";
+  string text_inc1 = "INC1: {{USER1}}; {{>INCTWO}}";
+  string text_inc2 = kAutoescapeHtmlPragma + "INC2: {{USER2}}; {{>INCTHREE}}";
+  string text_inc3 = "INC3: {{USER3}}; {{>INCFOUR}}";
+  string text_inc4 = kAutoescapeHtmlPragma + "INC4: {{USER4}}";
+  dict.SetValue("USER", user);
+
+  TemplateDictionary *dict_inc1 = dict.AddIncludeDictionary("INCONE");
+  dict_inc1->SetFilename(StringToTemplateFile(text_inc1));
+  dict_inc1->SetValue("USER1", user);
+
+  TemplateDictionary *dict_inc2 = dict_inc1->AddIncludeDictionary("INCTWO");
+  filename = StringToTemplateFile(text_inc2);
+  dict_inc2->SetFilename(filename);
+  dict_inc2->SetValue("USER2", user);
+
+  TemplateDictionary *dict_inc3 = dict_inc2->AddIncludeDictionary("INCTHREE");
+  dict_inc3->SetFilename(StringToTemplateFile(text_inc3));
+  dict_inc3->SetValue("USER3", user);
+
+  TemplateDictionary *dict_inc4 = dict_inc3->AddIncludeDictionary("INCFOUR");
+  filename = StringToTemplateFile(text_inc4);
+  dict_inc4->SetFilename(filename);
+  dict_inc4->SetValue("USER4", user);
+
+  tpl = StringToTemplate(text, strip);
+  string expected_out = "Parent: " + user + "; INC1: " + user +
+      "; INC2: " + user_esc + "; INC3: " + user + "; INC4: " + user_esc;
+  AssertExpandIs(tpl, &dict, expected_out, true);
+
+  // Check that we do not modify template-includes.
+  // Here, xml_escape would have been changed to :h:xml_escape
+  // causing a double-escaping of the USER.
+  text = kAutoescapeHtmlPragma + "{{>INC:xml_escape}}";
+  inc_text = "{{USER}}";
+  tpl = StringToTemplate(text, strip);
+  ASSERT(tpl);
+  TemplateDictionary dict3("dict");
+  inc_dict = dict3.AddIncludeDictionary("INC");
+  inc_dict->SetFilename(StringToTemplateFile(inc_text));
+  inc_dict->SetValue("USER", user);
+  AssertExpandIs(tpl, &dict3, user_esc, true);
+
+  // Test that {{%...}} is a "removable" marker. A related test is
+  // also added to TestStrip().
+  tpl = StringToTemplate("{{%AUTOESCAPE context=\"HTML\"}}\nText\n Text",
+                         STRIP_BLANK_LINES);
+  AssertExpandIs(tpl, &dict, "Text\n Text", true);
+}
+
+TEST(Template, RegisterString) {
+  ASSERT(Template::StringToTemplateCache("file1", "Some text"));
+  Template* tpl = Template::GetTemplate("file1", STRIP_WHITESPACE);
+  ASSERT(tpl);
+  ASSERT(Template::GetTemplate("file1", STRIP_WHITESPACE) == tpl);
+
+  ASSERT(Template::StringToTemplateCache("file2", "Top {{>INC}}"));
+
+  TemplateDictionary dict("dict");
+  string expected = "Some text";
+  AssertExpandIs(tpl, &dict, expected, true);
+
+  TemplateDictionary* sub_dict = dict.AddIncludeDictionary("INC");
+  sub_dict->SetFilename("file1");
+  tpl = Template::GetTemplate("file2", STRIP_WHITESPACE);
+  expected = "Top Some text";
+  AssertExpandIs(tpl, &dict, expected, true);
+}
+
+// This tests that StaticTemplateString is sufficiently initialized at
+// static-initialization time (as opposed to dynamic-initialization
+// time, which comes later), that we can safely expand templates
+// during dynamic initialization.  This is worth testing, because some
+// parts of a StaticTemplateString -- especially the hash value, *do*
+// get computed later at dynamic-initialization time, and we want to
+// make sure that things still work properly even if we access the
+// StaticTemplateString before that happens.
+extern const StaticTemplateString kLateDefine;
+class DynamicInitializationTemplateExpander {
+ public:
+  DynamicInitializationTemplateExpander() {
+    Template* tpl = Template::StringToTemplate("hi {{VAR}} lo",
+                                               STRIP_WHITESPACE);
+    TemplateDictionary dict("dict");
+    dict.SetValue("VAR", TemplateString("short-lived", strlen("short")));
+    AssertExpandIs(tpl, &dict, "hi short lo", true);
+    dict.SetValue("VAR", kHello);
+    AssertExpandIs(tpl, &dict, "hi Hello lo", true);
+    dict.SetValue("VAR", kLateDefine);
+    AssertExpandIs(tpl, &dict, "hi laterz lo", true);
+    delete tpl;
+  }
+};
+DynamicInitializationTemplateExpander sts_tester;  // this runs before main()
+const StaticTemplateString kLateDefine = STS_INIT(kLateDefine, "laterz");
+
+int main(int argc, char** argv) {
+
+  CreateOrCleanTestDirAndSetAsTmpdir(FLAGS_test_tmpdir);
+
+  // This goes first so that future tests don't mess up the filenames.
+  // So we make it a normal function rather than using TEST() on it.
+  TestAnnotation();
+  return RUN_ALL_TESTS();
+}
diff --git a/src/tests/template_unittest_test_footer.in b/src/tests/template_unittest_test_footer.in
new file mode 100644
index 0000000..c81be51
--- /dev/null
+++ b/src/tests/template_unittest_test_footer.in
@@ -0,0 +1,49 @@
+<center><p><hr class=z>
+
+{{#TRIM_LINE}}
+<table width=100% border=0 cellpadding=0 cellspacing=0>
+ <tr>
+  <td bgcolor={{TRIM_LINE_COLOR}} colspan=2{{TRIM_LINE_HEIGHT}}>{{CLEARDOT}}</td>
+ </tr>
+</table>
+{{/TRIM_LINE}}
+    <table width=100% cellpadding=2 cellspacing=0 border=0>
+        <tr>
+        <td align=center{{FOOTER_BAR_ATTRIBUTES}}><font size=-1>
+        {{#FOOTER_BAR_TEXT}}
+        {{HOME_LINK}} -{{BI_SPACE}}
+        {{ADVERTISE_LINK}} -{{BI_SPACE}}
+        {{#PROMO_LICENSING_SECTION}}
+          {{PROMO_LICENSING_LINK}} -{{BI_SPACE}}
+        {{/PROMO_LICENSING_SECTION}}
+        {{ABOUT_GOOGLE_LINK}}
+        {{/FOOTER_BAR_TEXT}}
+        {{#EMPTY_FOOTER_BAR_TEXT}}&nbsp;
+        {{/EMPTY_FOOTER_BAR_TEXT}}
+        </font>
+    </table>
+    <br>
+    <font size=-1 class=p>
+    {{MODIFIED_BY_GOOGLE}}{{MSG_copyright}}
+    </font>
+    {{>ODP_ATTRIBUTION}}
+</center>
+
+{{#CLOSING_DIV_SECTION}}
+  </div>
+{{/CLOSING_DIV_SECTION}}
+
+{{>GOOGLE_COMPLETE_JS}}
+{{>SITE_SPEED_SCRIPT_FOOTER}}
+{{>AD_WIDE_WRAP_JAVASCRIPT}}
+{{>BROWSER_STATS_INCLUDE}}
+{{#LATENCY_PREFETCH}}
+  <link rel=prefetch href="{{LATENCY_PREFETCH_URL}}">
+{{/LATENCY_PREFETCH}}
+
+{{#JAVASCRIPT_FOOTER_SECTION}}
+  <script><!--{{BI_NEWLINE}}
+  {{>FAST_NEXT_JAVASCRIPT}}
+  //-->{{BI_NEWLINE}}
+  </script>
+{{/JAVASCRIPT_FOOTER_SECTION}}
diff --git a/src/tests/template_unittest_test_footer_dict01.out b/src/tests/template_unittest_test_footer_dict01.out
new file mode 100644
index 0000000..9bd3bf3
--- /dev/null
+++ b/src/tests/template_unittest_test_footer_dict01.out
@@ -0,0 +1,10 @@
+<center><p><hr class=z><table width=100% cellpadding=2 cellspacing=0 border=0><tr><td align=center><font size=-1>&lt;b&gt;Time to go home!&lt;/b&gt; - <b>Be advertiser #2</b> - <A HREF='foo'> - <A HREF=/>About Google!</A></font></table><br><font size=-1 class=p>2005&copy; Google Inc. (all rights reserved)</font></center></div><link rel=prefetch href="/latency"><script><!--
+<html><head></head><body></body></html>
+<html><head>head</head><body></body></html>
+//-->
+</script><script><!--
+<html><head>include-head</head><body>&lt;b&gt;&lt;A HREF=/&gt;&lt;/b&gt;: 0.3333</body></html><html><head><script>
+<!--
+</script>
+</head><html><a id="aw" onMouseOver="return ss('')"><b></b></a></html></body>//-->
+</script>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_footer_dict02.out b/src/tests/template_unittest_test_footer_dict02.out
new file mode 100644
index 0000000..dcdb111
--- /dev/null
+++ b/src/tests/template_unittest_test_footer_dict02.out
@@ -0,0 +1 @@
+<center><p><hr class=z><table width=100% cellpadding=2 cellspacing=0 border=0><tr><td align=center><font size=-1></font></table><br><font size=-1 class=p></font></center>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_html.in b/src/tests/template_unittest_test_html.in
new file mode 100644
index 0000000..5d8ad35
--- /dev/null
+++ b/src/tests/template_unittest_test_html.in
@@ -0,0 +1,65 @@
+<html>
+<head>
+<script>{{BI_NEWLINE}}
+  <!--{{BI_NEWLINE}}
+
+  {{! Include the JS code to do query tracking. }}
+  {{! javascript_query_tracking_post*.tpl }}
+  {{>JAVASCRIPT_QUERY_TRACKING_FUNCTION}}
+
+  {{! netscape requires "window.status"; IE allows just "status" here }}
+  {{! must return true when mousing over a link; not necessary when over }}
+  {{! the table cell in general }}
+
+  {{! The "id" parameter here refers to the value of the id attribute     }}
+  {{! of the anchor element for the ad (of the form "aw[POS]").         }}
+  {{! This is used by some spam-protection JavaScript to modify         }}
+  {{! parameters in the URL of the link.                        }}
+
+  {{#NO_MOUSEOVER_FUNCTIONS}}
+  function ss(w,id){
+    window.status=w;
+    return true;
+  }{{BI_NEWLINE}}
+  {{/NO_MOUSEOVER_FUNCTIONS}}
+
+  {{#MOUSEOVER_FUNCTIONS}}
+    {{! If any ads are mouseover ads, this is used to import
+      increment_mouseover_js.tpl, which redefines function ss to count
+      mouseovers.  Otherwise it produces no output. }}
+    {{>MOUSEOVER_JAVASCRIPT}}
+    {{! Since JSCompiler renames all functions/variables not beginning with an
+      underscore, we use _ss as our compiled function name,
+      then set ss to _ss }}
+    ss = _ss;
+  {{/MOUSEOVER_FUNCTIONS}}
+</script>{{BI_NEWLINE}}
+</head>
+
+<html>
+
+<a id="aw" onMouseOver="return ss('{{GOTO_MESSAGE}}')"{{TARGET}}>
+<b>{{TAG_LINE}}</b>
+</a>
+
+{{#UPDATE_SECTION}}Last updated: {{UPDATE}}<br>{{/UPDATE_SECTION}}
+
+{{#RESULTS}}
+<table cellspacing=0 cellpadding=0{{TABLE_WIDTH}} align={{ALIGNMENT}}{{BI_SPACE}}
+     {{#WHITE_BG}}bgColor=#ffffff {{/WHITE_BG}}border=0>
+  <tr>
+    <td>
+      <ol>
+        <li> Result: {{RESULT}}
+        <li> Goodness of result: {{GOODNESS}}
+        <li> xml-safe result: {{XML_RESULT}}
+      </ol>
+    </td>
+  </tr>
+</table>
+{{/RESULTS}}
+
+{{>FOOTER}}
+
+</html>
+</body>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_html_dict01.anno_out b/src/tests/template_unittest_test_html_dict01.anno_out
new file mode 100644
index 0000000..40012d9
--- /dev/null
+++ b/src/tests/template_unittest_test_html_dict01.anno_out
@@ -0,0 +1,66 @@
+{{#FILE=template_unittest_test_html.in}}{{#SEC=__{{MAIN}}__}}<html>
+<head>
+<script>{{#VAR=BI_NEWLINE}}
+{{/VAR}}
+  <!--{{#VAR=BI_NEWLINE}}
+{{/VAR}}
+{{#SEC=MOUSEOVER_FUNCTIONS}}    
+{{#INC=MOUSEOVER_JAVASCRIPT}}{{MISSING_FILE=not_a_template}}{{/INC}}    
+    ss = _ss;
+{{/SEC}}</script>{{#VAR=BI_NEWLINE}}
+{{/VAR}}
+</head>
+<html>
+<a id="aw" onMouseOver="return ss('{{#VAR=GOTO_MESSAGE}}print \x22Go home\x22{{/VAR}}')"{{#VAR=TARGET}}{{/VAR}}>
+<b>{{#VAR=TAG_LINE}}{{/VAR}}</b>
+</a>
+{{#SEC=UPDATE_SECTION}}Last updated: {{#VAR=UPDATE}}monday &amp; tuesday{{/VAR}}<br>{{/SEC}}
+{{#SEC=RESULTS}}<table cellspacing=0 cellpadding=0{{#VAR=TABLE_WIDTH}}{{/VAR}} align={{#VAR=ALIGNMENT}}"right"{{/VAR}}{{#VAR=BI_SPACE}} {{/VAR}}
+     {{#SEC=WHITE_BG}}bgColor=#ffffff {{/SEC}}border=0>
+  <tr>
+    <td>
+      <ol>
+        <li> Result: {{#VAR=RESULT}}<&>"result" #0'&'{{/VAR}}
+        <li> Goodness of result: {{#VAR=GOODNESS}}5{{/VAR}}
+        <li> xml-safe result: {{#VAR=XML_RESULT}}&lt;&amp;&gt;&quot;result&quot; #0&#39;&amp;&#39;{{/VAR}}
+      </ol>
+    </td>
+  </tr>
+</table>
+{{/SEC}}{{#SEC=RESULTS}}<table cellspacing=0 cellpadding=0{{#VAR=TABLE_WIDTH}}{{/VAR}} align={{#VAR=ALIGNMENT}}"right"{{/VAR}}{{#VAR=BI_SPACE}} {{/VAR}}
+     border=0>
+  <tr>
+    <td>
+      <ol>
+        <li> Result: {{#VAR=RESULT}}<&>"result" #1'&'{{/VAR}}
+        <li> Goodness of result: {{#VAR=GOODNESS}}6{{/VAR}}
+        <li> xml-safe result: {{#VAR=XML_RESULT}}&lt;&amp;&gt;&quot;result&quot; #1&#39;&amp;&#39;{{/VAR}}
+      </ol>
+    </td>
+  </tr>
+</table>
+{{/SEC}}{{#SEC=RESULTS}}<table cellspacing=0 cellpadding=0{{#VAR=TABLE_WIDTH}}{{/VAR}} align={{#VAR=ALIGNMENT}}"right"{{/VAR}}{{#VAR=BI_SPACE}} {{/VAR}}
+     {{#SEC=WHITE_BG}}bgColor=#ffffff {{/SEC}}border=0>
+  <tr>
+    <td>
+      <ol>
+        <li> Result: {{#VAR=RESULT}}<&>"result" #2'&'{{/VAR}}
+        <li> Goodness of result: {{#VAR=GOODNESS}}7{{/VAR}}
+        <li> xml-safe result: {{#VAR=XML_RESULT}}&lt;&amp;&gt;&quot;result&quot; #2&#39;&amp;&#39;{{/VAR}}
+      </ol>
+    </td>
+  </tr>
+</table>
+{{/SEC}}{{#INC=FOOTER}}{{#FILE=template_unittest_test_footer.in}}{{#SEC=__{{MAIN}}__}}<center><p><hr class=z>
+    <table width=100% cellpadding=2 cellspacing=0 border=0>
+        <tr>
+        <td align=center{{#VAR=FOOTER_BAR_ATTRIBUTES}}{{/VAR}}><font size=-1>
+                </font>
+    </table>
+    <br>
+    <font size=-1 class=p>
+    {{#VAR=MODIFIED_BY_GOOGLE}}{{/VAR}}{{#VAR=MSG_copyright}}{{/VAR}}
+    </font>
+</center>
+{{/SEC}}{{/FILE}}{{/INC}}</html>
+</body>{{/SEC}}{{/FILE}}
diff --git a/src/tests/template_unittest_test_html_dict01.out b/src/tests/template_unittest_test_html_dict01.out
new file mode 100644
index 0000000..1abe7b7
--- /dev/null
+++ b/src/tests/template_unittest_test_html_dict01.out
@@ -0,0 +1,4 @@
+<html><head><script>
+<!--
+ss = _ss;</script>
+</head><html><a id="aw" onMouseOver="return ss('print \x22Go home\x22')"><b></b></a>Last updated: monday &amp; tuesday<br><table cellspacing=0 cellpadding=0 align="right" bgColor=#ffffff border=0><tr><td><ol><li> Result: <&>"result" #0'&'<li> Goodness of result: 5<li> xml-safe result: &lt;&amp;&gt;&quot;result&quot; #0&#39;&amp;&#39;</ol></td></tr></table><table cellspacing=0 cellpadding=0 align="right" border=0><tr><td><ol><li> Result: <&>"result" #1'&'<li> Goodness of result: 6<li> xml-safe result: &lt;&amp;&gt;&quot;result&quot; #1&#39;&amp;&#39;</ol></td></tr></table><table cellspacing=0 cellpadding=0 align="right" bgColor=#ffffff border=0><tr><td><ol><li> Result: <&>"result" #2'&'<li> Goodness of result: 7<li> xml-safe result: &lt;&amp;&gt;&quot;result&quot; #2&#39;&amp;&#39;</ol></td></tr></table><center><p><hr class=z><table width=100% cellpadding=2 cellspacing=0 border=0><tr><td align=center><font size=-1></font></table><br><font size=-1 class=p></font></center></html></body>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_html_dict02.out b/src/tests/template_unittest_test_html_dict02.out
new file mode 100644
index 0000000..2fed87b
--- /dev/null
+++ b/src/tests/template_unittest_test_html_dict02.out
@@ -0,0 +1,4 @@
+<html><head><script>
+<!--
+</script>
+</head><html><a id="aw" onMouseOver="return ss('')"><b></b></a></html></body>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_invalid1.in b/src/tests/template_unittest_test_invalid1.in
new file mode 100644
index 0000000..cf0e80a
--- /dev/null
+++ b/src/tests/template_unittest_test_invalid1.in
@@ -0,0 +1,3 @@
+This is html
+{This is fine}
+{{This is not so fine}}   {{! Can't have spaces in a variable name!}}
diff --git a/src/tests/template_unittest_test_invalid2.in b/src/tests/template_unittest_test_invalid2.in
new file mode 100644
index 0000000..48383a6
--- /dev/null
+++ b/src/tests/template_unittest_test_invalid2.in
@@ -0,0 +1,7 @@
+This is html.
+
+{{#SECTION}}
+Happy section
+{{/SEC}}
+
+But the section never ends!
diff --git a/src/tests/template_unittest_test_markerdelim.in b/src/tests/template_unittest_test_markerdelim.in
new file mode 100644
index 0000000..e3762e6
--- /dev/null
+++ b/src/tests/template_unittest_test_markerdelim.in
@@ -0,0 +1,67 @@
+{{! Use <<< and >>> to delimit template stuff, rather than {{ and }}
+{{=<<< >>>=}}
+<html>
+<head>
+<script><<<BI_NEWLINE>>>
+  <!--<<<BI_NEWLINE>>>
+
+  <<<! Include the JS code to do query tracking. >>>
+  <<<! javascript_query_tracking_post*.tpl >>>
+  <<<>JAVASCRIPT_QUERY_TRACKING_FUNCTION>>>
+
+  <<<! netscape requires "window.status"; IE allows just "status" here >>>
+  <<<! must return true when mousing over a link; not necessary when over >>>
+  <<<! the table cell in general >>>
+
+  <<<! The "id" parameter here refers to the value of the id attribute     >>>
+  <<<! of the anchor element for the ad (of the form "aw[POS]").         >>>
+  <<<! This is used by some spam-protection JavaScript to modify         >>>
+  <<<! parameters in the URL of the link.                        >>>
+
+  <<<#NO_MOUSEOVER_FUNCTIONS>>>
+  function ss(w,id){
+    window.status=w;
+    return true;
+  }<<<BI_NEWLINE>>>
+  <<</NO_MOUSEOVER_FUNCTIONS>>>
+
+  <<<#MOUSEOVER_FUNCTIONS>>>
+    <<<! If any ads are mouseover ads, this is used to import
+      increment_mouseover_js.tpl, which redefines function ss to count
+      mouseovers.  Otherwise it produces no output. >>>
+    <<<>MOUSEOVER_JAVASCRIPT>>>
+    <<<! Since JSCompiler renames all functions/variables not beginning with an
+      underscore, we use _ss as our compiled function name,
+      then set ss to _ss >>>
+    ss = _ss;
+  <<</MOUSEOVER_FUNCTIONS>>>
+</script><<<BI_NEWLINE>>>
+</head>
+
+<html>
+
+<a id="aw" onMouseOver="return ss('<<<GOTO_MESSAGE>>>')"<<<TARGET>>>>
+<b><<<TAG_LINE>>></b>
+</a>
+
+<<<#UPDATE_SECTION>>>Last updated: <<<UPDATE>>><br><<</UPDATE_SECTION>>>
+
+<<<#RESULTS>>>
+<table cellspacing=0 cellpadding=0<<<TABLE_WIDTH>>> align=<<<ALIGNMENT>>><<<BI_SPACE>>>
+     <<<#WHITE_BG>>>bgColor=#ffffff <<</WHITE_BG>>>border=0>
+  <tr>
+    <td>
+      <ol>
+        <li> Result: <<<RESULT>>>
+        <li> Goodness of result: <<<GOODNESS>>>
+        <li> xml-safe result: <<<XML_RESULT>>>
+      </ol>
+    </td>
+  </tr>
+</table>
+<<</RESULTS>>>
+
+<<<>FOOTER>>>
+
+</html>
+</body>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_markerdelim_dict01.out b/src/tests/template_unittest_test_markerdelim_dict01.out
new file mode 100644
index 0000000..1abe7b7
--- /dev/null
+++ b/src/tests/template_unittest_test_markerdelim_dict01.out
@@ -0,0 +1,4 @@
+<html><head><script>
+<!--
+ss = _ss;</script>
+</head><html><a id="aw" onMouseOver="return ss('print \x22Go home\x22')"><b></b></a>Last updated: monday &amp; tuesday<br><table cellspacing=0 cellpadding=0 align="right" bgColor=#ffffff border=0><tr><td><ol><li> Result: <&>"result" #0'&'<li> Goodness of result: 5<li> xml-safe result: &lt;&amp;&gt;&quot;result&quot; #0&#39;&amp;&#39;</ol></td></tr></table><table cellspacing=0 cellpadding=0 align="right" border=0><tr><td><ol><li> Result: <&>"result" #1'&'<li> Goodness of result: 6<li> xml-safe result: &lt;&amp;&gt;&quot;result&quot; #1&#39;&amp;&#39;</ol></td></tr></table><table cellspacing=0 cellpadding=0 align="right" bgColor=#ffffff border=0><tr><td><ol><li> Result: <&>"result" #2'&'<li> Goodness of result: 7<li> xml-safe result: &lt;&amp;&gt;&quot;result&quot; #2&#39;&amp;&#39;</ol></td></tr></table><center><p><hr class=z><table width=100% cellpadding=2 cellspacing=0 border=0><tr><td align=center><font size=-1></font></table><br><font size=-1 class=p></font></center></html></body>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_markerdelim_dict02.out b/src/tests/template_unittest_test_markerdelim_dict02.out
new file mode 100644
index 0000000..2fed87b
--- /dev/null
+++ b/src/tests/template_unittest_test_markerdelim_dict02.out
@@ -0,0 +1,4 @@
+<html><head><script>
+<!--
+</script>
+</head><html><a id="aw" onMouseOver="return ss('')"><b></b></a></html></body>
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_modifiers.in b/src/tests/template_unittest_test_modifiers.in
new file mode 100644
index 0000000..b70e93f
--- /dev/null
+++ b/src/tests/template_unittest_test_modifiers.in
@@ -0,0 +1,15 @@
+<html>
+<body>
+{{#UPDATE_SECTION}}
+  {{UPDATE}}
+  {{UPDATE:h}}
+  {{UPDATE:javascript_escape}}
+  {{UPDATE:h:u}}
+{{/UPDATE_SECTION}}
+{{! There should be no problem with this comment having a : in it. }}
+  <IMG src=foo.jpg align={{ALIGNMENT}}>
+  <IMG src="mouseover() {img=\'foo.jpg\' align={{ALIGNMENT:j}}}">
+
+{{>SIMPLE:html_escape}}
+</body>
+</html>{{BI_NEWLINE}}
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_modifiers_dict01.anno_out b/src/tests/template_unittest_test_modifiers_dict01.anno_out
new file mode 100644
index 0000000..e14adb6
--- /dev/null
+++ b/src/tests/template_unittest_test_modifiers_dict01.anno_out
@@ -0,0 +1,11 @@
+{{#FILE=template_unittest_test_modifiers.in}}{{#SEC=__{{MAIN}}__}}<html>
+<body>
+{{#SEC=UPDATE_SECTION}}  {{#VAR=UPDATE}}monday &amp; tuesday{{/VAR}}
+  {{#VAR=UPDATE:html_escape}}monday &amp;amp; tuesday{{/VAR}}
+  {{#VAR=UPDATE:javascript_escape}}monday \x26amp; tuesday{{/VAR}}
+  {{#VAR=UPDATE:html_escape:url_query_escape}}monday+%26amp%3Bamp%3B+tuesday{{/VAR}}
+{{/SEC}}  <IMG src=foo.jpg align={{#VAR=ALIGNMENT}}"right"{{/VAR}}>
+  <IMG src="mouseover() {img=\'foo.jpg\' align={{#VAR=ALIGNMENT:javascript_escape}}\x22right\x22{{/VAR}}}">
+{{#INC=SIMPLE:html_escape}}{{#FILE=template_unittest_test_simple.in}}{{#SEC=__{{MAIN}}__}}&lt;html&gt; &lt;head&gt;   {{#VAR=HEAD}}{{/VAR}} &lt;/head&gt; &lt;body&gt;   {{#VAR=BODY}}{{/VAR}} &lt;/body&gt; &lt;/html&gt;{{#VAR=BI_NEWLINE}} {{/VAR}}{{/SEC}}{{/FILE}}{{/INC}}</body>
+</html>{{#VAR=BI_NEWLINE}}
+{{/VAR}}{{/SEC}}{{/FILE}}
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_modifiers_dict01.out b/src/tests/template_unittest_test_modifiers_dict01.out
new file mode 100644
index 0000000..ea3cbad
--- /dev/null
+++ b/src/tests/template_unittest_test_modifiers_dict01.out
@@ -0,0 +1 @@
+<html><body>monday &amp; tuesdaymonday &amp;amp; tuesdaymonday \x26amp; tuesdaymonday+%26amp%3Bamp%3B+tuesday<IMG src=foo.jpg align="right"><IMG src="mouseover() {img=\'foo.jpg\' align=\x22right\x22}">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt; </body></html>
diff --git a/src/tests/template_unittest_test_nul.in b/src/tests/template_unittest_test_nul.in
new file mode 100644
index 0000000..82a536b
--- /dev/null
+++ b/src/tests/template_unittest_test_nul.in
Binary files differ
diff --git a/src/tests/template_unittest_test_nul_dict01.out b/src/tests/template_unittest_test_nul_dict01.out
new file mode 100644
index 0000000..90e9c58
--- /dev/null
+++ b/src/tests/template_unittest_test_nul_dict01.out
Binary files differ
diff --git a/src/tests/template_unittest_test_selective_css.in b/src/tests/template_unittest_test_selective_css.in
new file mode 100644
index 0000000..96e8246
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_css.in
@@ -0,0 +1,15 @@
+{{%AUTOESCAPE context="CSS"}}
+
+P.abstract {
+  margin-{{AE_START_EDGE}}:0;
+  text-align:{{AE_END_EDGE}};
+  font-size:{{AE_FONT_SIZE_PC}};
+}
+.italic {font-style:{{AE_ITALIC}}}
+
+H1 {
+  font-size:{{AE_FONT_SIZE_PT}};
+  color:{{AE_MAUVE_RGB}};
+}
+
+BODY {background:transparent url('{{AE_URL_GOOD}}');}
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_css_dict01.out b/src/tests/template_unittest_test_selective_css_dict01.out
new file mode 100644
index 0000000..1abf6f8
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_css_dict01.out
@@ -0,0 +1 @@
+P.abstract {margin-left:0;text-align:center;font-size:120%;}.italic {font-style:italic}H1 {font-size:12pt;color:#FF7BD5;}BODY {background:transparent url('httpwww.google.com');}
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_css_dict02.out b/src/tests/template_unittest_test_selective_css_dict02.out
new file mode 100644
index 0000000..f86da6f
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_css_dict02.out
@@ -0,0 +1 @@
+P.abstract {margin-:0;text-align:;font-size:;}.italic {font-style:}H1 {font-size:;color:;}BODY {background:transparent url('');}
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_html.in b/src/tests/template_unittest_test_selective_html.in
new file mode 100644
index 0000000..e005fbc
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_html.in
@@ -0,0 +1,15 @@
+{{%AUTOESCAPE context="HTML"}}
+{{!Is a copy of template_unittest_test_autoescape_simple.in}}
+<h1>{{AE_TITLE_GOOD}}</h1>
+<h1>{{AE_TITLE_BAD}}</h1>
+<img src="{{AE_URL_GOOD}}">
+<img src="{{AE_URL_BAD}}">
+<div style="background:{{AE_BG_COLOR_GOOD}}">
+<div style="background:{{AE_BG_COLOR_BAD}}">
+</div>
+<script>
+  var msg_text = '{{AE_JS_GOOD}}';
+  var msg_text = '{{AE_JS_BAD}}';
+</script>
+Goodbye {{AE_USERNAME_GOOD}}!
+Goodbye {{AE_USERNAME_BAD}}!
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_html_dict01.out b/src/tests/template_unittest_test_selective_html_dict01.out
new file mode 100644
index 0000000..4bec106
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_html_dict01.out
@@ -0,0 +1 @@
+<h1>Hello World!</h1><h1>Hello &lt;script&gt;alert(1)&lt;/script&gt; World!</h1><img src="http://www.google.com/"><img src="#"><div style="background:red"><div style="background:evil! "></div><script>var msg_text = 'your text here';var msg_text = 'your text\x27is clever\x27thanks';</script>Goodbye Mr. Nice!Goodbye Doctor&lt;script&gt;alert(2)&lt;/script&gt;Evil!
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_html_dict02.out b/src/tests/template_unittest_test_selective_html_dict02.out
new file mode 100644
index 0000000..2aa9b17
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_html_dict02.out
@@ -0,0 +1 @@
+<h1></h1><h1></h1><img src=""><img src=""><div style="background:"><div style="background:"></div><script>var msg_text = '';var msg_text = '';</script>Goodbye !Goodbye !
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_js.in b/src/tests/template_unittest_test_selective_js.in
new file mode 100644
index 0000000..de511db
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_js.in
@@ -0,0 +1,7 @@
+{{%AUTOESCAPE context="JAVASCRIPT"}}
+  var msg_text1 = '{{AE_JS_GOOD}}';
+  var msg_text2 = '{{AE_JS_BAD}}';
+{{!Below variable is not quoted}}
+  var msg_text3 = {{AE_JS_BAD}};
+{{!Below variable ends up with :h:j}}
+  var msg_text4 = '{{AE_JS_BAD:h}}';
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_js_dict01.out b/src/tests/template_unittest_test_selective_js_dict01.out
new file mode 100644
index 0000000..75fce98
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_js_dict01.out
@@ -0,0 +1 @@
+var msg_text1 = 'your text here';var msg_text2 = 'your text\x27is clever\x27thanks';var msg_text3 = null;var msg_text4 = 'your text\x26#39;is clever\x26#39;thanks';
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_selective_js_dict02.out b/src/tests/template_unittest_test_selective_js_dict02.out
new file mode 100644
index 0000000..440a14b
--- /dev/null
+++ b/src/tests/template_unittest_test_selective_js_dict02.out
@@ -0,0 +1 @@
+var msg_text1 = '';var msg_text2 = '';var msg_text3 = ;var msg_text4 = '';
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_simple.in b/src/tests/template_unittest_test_simple.in
new file mode 100644
index 0000000..386fb63
--- /dev/null
+++ b/src/tests/template_unittest_test_simple.in
@@ -0,0 +1,8 @@
+<html>
+<head>
+  {{HEAD}}
+</head>
+<body>
+  {{BODY}}
+</body>
+</html>{{BI_NEWLINE}}
\ No newline at end of file
diff --git a/src/tests/template_unittest_test_simple_dict01.out b/src/tests/template_unittest_test_simple_dict01.out
new file mode 100644
index 0000000..e91f0cc
--- /dev/null
+++ b/src/tests/template_unittest_test_simple_dict01.out
@@ -0,0 +1 @@
+<html><head>   This is the head   </head><body></body></html>
diff --git a/src/tests/template_unittest_test_simple_dict02.out b/src/tests/template_unittest_test_simple_dict02.out
new file mode 100644
index 0000000..30e84fd
--- /dev/null
+++ b/src/tests/template_unittest_test_simple_dict02.out
@@ -0,0 +1 @@
+<html><head></head><body></body></html>
diff --git a/src/tests/template_unittest_test_simple_dict03.out b/src/tests/template_unittest_test_simple_dict03.out
new file mode 100644
index 0000000..5106739
--- /dev/null
+++ b/src/tests/template_unittest_test_simple_dict03.out
@@ -0,0 +1,2 @@
+<html><head>   </head><body>

+</body></html>
diff --git a/src/tests/template_unittest_test_valid1.in b/src/tests/template_unittest_test_valid1.in
new file mode 100644
index 0000000..57f72b3
--- /dev/null
+++ b/src/tests/template_unittest_test_valid1.in
@@ -0,0 +1,3 @@
+This is ok.
+{This is also ok.}
+Look ma!, no template substitutions at all!
diff --git a/src/tests/template_unittest_test_valid1_dict01.out b/src/tests/template_unittest_test_valid1_dict01.out
new file mode 100644
index 0000000..c8e3d30
--- /dev/null
+++ b/src/tests/template_unittest_test_valid1_dict01.out
@@ -0,0 +1 @@
+This is ok.{This is also ok.}Look ma!, no template substitutions at all!
\ No newline at end of file