#ifndef PUGIXML_NO_XPATH

#include "test.hpp"

#include <string.h>
#include <wchar.h>

#include <string>
#include <vector>
#include <algorithm>
#include <limits>

using namespace pugi;

static void load_document_copy(xml_document& doc, const char_t* text)
{
	xml_document source;
	CHECK(source.load_string(text));

	doc.append_copy(source.first_child());
}

template <typename T>
static void random_shuffle(std::vector<T>& v)
{
	size_t rng = 2147483647;

	for (size_t i = v.size() - 1; i > 0; --i)
	{
		// Fisher-Yates shuffle
		size_t j = rng % (i + 1);
		std::swap(v[j], v[i]);

		// LCG RNG, constants from Numerical Recipes
		rng = rng * 1664525 + 1013904223;
	}
}

TEST(xpath_allocator_many_pages)
{
	std::basic_string<char_t> query = STR("0");

	for (int i = 0; i < 128; ++i) query += STR("+string-length('abcdefgh')");

	CHECK_XPATH_NUMBER(xml_node(), query.c_str(), 1024);
}

TEST(xpath_allocator_large_page)
{
	std::basic_string<char_t> query;

	for (int i = 0; i < 1024; ++i) query += STR("abcdefgh");

	CHECK_XPATH_NUMBER(xml_node(), (STR("string-length('") + query + STR("')")).c_str(), 8192);
}

TEST_XML(xpath_sort_complex, "<node><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2></node>")
{
	// just some random union order, it should not matter probably?
	xpath_node_set ns = doc.child(STR("node")).select_nodes(STR("child1 | child2 | child1/@* | . | child2/@* | child2/text()"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 2 % 3 % 4 % 5 % 6 % 7 % 8;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 8 % 7 % 6 % 5 % 4 % 3 % 2;
}

TEST(xpath_sort_complex_copy) // copy the document so that document order optimization does not work
{
	xml_document doc;
	load_document_copy(doc, STR("<node><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2></node>"));

	// just some random union order, it should not matter probably?
	xpath_node_set ns = doc.child(STR("node")).select_nodes(STR("child1 | child2 | child1/@* | . | child2/@* | child2/text()"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 2 % 3 % 4 % 5 % 6 % 7 % 8;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 8 % 7 % 6 % 5 % 4 % 3 % 2;
}

TEST_XML(xpath_sort_children, "<node><child><subchild id='1'/></child><child><subchild id='2'/></child></node>")
{
	xpath_node_set ns = doc.child(STR("node")).select_nodes(STR("child/subchild[@id=1] | child/subchild[@id=2]"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 4 % 7;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 7 % 4;
}

TEST(xpath_sort_children_copy) // copy the document so that document order optimization does not work
{
	xml_document doc;
	load_document_copy(doc, STR("<node><child><subchild id='1'/></child><child><subchild id='2'/></child></node>"));

	xpath_node_set ns = doc.child(STR("node")).select_nodes(STR("child/subchild[@id=1] | child/subchild[@id=2]"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 4 % 7;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 7 % 4;
}

TEST_XML(xpath_sort_attributes, "<node/>")
{
	xml_node n = doc.child(STR("node"));

	// we need to insert attributes manually since unsorted node sets are (always?) sorted via pointers because of remove_duplicates,
	// so we need to have different document and pointer order to cover all comparator cases
	n.append_attribute(STR("attr2"));
	n.append_attribute(STR("attr3"));
	n.insert_attribute_before(STR("attr1"), n.attribute(STR("attr2")));

	xpath_node_set ns = n.select_nodes(STR("@* | @*"));

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	ns.sort(false);
	xpath_node_set sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 3 % 4 % 5;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 5 % 4 % 3;
}

TEST_XML(xpath_sort_attributes_docorder, "<node attr1='' attr2='value' attr4='value' />")
{
	xml_node n = doc.child(STR("node"));

	n.first_attribute().set_name(STR("attribute1"));
	n.insert_attribute_after(STR("attr3"), n.attribute(STR("attr2")));

	xpath_node_set ns = n.select_nodes(STR("@* | @*"));

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	ns.sort(false);
	xpath_node_set sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 3 % 4 % 5 % 6;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 6 % 5 % 4 % 3;
}

TEST(xpath_sort_random_medium)
{
	xml_document doc;
	load_document_copy(doc, STR("<node>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("</node>"));

	xpath_node_set ns = doc.select_nodes(STR("//node() | //@*"));

	std::vector<xpath_node> nsv(ns.begin(), ns.end());
	random_shuffle(nsv);

	xpath_node_set copy(&nsv[0], &nsv[0] + nsv.size());
	copy.sort();

	xpath_node_set_tester tester(copy, "sorted order failed");

	for (unsigned int i = 2; i < 39; ++i) tester % i;
}

TEST(xpath_sort_random_large)
{
	xml_document doc;
	load_document_copy(doc, STR("<node>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
		STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2></node>"));

	xpath_node_set ns = doc.select_nodes(STR("//node() | //@*"));

	std::vector<xpath_node> nsv(ns.begin(), ns.end());
	random_shuffle(nsv);

	xpath_node_set copy(&nsv[0], &nsv[0] + nsv.size());
	copy.sort();

	xpath_node_set_tester tester(copy, "sorted order failed");

	for (unsigned int i = 2; i < 129; ++i) tester % i;
}

TEST(xpath_long_numbers_parse)
{
	const char_t* str_flt_max = STR("340282346638528860000000000000000000000");
	const char_t* str_flt_max_dec = STR("340282346638528860000000000000000000000.000000");

	const char_t* str_dbl_max = STR("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
	const char_t* str_dbl_max_dec = STR("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000");

	xml_node c;

	// check parsing
	CHECK_XPATH_NUMBER(c, str_flt_max, double(std::numeric_limits<float>::max()));
	CHECK_XPATH_NUMBER(c, str_flt_max_dec, double(std::numeric_limits<float>::max()));
	CHECK_XPATH_NUMBER(c, str_dbl_max, std::numeric_limits<double>::max());
	CHECK_XPATH_NUMBER(c, str_dbl_max_dec, std::numeric_limits<double>::max());
}

static bool test_xpath_string_prefix(const xml_node& node, const char_t* query, const char_t* expected, size_t match_length)
{
	xpath_query q(query);

	char_t result[32];
	size_t size = q.evaluate_string(result, sizeof(result) / sizeof(result[0]), node);

	size_t expected_length = std::char_traits<char_t>::length(expected);

	return size == expected_length + 1 && std::char_traits<char_t>::compare(result, expected, match_length) == 0;
}

TEST(xpath_long_numbers_stringize)
{
	const char_t* str_flt_max = STR("340282346638528860000000000000000000000");
	const char_t* str_flt_max_dec = STR("340282346638528860000000000000000000000.000000");

	const char_t* str_dbl_max = STR("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
	const char_t* str_dbl_max_dec = STR("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000");

	xml_node c;

	CHECK(test_xpath_string_prefix(c, str_flt_max, str_flt_max, 15));
	CHECK(test_xpath_string_prefix(c, str_flt_max_dec, str_flt_max, 15));

	CHECK(test_xpath_string_prefix(c, str_dbl_max, str_dbl_max, 15));
	CHECK(test_xpath_string_prefix(c, str_dbl_max_dec, str_dbl_max, 15));
}

TEST(xpath_denorm_numbers)
{
	std::basic_string<char_t> query;

	// 10^-318 - double denormal
	for (int i = 0; i < 106; ++i)
	{
		if (i != 0) query += STR(" * ");
		query += STR("0.001");
	}

	// check if current fpu setup supports denormals
	double denorm = xpath_query(query.c_str()).evaluate_number(xml_node());

	if (denorm != 0.0)
	{
		CHECK_XPATH_STRING(xml_node(), query.c_str(), STR("0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009999987484955998"));
	}
}

TEST_XML(xpath_rexml_1, "<a><b><c id='a'/></b><c id='b'/></a>")
{
	CHECK_XPATH_NODESET(doc, STR("//*[local-name()='c' and @id='b']")) % 6;
	CHECK_XPATH_NODESET(doc, STR("//*[ local-name()='c' and @id='b' ]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c[@id]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c[(@id)]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c[ @id ]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c[ (@id) ]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c[( @id )]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c[ ( @id ) ]")) % 6;
	CHECK_XPATH_NODESET(doc, STR("/a/c [ ( @id ) ] ")) % 6;
	CHECK_XPATH_NODESET(doc, STR(" / a / c [ ( @id ) ] ")) % 6;
}

TEST_XML(xpath_rexml_2, "<a:x xmlns:a='1'><a:y p='p' q='q'><a:z>zzz</a:z></a:y></a:x>")
{
	CHECK_XPATH_NODESET(doc, STR("a:x/a:y[@p='p' and @q='q']/a:z/text()")) % 8;
}

TEST_XML(xpath_rexml_3, "<article><section role='subdivision' id='1'><para>free flowing text.</para></section><section role='division'><section role='subdivision' id='2'><para>free flowing text.</para></section><section role='division'><para>free flowing text.</para></section></section></article>")
{
	CHECK_XPATH_NODESET(doc, STR("//section[../self::section[@role=\"division\"]]")) % 10 % 15;
    CHECK_XPATH_NODESET(doc, STR("//section[@role=\"subdivision\" and not(../self::section[@role=\"division\"])]")) % 3;
	CHECK_XPATH_NODESET(doc, STR("//section[@role=\"subdivision\"][not(../self::section[@role=\"division\"])]")) % 3;
}

TEST_XML_FLAGS(xpath_rexml_4, "<a><b number='1' str='abc'>TEXT1</b><c number='1'/><c number='2' str='def'><b number='3'/><d number='1' str='abc'>TEXT2</d><b number='2'><!--COMMENT--></b></c></a>", parse_default | parse_comments)
{
	CHECK_XPATH_NODESET(doc, STR("/descendant-or-self::node()[count(child::node()|following-sibling::node()|preceding-sibling::node())=0]")) % 6 % 17 % 20;
}

TEST_XML(xpath_rexml_5, "<a><b><c id='a'/></b><c id='b'/></a>")
{
	CHECK_XPATH_FAIL(STR(".//[@id]"));
	CHECK_XPATH_NODESET(doc, STR(".//self::*[@id]")) % 4 % 6;
	CHECK_XPATH_NODESET(doc, STR(".//node()[@id]")) % 4 % 6;
}

TEST_XML(xpath_rexml_6, "<div><span><strong>a</strong></span><em>b</em></div>")
{
	CHECK_XPATH_NODESET(doc, STR("//em|//strong")) % 4 % 6;
	CHECK_XPATH_NODESET(doc, STR("//*[self::em | self::strong]")) % 4 % 6;
	CHECK_XPATH_NODESET(doc, STR("//*[name()=\"em\" or name()=\"strong\"]")) % 4 % 6;
	CHECK_XPATH_NODESET(doc, STR("//*[self::em or self::strong]")) % 4 % 6;
}

TEST_XML(xpath_xsl_list_1, "<input><type>whatever</type></input><input><type>text</type></input><input><type>select</type></input><input><type>something</type></input>")
{
	// if I'm not last, and the next input/type isn't select
	CHECK_XPATH_NODESET(doc, STR("input[type[parent::input/following-sibling::input[1]/type != 'select']]")) % 2 % 8;
	CHECK_XPATH_NODESET(doc, STR("input[type[../following-sibling::input[1]/type != 'select']]")) % 2 % 8;

	CHECK_XPATH_NODESET(doc, STR("input[position()+1]"));
}

TEST_XML(xpath_xsl_list_2, "<TR><TD id='1'>text1</TD><TD id='2'>text2</TD><TD id='3'>text3</TD><TD id='4'>text4</TD></TR>")
{
	CHECK_XPATH_FAIL(STR(".[not(.=ancestor::TR/TD[15]/node())]"));

	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("1")).first_child(), STR("self::node()[not(.=ancestor::TR/TD[3]/node())]")) % 5;
	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("2")).first_child(), STR("self::node()[not(.=ancestor::TR/TD[3]/node())]")) % 8;
	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("3")).first_child(), STR("self::node()[not(.=ancestor::TR/TD[3]/node())]"));
	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("4")).first_child(), STR("self::node()[not(.=ancestor::TR/TD[3]/node())]")) % 14;

	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("1")), STR("node()[not(.=ancestor::TR/TD[3]/node())]")) % 5;
	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("2")), STR("node()[not(.=ancestor::TR/TD[3]/node())]")) % 8;
	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("3")), STR("node()[not(.=ancestor::TR/TD[3]/node())]"));
	CHECK_XPATH_NODESET(doc.child(STR("TR")).find_child_by_attribute(STR("TD"), STR("id"), STR("4")), STR("node()[not(.=ancestor::TR/TD[3]/node())]")) % 14;
}

TEST_XML(xpath_star_token, "<node>0.5<section><child/><child/><child/><child/></section><section/></node>")
{
	CHECK_XPATH_NODESET(doc, STR("//*[/* * 4]")) % 6 % 9;
	CHECK_XPATH_NODESET(doc, STR("//*[/**4]")) % 6 % 9;
	CHECK_XPATH_FAIL(STR("//*[/***4]"));
}

TEST(xpath_miscellaneous)
{
	CHECK_XPATH_FAIL(STR("/root/child[a=3]/substring(child::text())"));
	CHECK_XPATH_NODESET(xml_node(), STR("foo/@FOO/@bar"));
}

TEST_XML(xpath_context_node, "<node>5</node>")
{
	CHECK_XPATH_NODESET(doc, STR("node")) % 2;
	CHECK_XPATH_BOOLEAN(doc, STR("node"), true);
	CHECK_XPATH_NUMBER(doc, STR("node"), 5);
	CHECK_XPATH_STRING(doc, STR("node"), STR("5"));
}

TEST_XML(xpath_context_position, "<node>5</node>")
{
	CHECK_XPATH_NODESET(doc, STR("id(position() + last())"));
	CHECK_XPATH_BOOLEAN(doc, STR("position() + last() = 2"), true);
	CHECK_XPATH_NUMBER(doc, STR("position() + last()"), 2);
	CHECK_XPATH_STRING(doc, STR("position() + last()"), STR("2"));
}

TEST(xpath_lexer_unknown_lexeme)
{
	CHECK_XPATH_FAIL(STR("(^3))"));
	CHECK_XPATH_FAIL(STR("(!3))"));
}

TEST(xpath_large_node_set)
{
	xml_document doc;
	CHECK(doc.load_file("tests/data/large.xml"));

	xpath_node_set ns = doc.select_nodes(STR("//*"));

	CHECK(ns.size() == 10001);
}

TEST(xpath_out_of_memory_query)
{
	test_runner::_memory_fail_threshold = 1;

	CHECK_ALLOC_FAIL(xpath_query q(STR("node")));
}

TEST_XML(xpath_out_of_memory_evaluate, "<n/>")
{
	test_runner::_memory_fail_threshold = 4196 * sizeof(char_t) + 4096 * 2 + 32768;

	std::basic_string<char_t> query = STR("*[concat(\"a\", \"");

	query.resize(4196, 'a');
	query += STR("\")]");

	xpath_query q(query.c_str());

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_boolean(doc) == false));
	CHECK_ALLOC_FAIL(CHECK_DOUBLE_NAN(q.evaluate_number(doc)));

#ifndef PUGIXML_NO_STL
	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(doc).empty()));
#endif

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, doc) == 1));
	CHECK_ALLOC_FAIL(CHECK(q.evaluate_node(doc) == xpath_node()));
	CHECK_ALLOC_FAIL(CHECK(q.evaluate_node_set(doc).empty()));
}

TEST(xpath_out_of_memory_evaluate_concat)
{
	test_runner::_memory_fail_threshold = 4196 * sizeof(char_t) + 4096 * 2;

	std::basic_string<char_t> query = STR("concat(\"a\", \"");

	query.resize(4196, 'a');
	query += STR("\")");

	xpath_query q(query.c_str());

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, xml_node()) == 1));
}

TEST(xpath_out_of_memory_evaluate_concat_list)
{
	std::basic_string<char_t> query = STR("concat(");

	for (size_t i = 0; i < 500; ++i)
		query += STR("\"\",");

	query += STR("\"\")");

	xpath_query q(query.c_str());

	test_runner::_memory_fail_threshold = 1;

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, xml_node()) == 1));
}

TEST(xpath_out_of_memory_evaluate_substring)
{
	test_runner::_memory_fail_threshold = 4196 * sizeof(char_t) + 4096 * 2;

	std::basic_string<char_t> query = STR("substring(\"");

	query.resize(4196, 'a');
	query += STR("\", 1, 4097)");

	xpath_query q(query.c_str());

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, xml_node()) == 1));
}

TEST_XML(xpath_out_of_memory_evaluate_union, "<node />")
{
	// left hand side: size * sizeof(xpath_node) (8 on 32-bit, 16 on 64-bit)
	// right hand side: same
	// to make sure that when we append right hand side to left hand side, we run out of an XPath stack page (4K), we need slightly more than 2K/8 = 256 nodes on 32-bit, 128 nodes on 64-bit
	size_t count = sizeof(void*) == 4 ? 300 : 150;

	for (size_t i = 0; i < count; ++i)
		doc.first_child().append_child(STR("a"));

	xpath_query q(STR("a|a"));

	test_runner::_memory_fail_threshold = 1;

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_node_set(doc.child(STR("node"))).empty()));
}

TEST_XML(xpath_out_of_memory_evaluate_union_hash, "<node />")
{
	// left hand side: size * sizeof(xpath_node) (8 on 32-bit, 16 on 64-bit)
	// right hand side: same
	// hash table: size * 1.5 * sizeof(void*)
	// to make sure that when we append right hand side to left hand side, we do *not* run out of an XPath stack page (4K), we need slightly less than 2K/8 = 256 nodes on 32-bit, 128 nodes on 64-bit
	size_t count = sizeof(void*) == 4 ? 200 : 100;

	for (size_t i = 0; i < count; ++i)
		doc.first_child().append_child(STR("a"));

	xpath_query q(STR("a|a"));

	test_runner::_memory_fail_threshold = 1;

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_node_set(doc.child(STR("node"))).empty()));
}


TEST_XML(xpath_out_of_memory_evaluate_predicate, "<node><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/><a/></node>")
{
	test_runner::_memory_fail_threshold = 32768 + 4096 * 2;

	xpath_query q(STR("//a[//a[//a[//a[true()]]]]"));

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_node_set(doc).empty()));
}

TEST_XML(xpath_out_of_memory_evaluate_normalize_space_0, "<node> a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z </node>")
{
	test_runner::_memory_fail_threshold = 32768 + 4096 * 2;

	xpath_query q(STR("concat(normalize-space(), normalize-space(), normalize-space(), normalize-space(), normalize-space(), normalize-space(), normalize-space(), normalize-space())"));

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, doc.first_child()) == 1));
}

TEST_XML(xpath_out_of_memory_evaluate_normalize_space_1, "<node> a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z </node>")
{
	test_runner::_memory_fail_threshold = 32768 + 4096 * 2;

	xpath_query q(STR("concat(normalize-space(node), normalize-space(node), normalize-space(node), normalize-space(node), normalize-space(node), normalize-space(node), normalize-space(node), normalize-space(node))"));

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, doc) == 1));
}

TEST_XML(xpath_out_of_memory_evaluate_translate, "<node> a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z </node>")
{
	test_runner::_memory_fail_threshold = 32768 + 4096 * 2;

	xpath_query q(STR("concat(translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'), translate(node, 'a', '\xe9'))"));

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, doc) == 1));
}

TEST_XML(xpath_out_of_memory_evaluate_translate_table, "<node> a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z </node>")
{
	test_runner::_memory_fail_threshold = 32768 + 4096 * 2;

	xpath_query q(STR("concat(translate(node, 'a', 'A'), translate(node, 'a', 'A'), translate(node, 'a', 'A'), translate(node, 'a', 'A'), translate(node, 'a', 'A'), translate(node, 'a', 'A'), translate(node, 'a', 'A'), translate(node, 'a', 'A'))"));

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, doc) == 1));
}

TEST(xpath_out_of_memory_evaluate_string_append)
{
	test_runner::_memory_fail_threshold = 32768 + 4096 * 2;

	std::basic_string<char_t> literal(5000, 'a');

	std::basic_string<char_t> buf;
	buf += STR("<n><c>text</c><c>");
	buf += literal;
	buf += STR("</c></n>");

	xml_document doc;
	CHECK(doc.load_buffer_inplace(&buf[0], buf.size() * sizeof(char_t)));

	xpath_query q(STR("string(n)"));
	CHECK(q);

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, doc) == 1));
}

TEST(xpath_out_of_memory_evaluate_number_to_string)
{
	test_runner::_memory_fail_threshold = 4096 + 128;

	xpath_variable_set vars;
	vars.set(STR("x"), 1e+308);

	xpath_query q(STR("concat($x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x, $x)"), &vars);

	CHECK_ALLOC_FAIL(CHECK(q.evaluate_string(0, 0, xml_node()) == 1));
}

TEST(xpath_memory_concat_massive)
{
	xml_document doc;
	xml_node node = doc.append_child(STR("node"));

	for (int i = 0; i < 5000; ++i)
		node.append_child(STR("c")).text().set(i % 10);

	xpath_query q(STR("/"));
	size_t size = q.evaluate_string(0, 0, node);

	CHECK(size == 5001);
}

TEST_XML(xpath_memory_translate_table, "<node>a</node>")
{
	test_runner::_memory_fail_threshold = 32768 + 4096 + 128;

	// 128b per table => we need 32+ translate calls to exhaust a page
	std::basic_string<char_t> query = STR("concat(");
	for (int i = 0; i < 64; ++i)
		query += STR("translate(.,'a','A'),");
	query += STR("'')");

	CHECK_ALLOC_FAIL(CHECK(!xpath_query(query.c_str())));
}

TEST_XML(xpath_sort_copy_share, "<node><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2></node>")
{
	// copy sharing shares the name/value data for nodes that can potentially make document order optimization invalid (silently)
	xml_node node = doc.child(STR("node"));
	xml_node child1 = node.child(STR("child1"));
	xml_node child2 = node.child(STR("child2"));

	// swap child1 & child2
	node.prepend_copy(child2);
	node.append_copy(child1);

	node.remove_child(child1);
	node.remove_child(child2);

	// just some random union order, it should not matter probably?
	xpath_node_set ns = doc.child(STR("node")).select_nodes(STR("child1 | child2 | child1/@* | . | child2/@* | child2/text()"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 2 % 3 % 4 % 5 % 6 % 7 % 8;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 8 % 7 % 6 % 5 % 4 % 3 % 2;
}

TEST_XML(xpath_sort_move_share, "<node><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2></node>")
{
	// moving changes the relation between name/value data and document order, this can potentially make document order optimization invalid (silently)
	xml_node node = doc.child(STR("node"));
	xml_node child1 = node.child(STR("child1"));
	xml_node child2 = node.child(STR("child2"));

	// swap child1 & child2
	node.prepend_move(child2);
	node.append_move(child1);

	// just some random union order, it should not matter probably?
	xpath_node_set ns = doc.child(STR("node")).select_nodes(STR("child1 | child2 | child1/@* | . | child2/@* | child2/text()"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 2 % 3 % 4 % 5 % 6 % 7 % 8;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 8 % 7 % 6 % 5 % 4 % 3 % 2;
}

TEST_XML(xpath_sort_append_buffer, "<node /><node />")
{
	// append_buffer changes the relation between name/value data and document order, this can potentially make document order optimization invalid (silently)
	const char* child1 = "<child1 attr1='value1' attr2='value2'/>";
	const char* child2 = "<child2 attr1='value1'>test   </child2>";

	doc.last_child().append_buffer(child2, strlen(child2));
	doc.first_child().append_buffer(child1, strlen(child1));

	// just some random union order, it should not matter probably?
	xpath_node_set ns = doc.select_nodes(STR("node/child1 | node/child2 | node/child1/@* | node/. | node/child2/@* | node/child2/text()"));

	ns.sort(false);
	xpath_node_set sorted = ns;

	ns.sort(true);
	xpath_node_set reverse_sorted = ns;

	xpath_node_set_tester(sorted, "sorted order failed") % 2 % 3 % 4 % 5 % 6 % 7 % 8 % 9;
	xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 9 % 8 % 7 % 6 % 5 % 4 % 3 % 2;
}

TEST(xpath_sort_crossdoc)
{
	xml_document doc1;
	CHECK(doc1.load_string(STR("<node />")));

	xml_document doc2;
	CHECK(doc2.load_string(STR("<node />")));

	xpath_node_set ns1 = doc1.select_nodes(STR("*"));
	CHECK(ns1.size() == 1);

	xpath_node_set ns2 = doc2.select_nodes(STR("*"));
	CHECK(ns2.size() == 1);

	xpath_variable_set set;
	set.set(STR("ns1"), ns1);
	set.set(STR("ns2"), ns2);

	xpath_node_set ns = xpath_query(STR("$ns1 | $ns2"), &set).evaluate_node_set(xpath_node());

	ns.sort();

	CHECK(ns.size() == 2);
	CHECK((ns[0] == ns1[0] && ns[1] == ns2[0]) || (ns[0] == ns2[0] && ns[1] == ns1[0]));
}

TEST(xpath_sort_crossdoc_dynamic)
{
	xml_document doc1;
	doc1.append_child(STR("node"));

	xml_document doc2;
	doc2.append_child(STR("node"));

	xpath_node_set ns1 = doc1.select_nodes(STR("*"));
	CHECK(ns1.size() == 1);

	xpath_node_set ns2 = doc2.select_nodes(STR("*"));
	CHECK(ns2.size() == 1);

	xpath_variable_set set;
	set.set(STR("ns1"), ns1);
	set.set(STR("ns2"), ns2);

	xpath_node_set ns = xpath_query(STR("$ns1 | $ns2"), &set).evaluate_node_set(xpath_node());

	ns.sort();

	CHECK(ns.size() == 2);
	CHECK((ns[0] == ns1[0] && ns[1] == ns2[0]) || (ns[0] == ns2[0] && ns[1] == ns1[0]));
}

TEST(xpath_sort_crossdoc_different_depth)
{
	xml_document doc1;
	doc1.append_child(STR("node"));

	xml_document doc2;
	doc2.append_child(STR("node")).append_child(STR("node"));

	xpath_node_set ns1 = doc1.select_nodes(STR("*"));
	CHECK(ns1.size() == 1);

	xpath_node_set ns2 = doc2.select_nodes(STR("*/*"));
	CHECK(ns2.size() == 1);

	xpath_variable_set set;
	set.set(STR("ns1"), ns1);
	set.set(STR("ns2"), ns2);

	xpath_node_set ns = xpath_query(STR("$ns1 | $ns2"), &set).evaluate_node_set(xpath_node());

	ns.sort();

	CHECK(ns.size() == 2);
	CHECK((ns[0] == ns1[0] && ns[1] == ns2[0]) || (ns[0] == ns2[0] && ns[1] == ns1[0]));
}

TEST_XML(xpath_sort_empty_node, "<node><child1/><child2/></node>")
{
	xml_node n = doc.child(STR("node"));
	xpath_node nodes[] = { n.child(STR("child2")), xml_node(), n.child(STR("child1")), xml_node() };
	xpath_node_set ns(nodes, nodes + sizeof(nodes) / sizeof(nodes[0]));

	ns.sort();

	CHECK(!ns[0] && !ns[1] && ns[2] == nodes[2] && ns[3] == nodes[0]);
}

TEST(xpath_allocate_string_out_of_memory)
{
	std::basic_string<char_t> query;

	for (int i = 0; i < 1024; ++i) query += STR("abcdefgh");

	test_runner::_memory_fail_threshold = 8*1024;

#ifndef __DMC__ // DigitalMars exception handling crashes instead of catching the exception...
	CHECK_ALLOC_FAIL(CHECK(!xpath_query(query.c_str())));
#endif
}

TEST(xpath_remove_duplicates)
{
	xml_document doc;

	for (int i = 0; i < 20; ++i)
	{
		doc.append_child(STR("node2"));
		doc.prepend_child(STR("node1"));
	}

	xpath_node_set ns = doc.select_nodes(STR("/node2/preceding::* | //node1 | /node() | /* | /node1/following-sibling::*"));

	ns.sort();

	{
		xpath_node_set_tester tester(ns, "sorted order failed");

		for (int i = 0; i < 40; ++i)
			tester % (2 + i);
	}
}

TEST(xpath_anonymous_nodes)
{
	xml_document doc;
	doc.append_child(node_element);
	doc.append_child(node_pi);

	CHECK_XPATH_NODESET(doc, STR("/name"));
	CHECK_XPATH_NODESET(doc, STR("/processing-instruction('a')"));
	CHECK_XPATH_NODESET(doc, STR("/ns:*"));
}
#endif
