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(¬hing_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,
+ ¬hing_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 "ed);
+
+ // Validates the parser in javascript state against the provided boolean.
+ void ValidateInJavascript(const string "ed);
+
+ // Validate the current parser javascript quoted state against the provided
+ // boolean.
+ void ValidateJavascriptQuoted(const string "ed);
+
+ // 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 "ed);
+
+ // 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('<?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>▼</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 »</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%> </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> <a href=/advanced_search?hl=en>Advanced Search</a><br> <a href=/preferences?hl=en>Preferences</a><br> <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 Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/about.html">About Google</a></font><p><font size=-2>©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('<?state state=value, tag=a, attr=onclick, attr_type=js,
+in_js=true, js_quoted=true?> x') &; &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"">
+
+
+
+ <?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 & 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",
+ "<A HREF='foo' id="bar && "
+ "baz">"));
+ 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 <a & b> #0.3333"));
+ EXPECT_TRUE(peer.ValueIs("PRE",
+ "if (a < 1 && b > 2)\n\t x = 0.3333;"));
+ EXPECT_TRUE(peer.ValueIs("URL", "pageviews-r%3Fegex"));
+
+ EXPECT_TRUE(peer.ValueIs("XML", "This&isjust& -- 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(©);
+ 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 & bar");
+ EXPECT_STREQ(peer.GetSectionValue("hardest HTML"),
+ "<A HREF='foo' id="bar && "
+ "baz">");
+}
+
+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> & b<wbr>­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> & b<wbr>­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> & b<wbr>­ar</b>");
+ EXPECT_STREQ(peer.GetSectionValue("invalid snippet"),
+ "<b><A HREF='foo' id="bar &&{ "
+ "baz"></b>");
+ EXPECT_STREQ(peer.GetSectionValue("snippet with italics"),
+ "<i>foo<br> & b<wbr>­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><b>foo</b></i>");
+ EXPECT_STREQ(peer.GetSectionValue("unclosed"),
+ "<b><i>foo</i></b>");
+ EXPECT_STREQ(peer.GetSectionValue("unterminated 1"), "foo<");
+ EXPECT_STREQ(peer.GetSectionValue("unterminated 2"), "foo<b");
+ EXPECT_STREQ(peer.GetSectionValue("unterminated 3"), "foo</");
+ EXPECT_STREQ(peer.GetSectionValue("unterminated 4"), "foo</b");
+ EXPECT_STREQ(peer.GetSectionValue("unterminated 5"), "<b>foo</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</em>");
+ 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"),
+ "<span dir=foo>bidi text</span>");
+ EXPECT_STREQ(peer.GetSectionValue("span no dir"),
+ "<span>bidi text</span>");
+ EXPECT_STREQ(peer.GetSectionValue("span bad attribute"),
+ "<span onclick=alert('foo')>bidi text</span>");
+ EXPECT_STREQ(peer.GetSectionValue("span quotes"),
+ "<span dir="rtl">bidi text</span>");
+ 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 <span dir=rtl>bidi text"
+ "</span></span>");
+ 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 & bar");
+ EXPECT_STREQ(peer.GetSectionValue("hardest PRE"),
+ " "--\v--\f--\n--\t--&--<-->--'--"");
+}
+
+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"),
+ "<>&'"");
+ EXPECT_STREQ(peer.GetSectionValue("harder XML-2"),
+ "Hello<script>alert('&')</script>");
+ EXPECT_STREQ(peer.GetSectionValue("hardest XML"),
+ "<<b>>&!''""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&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&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&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&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> </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", "© 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", §ion_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", §ion_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&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&yo lo", true);
+
+ tpl = StringToTemplate("hi {{VAR:h:h}} lo", STRIP_WHITESPACE);
+ AssertExpandIs(tpl, &dict, "hi yo&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&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 & print file bar\n", true);
+ dict.AddIncludeDictionary("INC")->SetFilename(incname2);
+ AssertExpandIs(tpl1, &dict, "hi include & print file inc2 bar\n",
+ true);
+ AssertExpandIs(tpl2, &dict, "hi include \\x26 print file\\ninc2\\n bar\n",
+ true);
+ AssertExpandIs(tpl3, &dict, "hi include & 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 & print file inc2 yo&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 <bad>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><BAD>FOO</BAD></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<>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}}
+ {{/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><b>Time to go home!</b> - <b>Be advertiser #2</b> - <A HREF='foo'> - <A HREF=/>About Google!</A></font></table><br><font size=-1 class=p>2005© 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><b><A HREF=/></b>: 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 & 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}}<&>"result" #0'&'{{/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}}<&>"result" #1'&'{{/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}}<&>"result" #2'&'{{/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 & 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: <&>"result" #0'&'</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: <&>"result" #1'&'</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: <&>"result" #2'&'</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 & 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: <&>"result" #0'&'</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: <&>"result" #1'&'</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: <&>"result" #2'&'</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 & tuesday{{/VAR}}
+ {{#VAR=UPDATE:html_escape}}monday &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}}__}}<html> <head> {{#VAR=HEAD}}{{/VAR}} </head> <body> {{#VAR=BODY}}{{/VAR}} </body> </html>{{#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 & tuesdaymonday &amp; tuesdaymonday \x26amp; tuesdaymonday+%26amp%3Bamp%3B+tuesday<IMG src=foo.jpg align="right"><IMG src="mouseover() {img=\'foo.jpg\' align=\x22right\x22}"><html><head></head><body></body></html> </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 <script>alert(1)</script> 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<script>alert(2)</script>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