aboutsummaryrefslogtreecommitdiffstats
path: root/document/src/tests
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /document/src/tests
Publish
Diffstat (limited to 'document/src/tests')
-rw-r--r--document/src/tests/.gitignore16
-rw-r--r--document/src/tests/CMakeLists.txt36
-rw-r--r--document/src/tests/annotation/.gitignore6
-rw-r--r--document/src/tests/annotation/CMakeLists.txt10
-rw-r--r--document/src/tests/annotation/annotation_test.cpp295
-rw-r--r--document/src/tests/arrayfieldvaluetest.cpp247
-rw-r--r--document/src/tests/arrayfieldvaluetest.h29
-rw-r--r--document/src/tests/base/.gitignore7
-rw-r--r--document/src/tests/base/CMakeLists.txt19
-rw-r--r--document/src/tests/base/documentid_benchmark.cpp21
-rw-r--r--document/src/tests/base/documentid_test.cpp228
-rw-r--r--document/src/tests/bucketselectortest.cpp102
-rw-r--r--document/src/tests/buckettest.cpp258
-rw-r--r--document/src/tests/cpp-globalidbucketids.txt38
-rw-r--r--document/src/tests/data/compressed.cfg139
-rw-r--r--document/src/tests/data/cppdocument.cfg131
-rw-r--r--document/src/tests/data/crossplatform-java-cpp-doctypes.cfg202
-rw-r--r--document/src/tests/data/crossplatform-java-cpp-document.cfg128
-rw-r--r--document/src/tests/data/defaultdoctypes.cfg150
-rw-r--r--document/src/tests/data/defaultdocument.cfg94
-rw-r--r--document/src/tests/data/docmancfg.txt170
-rw-r--r--document/src/tests/data/docselectcfg1.txt16
-rw-r--r--document/src/tests/data/docselectcfg2.txt35
-rw-r--r--document/src/tests/data/doctypesconfigtest.cfg184
-rw-r--r--document/src/tests/data/document-cpp-currentversion-lz4-9.datbin0 -> 332 bytes
-rw-r--r--document/src/tests/data/document-cpp-currentversion-uncompressed.datbin0 -> 332 bytes
-rw-r--r--document/src/tests/data/document-cpp-v7-uncompressed.datbin0 -> 354 bytes
-rw-r--r--document/src/tests/data/document-cpp-v8-uncompressed.datbin0 -> 346 bytes
-rw-r--r--document/src/tests/data/embeddeddocument.cfg102
-rw-r--r--document/src/tests/data/inheritancetest.cfg354
-rw-r--r--document/src/tests/data/serialize-fieldpathupdate-cpp.datbin0 -> 160 bytes
-rw-r--r--document/src/tests/data/serialize-fieldpathupdate-java.datbin0 -> 160 bytes
-rw-r--r--document/src/tests/data/serializejava-compressed.datbin0 -> 8337 bytes
-rw-r--r--document/src/tests/data/serializejava.datbin0 -> 398 bytes
-rw-r--r--document/src/tests/data/serializejavawithannotations.datbin0 -> 471 bytes
-rw-r--r--document/src/tests/data/serializeupdatecpp.datbin0 -> 201 bytes
-rw-r--r--document/src/tests/data/serializeupdatejava.datbin0 -> 112 bytes
-rw-r--r--document/src/tests/data/serializev6.datbin0 -> 391 bytes
-rw-r--r--document/src/tests/data/variablesizedocument.cfg34
-rw-r--r--document/src/tests/data/versionscfg.txt176
-rw-r--r--document/src/tests/datatype/.gitignore5
-rw-r--r--document/src/tests/datatype/CMakeLists.txt10
-rw-r--r--document/src/tests/datatype/datatype_test.cpp64
-rw-r--r--document/src/tests/documentcalculatortestcase.cpp202
-rw-r--r--document/src/tests/documentidtest.cpp183
-rw-r--r--document/src/tests/documentselectparsertest.cpp1278
-rw-r--r--document/src/tests/documentselecttest.h34
-rw-r--r--document/src/tests/documenttestcase.cpp1434
-rw-r--r--document/src/tests/documenttypetestcase.cpp254
-rw-r--r--document/src/tests/documentupdatetestcase.cpp1030
-rw-r--r--document/src/tests/documentupdatetestcase.h50
-rw-r--r--document/src/tests/fieldpathupdatetestcase.cpp1310
-rw-r--r--document/src/tests/fieldsettest.cpp348
-rw-r--r--document/src/tests/fieldtestcase.h20
-rw-r--r--document/src/tests/fieldvalue/.gitignore7
-rw-r--r--document/src/tests/fieldvalue/CMakeLists.txt28
-rw-r--r--document/src/tests/fieldvalue/document_test.cpp31
-rw-r--r--document/src/tests/fieldvalue/fieldvalue_test.cpp41
-rw-r--r--document/src/tests/fieldvalue/predicatefieldvalue_test.cpp70
-rw-r--r--document/src/tests/forcelinktest.cpp25
-rw-r--r--document/src/tests/gid_filter_test.cpp317
-rw-r--r--document/src/tests/globalidtest.cpp268
-rw-r--r--document/src/tests/heapdebugger.h68
-rw-r--r--document/src/tests/heapdebuggerlinux.cpp708
-rw-r--r--document/src/tests/heapdebuggerother.cpp44
-rw-r--r--document/src/tests/orderingselectortest.cpp98
-rw-r--r--document/src/tests/positiontypetest.cpp53
-rw-r--r--document/src/tests/predicate/.gitignore7
-rw-r--r--document/src/tests/predicate/CMakeLists.txt28
-rw-r--r--document/src/tests/predicate/predicate_builder_test.cpp81
-rw-r--r--document/src/tests/predicate/predicate_printer_test.cpp122
-rw-r--r--document/src/tests/predicate/predicate_test.cpp248
-rw-r--r--document/src/tests/primitivefieldvaluetest.cpp422
-rw-r--r--document/src/tests/repo/.gitignore5
-rw-r--r--document/src/tests/repo/CMakeLists.txt10
-rw-r--r--document/src/tests/repo/documenttyperepo_test.cpp519
-rw-r--r--document/src/tests/repo/documenttypes.cfg421
-rw-r--r--document/src/tests/serialization/.gitignore6
-rw-r--r--document/src/tests/serialization/CMakeLists.txt28
-rw-r--r--document/src/tests/serialization/annotation.serialize.test.cfg77
-rw-r--r--document/src/tests/serialization/annotation.serialize.test.repo.cfg130
-rw-r--r--document/src/tests/serialization/annotationserializer_test.cpp274
-rw-r--r--document/src/tests/serialization/compression_test.cpp30
-rw-r--r--document/src/tests/serialization/test_data_serialized_advancedbin0 -> 582 bytes
-rw-r--r--document/src/tests/serialization/test_data_serialized_simplebin0 -> 372 bytes
-rw-r--r--document/src/tests/serialization/vespadocumentserializer_test.cpp828
-rw-r--r--document/src/tests/setfieldvaluetest.h21
-rw-r--r--document/src/tests/stringtokenizertest.cpp109
-rw-r--r--document/src/tests/struct_anno/.gitignore2
-rw-r--r--document/src/tests/struct_anno/CMakeLists.txt10
-rw-r--r--document/src/tests/struct_anno/document.datbin0 -> 223 bytes
-rw-r--r--document/src/tests/struct_anno/documentmanager.cfg160
-rw-r--r--document/src/tests/struct_anno/documenttypes.cfg98
-rw-r--r--document/src/tests/struct_anno/struct_anno_test.cpp87
-rw-r--r--document/src/tests/structfieldvaluetest.cpp185
-rw-r--r--document/src/tests/tensor_fieldvalue/.gitignore1
-rw-r--r--document/src/tests/tensor_fieldvalue/CMakeLists.txt10
-rw-r--r--document/src/tests/tensor_fieldvalue/tensor_fieldvalue_test.cpp72
-rw-r--r--document/src/tests/testbytebuffer.cpp493
-rw-r--r--document/src/tests/testbytebuffer.h73
-rw-r--r--document/src/tests/testcase.h17
-rw-r--r--document/src/tests/testdocmantest.cpp62
-rw-r--r--document/src/tests/testrunner.cpp15
-rw-r--r--document/src/tests/teststringutil.cpp112
-rw-r--r--document/src/tests/teststringutil.h26
-rw-r--r--document/src/tests/testxml.cpp153
-rw-r--r--document/src/tests/urltypetest.cpp54
-rwxr-xr-xdocument/src/tests/vespaxml/fieldpathupdates.xml54
-rw-r--r--document/src/tests/vespaxml/test1.expected.xml5
-rw-r--r--document/src/tests/vespaxml/test1.xml9
-rw-r--r--document/src/tests/vespaxml/test10.xml9
-rw-r--r--document/src/tests/vespaxml/test11.xml8
-rw-r--r--document/src/tests/vespaxml/test12.xml9
-rw-r--r--document/src/tests/vespaxml/test13.xml7
-rw-r--r--document/src/tests/vespaxml/test14.xml8
-rw-r--r--document/src/tests/vespaxml/test15.xml8
-rw-r--r--document/src/tests/vespaxml/test16.xml8
-rw-r--r--document/src/tests/vespaxml/test17.xml9
-rw-r--r--document/src/tests/vespaxml/test18.xml9
-rw-r--r--document/src/tests/vespaxml/test2.expected.xml5
-rw-r--r--document/src/tests/vespaxml/test2.xml7
-rw-r--r--document/src/tests/vespaxml/test20.xml8
-rw-r--r--document/src/tests/vespaxml/test21.xml9
-rw-r--r--document/src/tests/vespaxml/test22.xml9
-rw-r--r--document/src/tests/vespaxml/test23.xml9
-rw-r--r--document/src/tests/vespaxml/test24.xml9
-rw-r--r--document/src/tests/vespaxml/test25.xml9
-rw-r--r--document/src/tests/vespaxml/test26.xml9
-rw-r--r--document/src/tests/vespaxml/test27.xml9
-rw-r--r--document/src/tests/vespaxml/test28.xml10
-rw-r--r--document/src/tests/vespaxml/test29.xml18
-rw-r--r--document/src/tests/vespaxml/test3.xml9
-rw-r--r--document/src/tests/vespaxml/test30.xml15
-rw-r--r--document/src/tests/vespaxml/test32.xml7
-rw-r--r--document/src/tests/vespaxml/test33.xml9
-rw-r--r--document/src/tests/vespaxml/test34.xml7
-rw-r--r--document/src/tests/vespaxml/test35.xml9
-rw-r--r--document/src/tests/vespaxml/test36.xml43
-rw-r--r--document/src/tests/vespaxml/test37.xml23
-rw-r--r--document/src/tests/vespaxml/test4.xml23
-rw-r--r--document/src/tests/vespaxml/test40.xml6
-rw-r--r--document/src/tests/vespaxml/test41.xml22
-rw-r--r--document/src/tests/vespaxml/test42.xml22
-rw-r--r--document/src/tests/vespaxml/test43.xml32
-rw-r--r--document/src/tests/vespaxml/test45.xml6
-rw-r--r--document/src/tests/vespaxml/test46.xml10
-rw-r--r--document/src/tests/vespaxml/test47.xml10
-rw-r--r--document/src/tests/vespaxml/test48.xml6
-rw-r--r--document/src/tests/vespaxml/test49.xml8
-rw-r--r--document/src/tests/vespaxml/test5.expected.xml3
-rw-r--r--document/src/tests/vespaxml/test5.xml6
-rw-r--r--document/src/tests/vespaxml/test50.xml8
-rw-r--r--document/src/tests/vespaxml/test51.xml6
-rw-r--r--document/src/tests/vespaxml/test52.xml10
-rw-r--r--document/src/tests/vespaxml/test53.xml11
-rw-r--r--document/src/tests/vespaxml/test54.xml10
-rw-r--r--document/src/tests/vespaxml/test55.xml11
-rw-r--r--document/src/tests/vespaxml/test56.xml8
-rw-r--r--document/src/tests/vespaxml/test57.xml8
-rw-r--r--document/src/tests/vespaxml/test58.xml15
-rw-r--r--document/src/tests/vespaxml/test59.xml7
-rw-r--r--document/src/tests/vespaxml/test6.xml7
-rw-r--r--document/src/tests/vespaxml/test7.xml9
-rw-r--r--document/src/tests/vespaxml/test8.xml9
-rw-r--r--document/src/tests/vespaxml/test9.xml7
-rw-r--r--document/src/tests/vespaxml/test_arraystruct.xml19
-rw-r--r--document/src/tests/vespaxml/test_doc5.xml8
-rw-r--r--document/src/tests/vespaxml/test_doc6.xml6
-rw-r--r--document/src/tests/vespaxml/test_doc8.xml9
-rw-r--r--document/src/tests/vespaxml/test_externalentity.xml11
-rw-r--r--document/src/tests/vespaxml/test_idprefix.xml13
-rw-r--r--document/src/tests/vespaxml/test_struct.xml9
-rw-r--r--document/src/tests/vespaxml/test_update1.xml12
-rw-r--r--document/src/tests/vespaxml/vespaxmldoctype.cfg133
-rw-r--r--document/src/tests/vespaxml/xxe.txt1
-rw-r--r--document/src/tests/weightedsetfieldvaluetest.cpp324
-rw-r--r--document/src/tests/weightedsetfieldvaluetest.h6
177 files changed, 17410 insertions, 0 deletions
diff --git a/document/src/tests/.gitignore b/document/src/tests/.gitignore
new file mode 100644
index 00000000000..3e5d3aea0af
--- /dev/null
+++ b/document/src/tests/.gitignore
@@ -0,0 +1,16 @@
+*.So
+*.core
+.*.swp
+.config.log
+.depend
+Makefile
+cpp-globalids.txt
+indexdir
+mmapbuffertestfile.txt
+mmapq_test
+mytestfile
+mytestfile_clear
+test.vlog
+testrunner
+*_test
+document_testrunner_app
diff --git a/document/src/tests/CMakeLists.txt b/document/src/tests/CMakeLists.txt
new file mode 100644
index 00000000000..95e6b4b9a18
--- /dev/null
+++ b/document/src/tests/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_testrunner_app
+ SOURCES
+ teststringutil.cpp
+ testbytebuffer.cpp
+ stringtokenizertest.cpp
+ documentcalculatortestcase.cpp
+ buckettest.cpp
+ globalidtest.cpp
+ documentidtest.cpp
+ documenttypetestcase.cpp
+ primitivefieldvaluetest.cpp
+ arrayfieldvaluetest.cpp
+ weightedsetfieldvaluetest.cpp
+ structfieldvaluetest.cpp
+ documenttestcase.cpp
+ testdocmantest.cpp
+ documentupdatetestcase.cpp
+ fieldpathupdatetestcase.cpp
+ documentselectparsertest.cpp
+ bucketselectortest.cpp
+ testxml.cpp
+ forcelinktest.cpp
+ orderingselectortest.cpp
+ testrunner.cpp
+ heapdebuggerother.cpp
+ positiontypetest.cpp
+ urltypetest.cpp
+ fieldsettest.cpp
+ gid_filter_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_testrunner_app COMMAND document_testrunner_app)
diff --git a/document/src/tests/annotation/.gitignore b/document/src/tests/annotation/.gitignore
new file mode 100644
index 00000000000..861906f0e95
--- /dev/null
+++ b/document/src/tests/annotation/.gitignore
@@ -0,0 +1,6 @@
+*.So
+*_test
+.depend
+Makefile
+annotation_test
+document_annotation_test_app
diff --git a/document/src/tests/annotation/CMakeLists.txt b/document/src/tests/annotation/CMakeLists.txt
new file mode 100644
index 00000000000..1ec6a3e3dde
--- /dev/null
+++ b/document/src/tests/annotation/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_annotation_test_app
+ SOURCES
+ annotation_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_annotation_test_app COMMAND document_annotation_test_app)
diff --git a/document/src/tests/annotation/annotation_test.cpp b/document/src/tests/annotation/annotation_test.cpp
new file mode 100644
index 00000000000..c4976485b8b
--- /dev/null
+++ b/document/src/tests/annotation/annotation_test.cpp
@@ -0,0 +1,295 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for annotation.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("annotation_test");
+
+#include <stdlib.h>
+#include <vespa/document/annotation/alternatespanlist.h>
+#include <vespa/document/annotation/annotation.h>
+#include <vespa/document/annotation/span.h>
+#include <vespa/document/annotation/spanlist.h>
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/datatype/annotationreferencedatatype.h>
+#include <vespa/document/datatype/annotationtype.h>
+#include <vespa/document/datatype/arraydatatype.h>
+#include <vespa/document/datatype/primitivedatatype.h>
+#include <vespa/document/datatype/structdatatype.h>
+#include <vespa/document/fieldvalue/annotationreferencefieldvalue.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/doublefieldvalue.h>
+#include <vespa/document/fieldvalue/structfieldvalue.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <memory>
+
+using std::unique_ptr;
+using namespace document;
+
+namespace {
+
+AnnotationType text_type(1, "text");
+AnnotationType begin_tag(2, "begintag");
+AnnotationType end_tag(3, "endtag");
+AnnotationType body_type(4, "body");
+AnnotationType header_type(5, "header");
+AnnotationType city_type(6, "city");
+AnnotationType markup_type(7, "markup");
+
+template <typename T>
+unique_ptr<T> makeUP(T *p) { return unique_ptr<T>(p); }
+
+TEST("requireThatSpansHaveOrder") {
+ Span span(10, 10);
+ Span before(5, 3);
+ Span overlap_start(8, 10);
+ Span contained(12, 3);
+ Span overlap_end(15, 10);
+ Span after(21, 10);
+ Span overlap_complete(5, 20);
+ Span shorter(10, 5);
+ Span longer(10, 15);
+ EXPECT_TRUE(span > before);
+ EXPECT_TRUE(span > overlap_start);
+ EXPECT_TRUE(span < contained);
+ EXPECT_TRUE(span < overlap_end);
+ EXPECT_TRUE(span < after);
+ EXPECT_TRUE(span > overlap_complete);
+ EXPECT_TRUE(span > shorter);
+ EXPECT_TRUE(span < longer);
+ EXPECT_TRUE(!(span < span));
+}
+
+TEST("requireThatSimpleSpanTreeCanBeBuilt") {
+ SpanList::UP root(new SpanList);
+ root->add(makeUP(new Span(0, 19)));
+ root->add(makeUP(new Span(19, 5)));
+ root->add(makeUP(new Span(24, 21)));
+ root->add(makeUP(new Span(45, 23)));
+ root->add(makeUP(new Span(68, 14)));
+
+ EXPECT_EQUAL(5u, root->size());
+ SpanList::const_iterator it = root->begin();
+ EXPECT_EQUAL(Span(0, 19), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(19, 5), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(24, 21), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(45, 23), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(68, 14), *(static_cast<Span *>(*it++)));
+ EXPECT_TRUE(it == root->end());
+
+ SpanTree tree("html", std::move(root));
+}
+
+TEST("requireThatSpanTreeCanHaveAnnotations") {
+ SpanList::UP root_owner(new SpanList);
+ SpanList *root = root_owner.get();
+ SpanTree tree("html", std::move(root_owner));
+
+ Span &span1 = root->add(makeUP(new Span(0, 19)));
+ tree.annotate(span1, markup_type);
+
+ Span &span2 = root->add(makeUP(new Span(19, 5)));
+ tree.annotate(span2, text_type);
+
+ EXPECT_EQUAL(2u, tree.numAnnotations());
+ SpanTree::const_iterator it = tree.begin();
+
+ EXPECT_EQUAL(Annotation(markup_type), *it++);
+ EXPECT_EQUAL(Annotation(text_type), *it++);
+ EXPECT_TRUE(it == tree.end());
+}
+
+TEST("requireThatSpanTreeCanHaveMultipleLevels") {
+ SpanList::UP root_owner(new SpanList);
+ SpanList *root = root_owner.get();
+ SpanTree tree("html", std::move(root_owner));
+
+ SpanList::UP header(new SpanList);
+ tree.annotate(header->add(makeUP(new Span(6, 6))), begin_tag);
+ tree.annotate(header->add(makeUP(new Span(12, 7))), begin_tag);
+ tree.annotate(header->add(makeUP(new Span(19, 5))), text_type);
+ tree.annotate(header->add(makeUP(new Span(24, 8))), end_tag);
+ tree.annotate(header->add(makeUP(new Span(32, 7))), end_tag);
+ tree.annotate(*header, header_type);
+
+ SpanList::UP body(new SpanList);
+ tree.annotate(body->add(makeUP(new Span(39, 6))), begin_tag);
+ tree.annotate(body->add(makeUP(new Span(45, 23))), text_type);
+ tree.annotate(body->add(makeUP(new Span(68, 7))), end_tag);
+ tree.annotate(*body, body_type);
+
+ tree.annotate(root->add(makeUP(new Span(0, 6))), begin_tag);
+ root->add(std::move(std::move(header)));
+ root->add(std::move(std::move(body)));
+ tree.annotate(root->add(makeUP(new Span(75, 7))), end_tag);
+
+ EXPECT_EQUAL(12u, tree.numAnnotations());
+ SpanTree::const_iterator it = tree.begin();
+ EXPECT_EQUAL(Annotation(begin_tag), *it++);
+ EXPECT_EQUAL(Annotation(begin_tag), *it++);
+ EXPECT_EQUAL(Annotation(text_type), *it++);
+ EXPECT_EQUAL(Annotation(end_tag), *it++);
+ EXPECT_EQUAL(Annotation(end_tag), *it++);
+ EXPECT_EQUAL(Annotation(header_type), *it++);
+ EXPECT_EQUAL(Annotation(begin_tag), *it++);
+ EXPECT_EQUAL(Annotation(text_type), *it++);
+ EXPECT_EQUAL(Annotation(end_tag), *it++);
+ EXPECT_EQUAL(Annotation(body_type), *it++);
+ EXPECT_EQUAL(Annotation(begin_tag), *it++);
+ EXPECT_EQUAL(Annotation(end_tag), *it++);
+ EXPECT_TRUE(it == tree.end());
+}
+
+TEST("requireThatAnnotationsCanHaveValues") {
+ PrimitiveDataType double_type(DataType::T_DOUBLE);
+ StructDataType city_data_type;
+ city_data_type.addField(Field("latitude", 0, double_type, false));
+ city_data_type.addField(Field("longitude", 1, double_type, false));
+
+ StructFieldValue::UP position(new StructFieldValue(city_data_type));
+ position->setValue("latitude", DoubleFieldValue(37.774929));
+ position->setValue("longitude", DoubleFieldValue(-122.419415));
+ StructFieldValue original(*position);
+
+ Annotation city(city_type, std::move(position));
+
+ EXPECT_TRUE(*city.getFieldValue() == original);
+}
+
+TEST("requireThatAnnotationsCanReferenceAnnotations") {
+ SpanList::UP root(new SpanList);
+ SpanTree tree("html", std::move(root));
+ size_t san_index = tree.annotate(makeUP(new Annotation(text_type)));
+ size_t fran_index = tree.annotate(makeUP(new Annotation(text_type)));
+
+ AnnotationReferenceDataType annotation_ref_type(text_type, 101);
+ ArrayDataType array_type(annotation_ref_type);
+ StructDataType city_data_type("name", 42);
+ city_data_type.addField(Field("references", 0, array_type, false));
+
+ StructFieldValue::UP city_data(new StructFieldValue(city_data_type));
+ ArrayFieldValue ref_list(array_type);
+ ref_list.add(AnnotationReferenceFieldValue(annotation_ref_type,
+ san_index));
+ ref_list.add(AnnotationReferenceFieldValue(annotation_ref_type,
+ fran_index));
+ city_data->setValue("references", ref_list);
+ StructFieldValue original(*city_data);
+
+ Annotation city(city_type, std::move(city_data));
+
+ ASSERT_TRUE(city.getFieldValue());
+ EXPECT_TRUE(city.getFieldValue()->isA(original));
+ EXPECT_EQUAL(original, *city.getFieldValue());
+}
+
+const double prob0 = 0.6;
+const double prob1 = 0.4;
+
+TEST("requireThatAlternateSpanListHoldsMultipleLists") {
+ AlternateSpanList span_list;
+ span_list.add(0, makeUP(new Span(0, 19)));
+ span_list.add(0, makeUP(new Span(19, 5)));
+ span_list.add(1, makeUP(new Span(0, 5)));
+ span_list.add(1, makeUP(new Span(5, 19)));
+ span_list.setProbability(0, prob0);
+ span_list.setProbability(1, prob1);
+
+ EXPECT_EQUAL(2u, span_list.getNumSubtrees());
+ EXPECT_EQUAL(2u, span_list.getSubtree(0).size());
+ EXPECT_EQUAL(2u, span_list.getSubtree(1).size());
+ EXPECT_EQUAL(prob0, span_list.getProbability(0));
+ EXPECT_EQUAL(prob1, span_list.getProbability(1));
+
+ SpanList::const_iterator it = span_list.getSubtree(0).begin();
+ EXPECT_EQUAL(Span(0, 19), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(19, 5), *(static_cast<Span *>(*it++)));
+ EXPECT_TRUE(it == span_list.getSubtree(0).end());
+
+ it = span_list.getSubtree(1).begin();
+ EXPECT_EQUAL(Span(0, 5), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(5, 19), *(static_cast<Span *>(*it++)));
+ EXPECT_TRUE(it == span_list.getSubtree(1).end());
+}
+
+struct MySpanTreeVisitor : SpanTreeVisitor {
+ int span_count;
+ int span_list_count;
+ int alt_span_list_count;
+
+ MySpanTreeVisitor()
+ : span_count(0), span_list_count(0), alt_span_list_count(0) {}
+
+ void visitChildren(const SpanList &node) {
+ for (SpanList::const_iterator
+ it = node.begin(); it != node.end(); ++it) {
+ (*it)->accept(*this);
+ }
+ }
+
+ void visitChildren(const SimpleSpanList &node) {
+ for (SimpleSpanList::const_iterator
+ it = node.begin(); it != node.end(); ++it) {
+ (*it).accept(*this);
+ }
+ }
+
+ void visit(const Span &) override { ++span_count; }
+ void visit(const SpanList &node) override {
+ ++span_list_count;
+ visitChildren(node);
+ }
+ void visit(const SimpleSpanList &node) override {
+ ++span_list_count;
+ visitChildren(node);
+ }
+ void visit(const AlternateSpanList &node) override {
+ ++alt_span_list_count;
+ for (size_t i = 0; i < node.getNumSubtrees(); ++i) {
+ visitChildren(node.getSubtree(i));
+ }
+ }
+};
+
+TEST("requireThatSpanTreeCanBeVisited") {
+ SpanList::UP root(new SpanList);
+ root->add(makeUP(new Span(0, 19)));
+ AlternateSpanList::UP alt_list(new AlternateSpanList);
+ alt_list->add(0, makeUP(new Span(19, 5)));
+ alt_list->add(1, makeUP(new Span(24, 21)));
+ root->add(std::move(alt_list));
+
+ SpanTree tree("html", std::move(root));
+
+ MySpanTreeVisitor visitor;
+ tree.accept(visitor);
+
+ EXPECT_EQUAL(3, visitor.span_count);
+ EXPECT_EQUAL(1, visitor.span_list_count);
+ EXPECT_EQUAL(1, visitor.alt_span_list_count);
+}
+
+TEST("requireThatDefaultAnnotationTypesHaveDefaultDataTypes") {
+ ASSERT_TRUE(AnnotationType::TERM->getDataType());
+ EXPECT_EQUAL(*DataType::STRING, *AnnotationType::TERM->getDataType());
+ ASSERT_TRUE(AnnotationType::TOKEN_TYPE->getDataType());
+ EXPECT_EQUAL(*DataType::INT, *AnnotationType::TOKEN_TYPE->getDataType());
+}
+
+TEST("require that SpanTrees can be compared") {
+ SpanList::UP root(new SpanList);
+ root->add(makeUP(new Span(0, 19)));
+ SpanTree tree1("html", std::move(root));
+
+ root.reset(new SpanList);
+ root->add(makeUP(new Span(0, 18)));
+ SpanTree tree2("html", std::move(root));
+
+ EXPECT_EQUAL(0, tree1.compare(tree1));
+ EXPECT_LESS(0, tree1.compare(tree2));
+ EXPECT_GREATER(0, tree2.compare(tree1));
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/arrayfieldvaluetest.cpp b/document/src/tests/arrayfieldvaluetest.cpp
new file mode 100644
index 00000000000..136129274b5
--- /dev/null
+++ b/document/src/tests/arrayfieldvaluetest.cpp
@@ -0,0 +1,247 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/vespalib/objects/nbostream.h>
+
+using vespalib::nbostream;
+
+namespace document {
+
+struct ArrayFieldValueTest : public CppUnit::TestFixture {
+ void setUp() {}
+ void tearDown() {}
+
+ void testArray();
+
+ CPPUNIT_TEST_SUITE(ArrayFieldValueTest);
+ CPPUNIT_TEST(testArray);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ArrayFieldValueTest);
+
+namespace {
+template <typename T>
+void deserialize(const ByteBuffer &buffer, T &value) {
+ uint16_t version = Document::getNewestSerializationVersion();
+ nbostream stream(buffer.getBufferAtPos(), buffer.getRemaining());
+ DocumentTypeRepo repo;
+ VespaDocumentDeserializer deserializer(repo, stream, version);
+ deserializer.read(value);
+}
+} // namespace
+
+void ArrayFieldValueTest::testArray()
+{
+ ArrayDataType type(*DataType::INT);
+ ArrayFieldValue value(type);
+
+ // Initially empty
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value.size());
+ CPPUNIT_ASSERT(value.isEmpty());
+ CPPUNIT_ASSERT(!value.contains(IntFieldValue(1)));
+
+ CPPUNIT_ASSERT(value.add(IntFieldValue(1)));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(1), value.size());
+ CPPUNIT_ASSERT(!value.isEmpty());
+ CPPUNIT_ASSERT(value.contains(IntFieldValue(1)));
+
+ // Adding some more
+ CPPUNIT_ASSERT(value.add(IntFieldValue(2)));
+ CPPUNIT_ASSERT(value.add(IntFieldValue(3)));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(3), value.size());
+ CPPUNIT_ASSERT(!value.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(IntFieldValue(1), (IntFieldValue&) value[0]);
+ CPPUNIT_ASSERT_EQUAL(IntFieldValue(2), (IntFieldValue&) value[1]);
+ CPPUNIT_ASSERT_EQUAL(IntFieldValue(3), (IntFieldValue&) value[2]);
+
+ // Serialize & equality
+ std::unique_ptr<ByteBuffer> buffer(value.serialize());
+ buffer->flip();
+ ArrayFieldValue value2(type);
+ CPPUNIT_ASSERT(value != value2);
+ deserialize(*buffer, value2);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+
+ // Various ways of removing
+ {
+ // By index
+ buffer->setPos(0);
+ deserialize(*buffer, value2);
+ value2.remove(1);
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(2)));
+ CPPUNIT_ASSERT_EQUAL(size_t(2), value2.size());
+
+ // By value
+ buffer->setPos(0);
+ deserialize(*buffer, value2);
+ CPPUNIT_ASSERT(value2.remove(IntFieldValue(1)));
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(1)));
+ CPPUNIT_ASSERT_EQUAL(size_t(2), value2.size());
+
+ // By value with multiple present
+ buffer->setPos(0);
+ deserialize(*buffer, value2);
+ value2.add(IntFieldValue(1));
+ CPPUNIT_ASSERT(value2.remove(IntFieldValue(1)));
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(1)));
+ CPPUNIT_ASSERT_EQUAL(size_t(2), value2.size());
+
+ // Clearing all
+ buffer->setPos(0);
+ deserialize(*buffer, value2);
+ value2.clear();
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(1)));
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value2.size());
+ CPPUNIT_ASSERT(value2.isEmpty());
+ }
+
+ // Updating
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ value2[1].assign(IntFieldValue(5));
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(2)));
+ CPPUNIT_ASSERT_EQUAL(IntFieldValue(5), (IntFieldValue&) value2[1]);
+ CPPUNIT_ASSERT(value != value2);
+ value2.assign(value);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ ArrayFieldValue::UP valuePtr(value2.clone());
+ CPPUNIT_ASSERT_EQUAL(value, *valuePtr);
+
+ // Iterating
+ const ArrayFieldValue& constVal(value);
+ for(ArrayFieldValue::const_iterator it = constVal.begin();
+ it != constVal.end(); ++it)
+ {
+ const FieldValue& fval1(*it);
+ (void) fval1;
+ CPPUNIT_ASSERT_EQUAL((uint32_t) IntFieldValue::classId,
+ it->getClass().id());
+ }
+ value2 = value;
+ for(ArrayFieldValue::iterator it = value2.begin(); it != value2.end(); ++it)
+ {
+ (*it).assign(IntFieldValue(7));
+ it->assign(IntFieldValue(7));
+ }
+ CPPUNIT_ASSERT(value != value2);
+ CPPUNIT_ASSERT(value2.contains(IntFieldValue(7)));
+ CPPUNIT_ASSERT(value2.remove(IntFieldValue(7)));
+ CPPUNIT_ASSERT(value2.isEmpty());
+
+ // Comparison
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(0, value.compare(value2));
+ value2.remove(1);
+ CPPUNIT_ASSERT(value.compare(value2) > 0);
+ CPPUNIT_ASSERT(value2.compare(value) < 0);
+ value2 = value;
+ value2[1].assign(IntFieldValue(5));
+ CPPUNIT_ASSERT(value.compare(value2) < 0);
+ CPPUNIT_ASSERT(value2.compare(value) > 0);
+
+ // Output
+ CPPUNIT_ASSERT_EQUAL(std::string("Array(size: 3,\n 1,\n 2,\n 3\n)"),
+ value.toString(false));
+ CPPUNIT_ASSERT_EQUAL(std::string("Array(size: 3,\n. 1,\n. 2,\n. 3\n.)"),
+ value.toString(true, "."));
+ CPPUNIT_ASSERT_EQUAL(std::string(
+ "<value>\n"
+ " <item>1</item>\n"
+ " <item>2</item>\n"
+ " <item>3</item>\n"
+ "</value>"
+ ),
+ value.toXml(" "));
+
+ // Failure situations.
+
+ // Refuse to accept non-array types
+ try{
+ ArrayFieldValue value6(*DataType::TAG);
+ CPPUNIT_FAIL("Didn't complain about non-array type");
+ } catch (std::exception& e) {
+ fprintf(stderr, "Exception = %s\n", e.what());
+ CPPUNIT_ASSERT_CONTAIN(
+ "Cannot generate an array value with non-array type",
+ e.what());
+ }
+
+ // Verify that datatypes are verified
+ // Created almost equal types to try to get it to fail
+ ArrayDataType type1(*DataType::INT);
+ ArrayDataType type2(*DataType::LONG);
+ ArrayDataType type3(static_cast<DataType &>(type1));
+ ArrayDataType type4(static_cast<DataType &>(type2));
+ ArrayFieldValue value3(type3);
+ ArrayFieldValue value4(type4);
+ try{
+ value3 = value4;
+ CPPUNIT_FAIL("Failed to check type equality in operator=");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot assign value of type", e.what());
+ }
+ try{
+ value3.assign(value4);
+ CPPUNIT_FAIL("Failed to check type equality in assign()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot assign value of type", e.what());
+ }
+ try{
+ ArrayFieldValue subValue(type2);
+ subValue.add(LongFieldValue(4));
+ value3.add(subValue);
+ CPPUNIT_FAIL("Failed to check type equality in add()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot add value of type",e.what());
+ }
+ try{
+ ArrayFieldValue subValue(type2);
+ subValue.add(LongFieldValue(4));
+ value3.contains(subValue);
+ CPPUNIT_FAIL("Failed to check type equality in contains()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("can't possibly be in array of type", e.what());
+ }
+ try{
+ ArrayFieldValue subValue(type2);
+ subValue.add(LongFieldValue(4));
+ value3.remove(subValue);
+ CPPUNIT_FAIL("Failed to check type equality in remove()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("can't possibly be in array of type", e.what());
+ }
+ // Verify that compare sees difference
+ {
+ ArrayFieldValue subValue1(type1);
+ ArrayFieldValue subValue2(type2);
+ subValue1.add(IntFieldValue(3));
+ subValue2.add(LongFieldValue(3));
+ value3.clear();
+ value4.clear();
+ value3.add(subValue1);
+ value4.add(subValue2);
+ CPPUNIT_ASSERT(value3.compare(value4) != 0);
+ }
+
+ // Removing non-existing element
+ value2 = value;
+ try{
+ value2.remove(5);
+ CPPUNIT_FAIL("Failed to throw out of bounds exception on remove(int)");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot remove index 5 from an array of size 3",
+ e.what());
+ }
+ CPPUNIT_ASSERT(!value2.remove(IntFieldValue(15)));
+}
+
+} // document
diff --git a/document/src/tests/arrayfieldvaluetest.h b/document/src/tests/arrayfieldvaluetest.h
new file mode 100644
index 00000000000..8118d57021a
--- /dev/null
+++ b/document/src/tests/arrayfieldvaluetest.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class ArrayFieldValue_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( ArrayFieldValue_Test);
+ CPPUNIT_TEST(testArray);
+ CPPUNIT_TEST(testArray2);
+ CPPUNIT_TEST(testArrayRaw);
+ CPPUNIT_TEST(testTextSerialize);
+ CPPUNIT_TEST(testArrayRemove);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp();
+
+ void tearDown();
+protected:
+ void testArray();
+ void testArray2();
+ void testArrayRaw();
+ void testTextSerialize();
+ void testArrayRemove();
+};
+
+
diff --git a/document/src/tests/base/.gitignore b/document/src/tests/base/.gitignore
new file mode 100644
index 00000000000..bdb07eae1cb
--- /dev/null
+++ b/document/src/tests/base/.gitignore
@@ -0,0 +1,7 @@
+*.So
+*_test
+*_benchmark
+.depend
+Makefile
+document_documentid_test_app
+document_documentid_benchmark_app
diff --git a/document/src/tests/base/CMakeLists.txt b/document/src/tests/base/CMakeLists.txt
new file mode 100644
index 00000000000..550d77d1340
--- /dev/null
+++ b/document/src/tests/base/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_documentid_test_app
+ SOURCES
+ documentid_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_documentid_test_app COMMAND document_documentid_test_app)
+vespa_add_executable(document_documentid_benchmark_app
+ SOURCES
+ documentid_benchmark.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_documentid_benchmark_app COMMAND document_documentid_benchmark_app BENCHMARK)
diff --git a/document/src/tests/base/documentid_benchmark.cpp b/document/src/tests/base/documentid_benchmark.cpp
new file mode 100644
index 00000000000..16f9c9016d7
--- /dev/null
+++ b/document/src/tests/base/documentid_benchmark.cpp
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/document/base/documentid.h>
+
+using namespace document;
+
+int main(int argc, char *argv[])
+{
+ if (argc < 3) {
+ fprintf(stderr, "Usage %s <docid> <count>\n", argv[0]);
+ }
+ vespalib::string s(argv[1]);
+ uint64_t n = strtoul(argv[2], NULL, 0);
+ printf("Creating documentid '%s' %ld times\n", s.c_str(), n);
+ printf("sizeof(IdString)=%ld, sizeof(IdIdString)=%ld\n", sizeof(IdString), sizeof(IdIdString));
+ for (uint64_t i=0; i < n; i++) {
+ IdString::UP id = IdString::createIdString(s);
+ id.reset();
+ }
+ return 0;
+}
+
diff --git a/document/src/tests/base/documentid_test.cpp b/document/src/tests/base/documentid_test.cpp
new file mode 100644
index 00000000000..62ce42c0fb6
--- /dev/null
+++ b/document/src/tests/base/documentid_test.cpp
@@ -0,0 +1,228 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for documentid.
+
+#include <vespa/log/log.h>
+LOG_SETUP("documentid_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/base/documentid.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace document;
+using vespalib::string;
+
+namespace {
+
+const string ns = "namespace";
+const string ns_id = "namespaceid";
+const string type = "my_type";
+
+void checkId(const string &id, IdString::Type t,
+ const string &ns_str, const string local_id) {
+ DocumentId doc_id(id);
+ EXPECT_EQUAL(t, doc_id.getScheme().getType());
+ EXPECT_EQUAL(ns_str, doc_id.getScheme().getNamespace());
+ EXPECT_EQUAL(local_id, doc_id.getScheme().getNamespaceSpecific());
+ EXPECT_EQUAL(id, doc_id.getScheme().toString());
+}
+
+template <typename IdType>
+const IdType &getAs(const DocumentId &doc_id) {
+ const IdType *id_str = dynamic_cast<const IdType *>(&doc_id.getScheme());
+ ASSERT_TRUE(id_str);
+ return *id_str;
+}
+
+template <typename IdType>
+void checkUser(const string &id, int64_t user_id) {
+ DocumentId doc_id(id);
+ EXPECT_EQUAL(user_id, getAs<IdType>(doc_id).getUserId());
+}
+
+void checkOrdering(const string &id, uint64_t ordering) {
+ DocumentId doc_id(id);
+ EXPECT_EQUAL(ordering, getAs<OrderDocIdString>(doc_id).getOrdering());
+}
+
+void checkGroup(const string &id, const string &group) {
+ DocumentId doc_id(id);
+ EXPECT_EQUAL(group, getAs<GroupDocIdString>(doc_id).getGroup());
+}
+
+void checkType(const string &id, const string &doc_type) {
+ DocumentId doc_id(id);
+ ASSERT_TRUE(doc_id.hasDocType());
+ EXPECT_EQUAL(doc_type, doc_id.getDocType());
+}
+
+TEST("require that doc id can be parsed") {
+ const string id = "doc:" + ns + ":" + ns_id;
+ checkId(id, IdString::DOC, ns, ns_id);
+}
+
+TEST("require that userdoc id can be parsed") {
+ const string id = "userdoc:" + ns + ":1234:" + ns_id;
+ checkId(id, IdString::USERDOC, ns, ns_id);
+ checkUser<UserDocIdString>(id, 1234);
+}
+
+TEST("require that orderdoc id can be parsed") {
+ const string id = "orderdoc(31,19):" + ns + ":1234:1268182861:" + ns_id;
+ checkId(id, IdString::ORDERDOC, ns, ns_id);
+ checkUser<OrderDocIdString>(id, 1234);
+ checkOrdering(id, 1268182861);
+}
+
+TEST("require that groupdoc id can be parsed") {
+ const string group = "mygroup";
+ const string id = "groupdoc:" + ns + ":" + group + ":" + ns_id;
+ checkId(id, IdString::GROUPDOC, ns, ns_id);
+ checkGroup(id, group);
+}
+
+TEST("require that id id can be parsed") {
+ const string id = "id:" + ns + ":" + type + "::" + ns_id;
+ checkId(id, IdString::ID, ns, ns_id);
+ checkType(id, type);
+}
+
+TEST("require that we allow ':' in namespace specific part") {
+ const string nss=":a:b:c:";
+ string id="id:" + ns + ":" + type + "::" + nss;
+ checkId(id, IdString::ID, ns, nss);
+ checkType(id, type);
+
+ id="doc:" + ns + ":" + nss;
+ checkId(id, IdString::DOC, ns, nss);
+
+ id="userdoc:" + ns + ":123:" + nss;
+ checkId(id, IdString::USERDOC, ns, nss);
+ checkUser<UserDocIdString>(id, 123);
+
+ id="groupdoc:" + ns + ":123:" + nss;
+ checkId(id, IdString::GROUPDOC, ns, nss);
+ checkGroup(id, "123");
+
+ id="orderdoc(5,2):" + ns + ":42:007:" + nss;
+ checkId(id, IdString::ORDERDOC, ns, nss);
+}
+
+TEST("require that id id can specify location") {
+ DocumentId id("id:ns:type:n=12345:foo");
+ EXPECT_EQUAL(12345u, id.getScheme().getLocation());
+ EXPECT_EQUAL(12345u, getAs<IdIdString>(id).getNumber());
+}
+
+TEST("require that id id's n key must be a 64-bit number") {
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:n=abc:foo"), IdParseException,
+ "'n'-value must be a 64-bit number");
+ DocumentId("id:ns:type:n=18446744073709551615:foo"); // ok
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:n=18446744073709551616:foo"),
+ IdParseException, "'n'-value out of range");
+}
+
+TEST("require that id id can specify group") {
+ DocumentId id1("id:ns:type:g=mygroup:foo");
+ DocumentId id2("id:ns:type:g=mygroup:bar");
+ DocumentId id3("id:ns:type:g=other group:baz");
+ EXPECT_EQUAL(id1.getScheme().getLocation(), id2.getScheme().getLocation());
+ EXPECT_NOT_EQUAL(id1.getScheme().getLocation(),
+ id3.getScheme().getLocation());
+ EXPECT_EQUAL("mygroup", getAs<IdIdString>(id1).getGroup());
+}
+
+TEST("require that id id location is specified by local id only by default") {
+ DocumentId id1("id:ns:type::locationspec");
+ DocumentId id2("id:ns:type:g=locationspec:bar");
+ EXPECT_EQUAL("locationspec", id1.getScheme().getNamespaceSpecific());
+ EXPECT_EQUAL("bar", id2.getScheme().getNamespaceSpecific());
+ EXPECT_EQUAL(id1.getScheme().getLocation(), id2.getScheme().getLocation());
+}
+
+TEST("require that local id can be empty") {
+ const string id = "userdoc:" + ns + ":1234:";
+ checkId(id, IdString::USERDOC, ns, "");
+ checkUser<UserDocIdString>(id, 1234);
+}
+
+TEST("require that document ids can be assigned") {
+ DocumentId id1("userdoc:" + ns + ":1234:");
+ DocumentId id2 = id1;
+ checkId(id2.toString(), IdString::USERDOC, ns, "");
+ checkUser<UserDocIdString>(id2.toString(), 1234);
+}
+
+TEST("require that illegal ids fail") {
+ EXPECT_EXCEPTION(DocumentId("idg:foo:bar:baz"), IdParseException,
+ "No scheme separator ':' found");
+ EXPECT_EXCEPTION(DocumentId("id:"), IdParseException, "too short");
+ EXPECT_EXCEPTION(DocumentId("id:ns"), IdParseException,
+ "No namespace separator ':' found");
+ EXPECT_EXCEPTION(DocumentId("id:ns:type"), IdParseException,
+ "No document type separator ':' found");
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:kv_pair"), IdParseException,
+ "No key/value-pairs separator ':' found");
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:k=foo:bar"), IdParseException,
+ "Illegal key 'k'");
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:n=0,n=1:bar"), IdParseException,
+ "Illegal key combination in n=0,n=1");
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:g=foo,g=ba:bar"), IdParseException,
+ "Illegal key combination in g=foo,g=ba");
+ EXPECT_EXCEPTION(DocumentId("id:ns:type:n=0,g=foo:bar"), IdParseException,
+ "Illegal key combination in n=0,g=foo");
+}
+
+TEST("require that key-value pairs in id id are preserved") {
+ const string id_str1 = "id:ns:type:n=1:foo";
+ EXPECT_EQUAL(id_str1, DocumentId(id_str1).toString());
+
+ const string id_str2 = "id:ns:type:g=mygroup:foo";
+ EXPECT_EQUAL(id_str2, DocumentId(id_str2).toString());
+}
+
+TEST("require that id strings reports features (hasNumber, hasGroup)") {
+ DocumentId none("id:ns:type::foo");
+ EXPECT_FALSE(none.getScheme().hasNumber());
+ EXPECT_FALSE(none.getScheme().hasGroup());
+ EXPECT_EQUAL("foo", none.getScheme().getNamespaceSpecific());
+
+ none = DocumentId("doc:ns:foo");
+ EXPECT_FALSE(none.getScheme().hasNumber());
+ EXPECT_FALSE(none.getScheme().hasGroup());
+ EXPECT_EQUAL("foo", none.getScheme().getNamespaceSpecific());
+
+ DocumentId user("id:ns:type:n=42:foo");
+ EXPECT_TRUE(user.getScheme().hasNumber());
+ EXPECT_FALSE(user.getScheme().hasGroup());
+ EXPECT_EQUAL(42u, user.getScheme().getNumber());
+ EXPECT_EQUAL("foo", user.getScheme().getNamespaceSpecific());
+
+ user = DocumentId("userdoc:ns:42:foo");
+ EXPECT_TRUE(user.getScheme().hasNumber());
+ EXPECT_FALSE(user.getScheme().hasGroup());
+ EXPECT_EQUAL(42u, user.getScheme().getNumber());
+ EXPECT_EQUAL("foo", user.getScheme().getNamespaceSpecific());
+
+ DocumentId group("id:ns:type:g=mygroup:foo");
+ EXPECT_FALSE(group.getScheme().hasNumber());
+ EXPECT_TRUE(group.getScheme().hasGroup());
+ EXPECT_EQUAL("mygroup", group.getScheme().getGroup());
+ EXPECT_EQUAL("foo", group.getScheme().getNamespaceSpecific());
+
+ group = DocumentId("groupdoc:ns:mygroup:foo");
+ EXPECT_FALSE(group.getScheme().hasNumber());
+ EXPECT_TRUE(group.getScheme().hasGroup());
+ EXPECT_EQUAL("mygroup", group.getScheme().getGroup());
+ EXPECT_EQUAL("foo", group.getScheme().getNamespaceSpecific());
+
+ DocumentId order("orderdoc(5,2):ns:42:007:foo");
+ EXPECT_TRUE(order.getScheme().hasNumber());
+ EXPECT_TRUE(order.getScheme().hasGroup());
+ EXPECT_EQUAL(42u, order.getScheme().getNumber());
+ EXPECT_EQUAL("42", order.getScheme().getGroup());
+ EXPECT_EQUAL("foo", order.getScheme().getNamespaceSpecific());
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/bucketselectortest.cpp b/document/src/tests/bucketselectortest.cpp
new file mode 100644
index 00000000000..f04816cba0d
--- /dev/null
+++ b/document/src/tests/bucketselectortest.cpp
@@ -0,0 +1,102 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/bucket/bucketselector.h>
+
+#include <vespa/document/bucket/bucketid.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/document/select/parser.h>
+#include <vespa/document/base/testdocrepo.h>
+#include <memory>
+
+using document::select::Node;
+using document::select::Parser;
+
+namespace document {
+
+struct BucketSelectorTest : public CppUnit::TestFixture {
+ void testSimple();
+
+ CPPUNIT_TEST_SUITE(BucketSelectorTest);
+ CPPUNIT_TEST(testSimple);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BucketSelectorTest);
+
+#define ASSERT_BUCKET_COUNT(expression, count) \
+{ \
+ TestDocRepo testRepo; \
+ BucketIdFactory idfactory; \
+ BucketSelector selector(idfactory); \
+ Parser parser(testRepo.getTypeRepo(), idfactory); \
+ std::unique_ptr<Node> node(parser.parse(expression)); \
+ CPPUNIT_ASSERT(node.get() != 0); \
+ std::unique_ptr<BucketSelector::BucketVector> buckets( \
+ selector.select(*node)); \
+ size_t bcount(buckets.get() ? buckets->size() : 0); \
+ std::ostringstream ost; \
+ ost << "Expression " << expression << " did not contain " << count \
+ << " buckets as expected"; \
+ CPPUNIT_ASSERT_EQUAL_MSG(ost.str(), (size_t) count, bcount); \
+}
+
+#define ASSERT_BUCKET(expression, bucket) \
+{ \
+ TestDocRepo testRepo; \
+ BucketIdFactory idfactory; \
+ BucketSelector selector(idfactory); \
+ Parser parser(testRepo.getTypeRepo(), idfactory); \
+ std::unique_ptr<Node> node(parser.parse(expression)); \
+ CPPUNIT_ASSERT(node.get() != 0); \
+ std::unique_ptr<BucketSelector::BucketVector> buckets( \
+ selector.select(*node)); \
+ std::ostringstream ost; \
+ ost << "Expression " << expression << " did not contain bucket " \
+ << bucket.toString(); \
+ if (buckets.get()) { \
+ ost << ". Buckets: " << std::hex << *buckets; \
+ } else { \
+ ost << ". Matches all buckets"; \
+ } \
+ CPPUNIT_ASSERT_MSG(ost.str(), buckets.get() && \
+ std::find(buckets->begin(), buckets->end(), \
+ bucket) != buckets->end()); \
+}
+
+void BucketSelectorTest::testSimple()
+{
+ ASSERT_BUCKET_COUNT("id = \"userdoc:ns:123:foobar\"", 1u);
+ ASSERT_BUCKET_COUNT("id = \"userdoc:ns:123:foo*\"", 0u);
+ ASSERT_BUCKET_COUNT("id == \"userdoc:ns:123:f?oo*\"", 1u);
+ ASSERT_BUCKET_COUNT("id =~ \"userdoc:ns:123:foo*\"", 0u);
+ ASSERT_BUCKET_COUNT("id =~ \"userdoc:ns:123:foo?\"", 0u);
+ ASSERT_BUCKET_COUNT("id.user = 123", 1u);
+ ASSERT_BUCKET_COUNT("id.user == 123", 1u);
+ ASSERT_BUCKET_COUNT("id.group = \"yahoo.com\"", 1u);
+ ASSERT_BUCKET_COUNT("id.group = \"yahoo.com\" or id.user=123", 2u);
+ ASSERT_BUCKET_COUNT("id.group = \"yahoo.com\" and id.user=123", 0u);
+ ASSERT_BUCKET_COUNT(
+ "id.group = \"yahoo.com\" and testdoctype1.hstringval=\"Doe\"", 1u);
+ ASSERT_BUCKET_COUNT("not id.group = \"yahoo.com\"", 0u);
+ ASSERT_BUCKET_COUNT("id.group != \"yahoo.com\"", 0u);
+ ASSERT_BUCKET_COUNT("id.group <= \"yahoo.com\"", 0u);
+
+ ASSERT_BUCKET_COUNT("id.bucket = 0x4000000000003018", 1u); // Bucket 12312
+ ASSERT_BUCKET_COUNT("id.bucket == 0x4000000000000258", 1u); // Bucket 600
+ ASSERT_BUCKET_COUNT("(testdoctype1 and id.bucket=0)", 1u);
+
+ ASSERT_BUCKET_COUNT("searchcolumn.3 = 1", 21845u);
+
+ // Check that the correct buckets is found
+ ASSERT_BUCKET("id = \"userdoc:ns:123:foobar\"",
+ document::BucketId(58, 123));
+
+ ASSERT_BUCKET("id.bucket == 0x4000000000000258", document::BucketId(16, 600));
+
+ ASSERT_BUCKET("id.user = 123", document::BucketId(32, 123));
+ ASSERT_BUCKET("id.group = \"yahoo.com\"",
+ document::BucketId(32, 0x9a1acd50));
+}
+
+} // document
diff --git a/document/src/tests/buckettest.cpp b/document/src/tests/buckettest.cpp
new file mode 100644
index 00000000000..2f2f3df9ab6
--- /dev/null
+++ b/document/src/tests/buckettest.cpp
@@ -0,0 +1,258 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <vespa/document/bucket/bucketidfactory.h>
+#include <vespa/document/base/documentid.h>
+#include <vespa/vespalib/util/random.h>
+
+namespace document {
+
+class BucketTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(BucketTest);
+ CPPUNIT_TEST(testBucket);
+ CPPUNIT_TEST(testBucketGeneration);
+ CPPUNIT_TEST(testBucketSerialization);
+ CPPUNIT_TEST(testReverseBucket);
+ CPPUNIT_TEST(testContains);
+ CPPUNIT_TEST(testGetBit);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void testBucket();
+ void testBucketGeneration();
+ void testBucketSerialization();
+ void testReverseBucket();
+ void testContains();
+ void testGetBit();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BucketTest);
+
+struct Hex {
+ BucketId::Type val;
+
+ Hex(BucketId::Type v) : val(v) {}
+ bool operator==(const Hex& h) const { return val == h.val; }
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Hex& h) {
+ out << std::hex << h.val << std::dec;
+ return out;
+}
+
+void BucketTest::testBucket()
+{
+ // Test empty (invalid) buckets
+ BucketId id1;
+ BucketId id2;
+ CPPUNIT_ASSERT_EQUAL(id1, id2);
+ CPPUNIT_ASSERT(!(id1 < id2) && !(id2 < id1));
+ CPPUNIT_ASSERT_EQUAL(Hex(0), Hex(id1.getId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0), Hex(id1.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("BucketId(0x0000000000000000)"),
+ id1.toString());
+ CPPUNIT_ASSERT_EQUAL(0u, id1.getUsedBits());
+
+ // Test bucket with a value
+ id2 = BucketId((BucketId::Type(16) << 58) | 0x123);
+ CPPUNIT_ASSERT(id1 != id2);
+ CPPUNIT_ASSERT((id1 < id2) && !(id2 < id1));
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000000123ull), Hex(id2.getId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000000123ull), Hex(id2.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("BucketId(0x4000000000000123)"),
+ id2.toString());
+ CPPUNIT_ASSERT_EQUAL(16u, id2.getUsedBits());
+
+ // Test copy constructor and operator=
+ BucketId id3(id2);
+ CPPUNIT_ASSERT_EQUAL(id2, id3);
+ id3 = id1;
+ CPPUNIT_ASSERT(!(id2 == id3));
+ id3 = id2;
+ CPPUNIT_ASSERT_EQUAL(id2, id3);
+}
+
+void BucketTest::testGetBit()
+{
+ for (uint32_t i = 0; i < 58; ++i) {
+ CPPUNIT_ASSERT_EQUAL(0, (int)document::BucketId(16, 0).getBit(i));
+ }
+
+ for (uint32_t i = 0; i < 4; ++i) {
+ CPPUNIT_ASSERT_EQUAL(0, (int)document::BucketId(16, 16).getBit(i));
+ }
+
+ CPPUNIT_ASSERT_EQUAL(1, (int)document::BucketId(16, 16).getBit(4));
+
+ for (uint32_t i = 5; i < 59; ++i) {
+ CPPUNIT_ASSERT_EQUAL(0, (int)document::BucketId(16, 16).getBit(i));
+ }
+
+ CPPUNIT_ASSERT_EQUAL(0, (int)document::BucketId(17, 0x0ffff).getBit(16));
+
+ for (uint32_t i = 0; i < 16; ++i) {
+ CPPUNIT_ASSERT_EQUAL(1, (int)document::BucketId(17, 0x0ffff).getBit(i));
+ }
+}
+
+void BucketTest::testBucketGeneration()
+{
+ BucketIdFactory factory;
+ DocumentId doc1("doc:ns:spec");
+ DocumentId doc2("doc:ns2:spec");
+ DocumentId doc3("doc:ns:spec2");
+ DocumentId userDoc1("userdoc:ns:18:spec");
+ DocumentId userDoc2("userdoc:ns2:18:spec2");
+ DocumentId userDoc3("userdoc:ns:19:spec");
+ DocumentId groupDoc1("groupdoc:ns:yahoo.com:spec");
+ DocumentId groupDoc2("groupdoc:ns2:yahoo.com:spec2");
+ DocumentId groupDoc3("groupdoc:ns:yahoo:spec");
+ DocumentId orderDoc1("orderdoc(31,19):ns:13:1268182861:foo");
+ DocumentId orderDoc2("orderdoc(31,19):ns:13:1205110861:foo");
+ DocumentId orderDoc3("orderdoc(31,19):ns:13:1205715661:foo");
+ DocumentId orderDoc4("orderdoc(4,0):ns:13:2:foo");
+ DocumentId orderDoc5("orderdoc(4,0):ns:13:4:foo");
+ DocumentId orderDoc6("orderdoc(4,0):ns:13:11:foo");
+
+ BucketId docBucket1(factory.getBucketId(doc1));
+ BucketId docBucket2(factory.getBucketId(doc2));
+ BucketId docBucket3(factory.getBucketId(doc3));
+ BucketId userDocBucket1(factory.getBucketId(userDoc1));
+ BucketId userDocBucket2(factory.getBucketId(userDoc2));
+ BucketId userDocBucket3(factory.getBucketId(userDoc3));
+ BucketId groupDocBucket1(factory.getBucketId(groupDoc1));
+ BucketId groupDocBucket2(factory.getBucketId(groupDoc2));
+ BucketId groupDocBucket3(factory.getBucketId(groupDoc3));
+ BucketId orderDocBucket1(factory.getBucketId(orderDoc1));
+ BucketId orderDocBucket2(factory.getBucketId(orderDoc2));
+ BucketId orderDocBucket3(factory.getBucketId(orderDoc3));
+ BucketId orderDocBucket4(factory.getBucketId(orderDoc4));
+ BucketId orderDocBucket5(factory.getBucketId(orderDoc5));
+ BucketId orderDocBucket6(factory.getBucketId(orderDoc6));
+
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe99703f200000012ull), Hex(userDocBucket1.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xebfa518a00000012ull), Hex(userDocBucket2.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xeac1850800000013ull), Hex(userDocBucket3.getRawId()));
+
+ CPPUNIT_ASSERT_EQUAL(Hex(0xeae764e90000000dull), Hex(orderDocBucket1.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xeacb85f10000000dull), Hex(orderDocBucket2.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xea68ddf10000000dull), Hex(orderDocBucket3.getRawId()));
+
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe87526540000000dull), Hex(orderDocBucket4.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xea59f8f20000000dull), Hex(orderDocBucket5.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe9eb703d0000000dull), Hex(orderDocBucket6.getRawId()));
+
+ userDocBucket1.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000000012ull), Hex(userDocBucket1.getId()));
+ userDocBucket2.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000000012ull), Hex(userDocBucket2.getId()));
+ userDocBucket3.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000000013ull), Hex(userDocBucket3.getId()));
+
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe90ce4b09a1acd50ull), Hex(groupDocBucket1.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe9cedaa49a1acd50ull), Hex(groupDocBucket2.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe8cdb18bafe81f24ull), Hex(groupDocBucket3.getRawId()));
+
+ groupDocBucket1.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x400000000000cd50ull), Hex(groupDocBucket1.getId()));
+ groupDocBucket2.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x400000000000cd50ull), Hex(groupDocBucket2.getId()));
+ groupDocBucket3.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000001f24ull), Hex(groupDocBucket3.getId()));
+
+ CPPUNIT_ASSERT_EQUAL(Hex(0xe980c9abd5fd8d11ull), Hex(docBucket1.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xeafe870c5f9c37b9ull), Hex(docBucket2.getRawId()));
+ CPPUNIT_ASSERT_EQUAL(Hex(0xeaebe9473ecbcd69ull), Hex(docBucket3.getRawId()));
+
+ docBucket1.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4000000000008d11ull), Hex(docBucket1.getId()));
+ docBucket2.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x40000000000037b9ull), Hex(docBucket2.getId()));
+ docBucket3.setUsedBits(16);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x400000000000cd69ull), Hex(docBucket3.getId()));
+}
+
+void BucketTest::testBucketSerialization()
+{
+ BucketIdFactory factory;
+ DocumentId doc(DocIdString("ns", "spec"));
+ BucketId bucket(factory.getBucketId(doc));
+
+ std::ostringstream ost;
+ ost << bucket.getRawId();
+ CPPUNIT_ASSERT_EQUAL(std::string("16825669947722927377"),
+ ost.str());
+
+ BucketId::Type id;
+ std::istringstream ist(ost.str());
+ ist >> id;
+ BucketId bucket2(id);
+
+ CPPUNIT_ASSERT_EQUAL(bucket, bucket2);
+}
+
+void BucketTest::testReverseBucket()
+{
+ {
+ BucketId id(0x3000000000000012ull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x480000000000000cull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+
+ {
+ BucketId id(0x4000000000000012ull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x4800000000000010ull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+
+ {
+ BucketId id(0x600000000000ffffull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0xffff000000000018ull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+
+ {
+ BucketId id(0x540000000001ffffull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0xffff800000000015ull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+
+ {
+ BucketId id(0xa80000000003ffffull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0xffffc0000000002aull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+
+ {
+ BucketId id(0xbc0000000007ffffull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0xffffe0000000002full), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+ {
+ BucketId id(0xcc0000000002ffffull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0xffff400000000033ull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+ {
+ BucketId id(0xebffffffffffffffull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0xfffffffffffffffaull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+ {
+ BucketId id(0xeaaaaaaaaaaaaaaaull);
+ CPPUNIT_ASSERT_EQUAL(Hex(0x555555555555557aull), Hex(id.toKey()));
+ CPPUNIT_ASSERT_EQUAL(Hex(id.getId()), Hex(document::BucketId::keyToBucketId(id.stripUnused().toKey())));
+ }
+}
+
+void BucketTest::testContains() {
+ BucketId id(18, 0x123456789ULL);
+ CPPUNIT_ASSERT(id.contains(BucketId(20, 0x123456789ULL)));
+ CPPUNIT_ASSERT(id.contains(BucketId(18, 0x888f56789ULL)));
+ CPPUNIT_ASSERT(id.contains(BucketId(24, 0x888456789ULL)));
+ CPPUNIT_ASSERT(!id.contains(BucketId(24, 0x888886789ULL)));
+ CPPUNIT_ASSERT(!id.contains(BucketId(16, 0x123456789ULL)));
+}
+
+} // document
diff --git a/document/src/tests/cpp-globalidbucketids.txt b/document/src/tests/cpp-globalidbucketids.txt
new file mode 100644
index 00000000000..33dca454d3b
--- /dev/null
+++ b/document/src/tests/cpp-globalidbucketids.txt
@@ -0,0 +1,38 @@
+doc:ns:specific - gid(0x2c01a21163cb7d0ce85fddd6) - BucketId(0xeadd5fe811a2012c)
+doc:another:specific - gid(0xcd2ba528d1135e40605ce372) - BucketId(0xeae35c6028a52bcd)
+doc:ns:another - gid(0x1d5324270601e76a7a1f58b7) - BucketId(0xeb581f7a2724531d)
+userdoc:ns:100:specific - gid(0x64000000e7bd2a0d13c71935) - BucketId(0xe919c71300000064)
+userdoc:np:100:another - gid(0x64000000102745c9ceec3238) - BucketId(0xe832ecce00000064)
+userdoc:ns:101:specific - gid(0x6500000026238735e9cd9bb8) - BucketId(0xe89bcde900000065)
+groupdoc:ns:agroup:specific - gid(0x3272feb9fb729a50be795117) - BucketId(0xeb5179beb9fe7232)
+groupdoc:np:agroup:another - gid(0x3272feb93ff5e65f3cf4aba9) - BucketId(0xe9abf43cb9fe7232)
+groupdoc:ns:another:specific - gid(0xb32d73e51ae730d4fa104396) - BucketId(0xea4310fae5732db3)
+doc:ns:0 - gid(0x87817cf2f6d05976505e74be) - BucketId(0xea745e50f27c8187)
+doc:ns:1 - gid(0x911a03b253cb5b1c283b2024) - BucketId(0xe8203b28b2031a91)
+doc:ns:2 - gid(0x1d82e56be428cda364ed6875) - BucketId(0xe968ed646be5821d)
+doc:ns:3 - gid(0xf8d223e4e68e0d571b95a6d8) - BucketId(0xe8a6951be423d2f8)
+doc:ns:4 - gid(0xdab024c2e41747dc92a1b8e3) - BucketId(0xebb8a192c224b0da)
+doc:ns:5 - gid(0xdb9023e3080c94901734f948) - BucketId(0xe8f93417e32390db)
+doc:ns:6 - gid(0xbd84ae30c63f7fdef9edbf69) - BucketId(0xe9bfedf930ae84bd)
+doc:ns:7 - gid(0x463977cf070d06e204b8166f) - BucketId(0xeb16b804cf773946)
+doc:ns:8 - gid(0x46cf1241cec694a0c07af5e2) - BucketId(0xeaf57ac04112cf46)
+doc:ns:9 - gid(0x909b77593ef7b309a06d22ef) - BucketId(0xeb226da059779b90)
+doc:ns:10 - gid(0x4888f0b3796031003a8840fb) - BucketId(0xeb40883ab3f08848)
+doc:ns:11 - gid(0x18fae4cbc359765470c10fcd) - BucketId(0xe90fc170cbe4fa18)
+doc:ns:12 - gid(0xc902059d4ac551616aea5431) - BucketId(0xe954ea6a9d0502c9)
+doc:ns:13 - gid(0x639b6aa505018e29ca4e342d) - BucketId(0xe9344ecaa56a9b63)
+doc:ns:14 - gid(0x5fa1f02be952a9d0811e8ddd) - BucketId(0xe98d1e812bf0a15f)
+doc:ns:15 - gid(0xfbc851f81830365c84229c49) - BucketId(0xe99c2284f851c8fb)
+doc:ns:16 - gid(0x06313edc8072f4495329fb5b) - BucketId(0xebfb2953dc3e3106)
+doc:ns:17 - gid(0x3d9df3e147de3a5fbd5664e4) - BucketId(0xe86456bde1f39d3d)
+doc:ns:18 - gid(0x75512f41a8dbde1c8f86a97d) - BucketId(0xe9a9868f412f5175)
+doc:ns:19 - gid(0x15ae3aa9919b2e1e46d84ada) - BucketId(0xea4ad846a93aae15)
+id:ns:type::specific - gid(0x2067d966823ebdfb79537da1) - BucketId(0xe97d537966d96720)
+id:another:type::specific - gid(0x2067d9664809eb39a3b72218) - BucketId(0xe822b7a366d96720)
+id:ns:type::another - gid(0xb32d73e53e034c30c58a01ff) - BucketId(0xeb018ac5e5732db3)
+id:ns:type:n=100:specific - gid(0x64000000dd4c4b431749f95f) - BucketId(0xebf9491700000064)
+id:np:type:n=100:another - gid(0x6400000031359e4a70e670a8) - BucketId(0xe870e67000000064)
+id:ns:type:n=101:specific - gid(0x6500000083f2836ce5883df1) - BucketId(0xe93d88e500000065)
+id:ns:type:g=agroup:specific - gid(0x3272feb90fe348ef910b064a) - BucketId(0xea060b91b9fe7232)
+id:np:type:g=agroup:another - gid(0x3272feb96d53d40047cb4f23) - BucketId(0xeb4fcb47b9fe7232)
+id:ns:type:g=another:specific - gid(0xb32d73e58bd215d1451922cc) - BucketId(0xe8221945e5732db3)
diff --git a/document/src/tests/data/compressed.cfg b/document/src/tests/data/compressed.cfg
new file mode 100644
index 00000000000..740e56c47ac
--- /dev/null
+++ b/document/src/tests/data/compressed.cfg
@@ -0,0 +1,139 @@
+enablecompression true
+datatype[11]
+datatype[0].id -260050933
+datatype[0].arraytype[0]
+datatype[0].weightedsettype[0]
+datatype[0].structtype[1]
+datatype[0].structtype[0].name serializetest.header
+datatype[0].structtype[0].version 0
+datatype[0].structtype[0].compresstype LZ4
+datatype[0].structtype[0].compresslevel 9
+datatype[0].structtype[0].compressthreshold 99
+datatype[0].structtype[0].compressminsize 0
+datatype[0].structtype[0].field[4]
+datatype[0].structtype[0].field[0].name floatfield
+datatype[0].structtype[0].field[0].id[0]
+datatype[0].structtype[0].field[0].datatype 1
+datatype[0].structtype[0].field[1].name stringfield
+datatype[0].structtype[0].field[1].id[0]
+datatype[0].structtype[0].field[1].datatype 2
+datatype[0].structtype[0].field[2].name longfield
+datatype[0].structtype[0].field[2].id[0]
+datatype[0].structtype[0].field[2].datatype 4
+datatype[0].structtype[0].field[3].name urifield
+datatype[0].structtype[0].field[3].id[0]
+datatype[0].structtype[0].field[3].datatype 10
+datatype[0].documenttype[0]
+datatype[1].id 1001
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 1
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 2001
+datatype[2].arraytype[1]
+datatype[2].arraytype[0].datatype 1001
+datatype[2].weightedsettype[0]
+datatype[2].structtype[0]
+datatype[2].documenttype[0]
+datatype[3].id 1026122976
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].name serializetest.body
+datatype[3].structtype[0].version 0
+datatype[3].structtype[0].compresstype LZ4
+datatype[3].structtype[0].compresslevel 9
+datatype[3].structtype[0].compressthreshold 99
+datatype[3].structtype[0].compressminsize 0
+datatype[3].structtype[0].field[8]
+datatype[3].structtype[0].field[0].name intfield
+datatype[3].structtype[0].field[0].id[0]
+datatype[3].structtype[0].field[0].datatype 0
+datatype[3].structtype[0].field[1].name rawfield
+datatype[3].structtype[0].field[1].id[0]
+datatype[3].structtype[0].field[1].datatype 3
+datatype[3].structtype[0].field[2].name doublefield
+datatype[3].structtype[0].field[2].id[0]
+datatype[3].structtype[0].field[2].datatype 5
+datatype[3].structtype[0].field[3].name contentfield
+datatype[3].structtype[0].field[3].id[0]
+datatype[3].structtype[0].field[3].datatype 2
+datatype[3].structtype[0].field[4].name bytefield
+datatype[3].structtype[0].field[4].id[0]
+datatype[3].structtype[0].field[4].datatype 16
+datatype[3].structtype[0].field[5].name arrayoffloatfield
+datatype[3].structtype[0].field[5].id[0]
+datatype[3].structtype[0].field[5].datatype 1001
+datatype[3].structtype[0].field[6].name arrayofarrayoffloatfield
+datatype[3].structtype[0].field[6].id[0]
+datatype[3].structtype[0].field[6].datatype 2001
+datatype[3].structtype[0].field[7].name docfield
+datatype[3].structtype[0].field[7].id[0]
+datatype[3].structtype[0].field[7].datatype 8
+datatype[3].documenttype[0]
+datatype[4].id 1306012852
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[0]
+datatype[4].documenttype[1]
+datatype[4].documenttype[0].name serializetest
+datatype[4].documenttype[0].version 0
+datatype[4].documenttype[0].inherits[0]
+datatype[4].documenttype[0].headerstruct -260050933
+datatype[4].documenttype[0].bodystruct 1026122976
+datatype[5].id -1686125086
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].name docindoc.header
+datatype[5].structtype[0].version 0
+datatype[5].structtype[0].field[0]
+datatype[5].documenttype[0]
+datatype[6].id 2030224503
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[0]
+datatype[6].structtype[1]
+datatype[6].structtype[0].name docindoc.body
+datatype[6].structtype[0].version 0
+datatype[6].structtype[0].field[0]
+datatype[6].documenttype[0]
+datatype[7].id 1447635645
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[0]
+datatype[7].documenttype[1]
+datatype[7].documenttype[0].name docindoc
+datatype[7].documenttype[0].version 0
+datatype[7].documenttype[0].inherits[0]
+datatype[7].documenttype[0].headerstruct -1686125086
+datatype[7].documenttype[0].bodystruct 2030224503
+datatype[8].id -792441727
+datatype[8].arraytype[0]
+datatype[8].weightedsettype[0]
+datatype[8].structtype[1]
+datatype[8].structtype[0].name mapindocindoc.header
+datatype[8].structtype[0].version 0
+datatype[8].structtype[0].field[0]
+datatype[8].documenttype[0]
+datatype[9].id 145123030
+datatype[9].arraytype[0]
+datatype[9].weightedsettype[0]
+datatype[9].structtype[1]
+datatype[9].structtype[0].name mapindocindoc.body
+datatype[9].structtype[0].version 0
+datatype[9].structtype[0].field[1]
+datatype[9].structtype[0].field[0].name stringinmap
+datatype[9].structtype[0].field[0].id[0]
+datatype[9].structtype[0].field[0].datatype 2
+datatype[9].documenttype[0]
+datatype[10].id -1456256770
+datatype[10].arraytype[0]
+datatype[10].weightedsettype[0]
+datatype[10].structtype[0]
+datatype[10].documenttype[1]
+datatype[10].documenttype[0].name mapindocindoc
+datatype[10].documenttype[0].version 0
+datatype[10].documenttype[0].inherits[0]
+datatype[10].documenttype[0].headerstruct -792441727
+datatype[10].documenttype[0].bodystruct 145123030
diff --git a/document/src/tests/data/cppdocument.cfg b/document/src/tests/data/cppdocument.cfg
new file mode 100644
index 00000000000..e4fcb5cb5bf
--- /dev/null
+++ b/document/src/tests/data/cppdocument.cfg
@@ -0,0 +1,131 @@
+enablecompression false
+datatype[11]
+datatype[0].id -260050933
+datatype[0].arraytype[0]
+datatype[0].weightedsettype[0]
+datatype[0].structtype[1]
+datatype[0].structtype[0].name serializetest.header
+datatype[0].structtype[0].version 0
+datatype[0].structtype[0].field[4]
+datatype[0].structtype[0].field[0].name floatfield
+datatype[0].structtype[0].field[0].id[0]
+datatype[0].structtype[0].field[0].datatype 1
+datatype[0].structtype[0].field[1].name stringfield
+datatype[0].structtype[0].field[1].id[0]
+datatype[0].structtype[0].field[1].datatype 2
+datatype[0].structtype[0].field[2].name longfield
+datatype[0].structtype[0].field[2].id[0]
+datatype[0].structtype[0].field[2].datatype 4
+datatype[0].structtype[0].field[3].name urifield
+datatype[0].structtype[0].field[3].id[0]
+datatype[0].structtype[0].field[3].datatype 10
+datatype[0].documenttype[0]
+datatype[1].id 1001
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 1
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 2001
+datatype[2].arraytype[1]
+datatype[2].arraytype[0].datatype 1001
+datatype[2].weightedsettype[0]
+datatype[2].structtype[0]
+datatype[2].documenttype[0]
+datatype[3].id 1026122976
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].name serializetest.body
+datatype[3].structtype[0].version 0
+datatype[3].structtype[0].field[8]
+datatype[3].structtype[0].field[0].name intfield
+datatype[3].structtype[0].field[0].id[0]
+datatype[3].structtype[0].field[0].datatype 0
+datatype[3].structtype[0].field[1].name rawfield
+datatype[3].structtype[0].field[1].id[0]
+datatype[3].structtype[0].field[1].datatype 3
+datatype[3].structtype[0].field[2].name doublefield
+datatype[3].structtype[0].field[2].id[0]
+datatype[3].structtype[0].field[2].datatype 5
+datatype[3].structtype[0].field[3].name contentfield
+datatype[3].structtype[0].field[3].id[0]
+datatype[3].structtype[0].field[3].datatype 2
+datatype[3].structtype[0].field[4].name bytefield
+datatype[3].structtype[0].field[4].id[0]
+datatype[3].structtype[0].field[4].datatype 16
+datatype[3].structtype[0].field[5].name arrayoffloatfield
+datatype[3].structtype[0].field[5].id[0]
+datatype[3].structtype[0].field[5].datatype 1001
+datatype[3].structtype[0].field[6].name arrayofarrayoffloatfield
+datatype[3].structtype[0].field[6].id[0]
+datatype[3].structtype[0].field[6].datatype 2001
+datatype[3].structtype[0].field[7].name docfield
+datatype[3].structtype[0].field[7].id[0]
+datatype[3].structtype[0].field[7].datatype 8
+datatype[3].documenttype[0]
+datatype[4].id 1306012852
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[0]
+datatype[4].documenttype[1]
+datatype[4].documenttype[0].name serializetest
+datatype[4].documenttype[0].version 0
+datatype[4].documenttype[0].inherits[0]
+datatype[4].documenttype[0].headerstruct -260050933
+datatype[4].documenttype[0].bodystruct 1026122976
+datatype[5].id -1686125086
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].name docindoc.header
+datatype[5].structtype[0].version 0
+datatype[5].structtype[0].field[0]
+datatype[5].documenttype[0]
+datatype[6].id 2030224503
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[0]
+datatype[6].structtype[1]
+datatype[6].structtype[0].name docindoc.body
+datatype[6].structtype[0].version 0
+datatype[6].structtype[0].field[0]
+datatype[6].documenttype[0]
+datatype[7].id 1447635645
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[0]
+datatype[7].documenttype[1]
+datatype[7].documenttype[0].name docindoc
+datatype[7].documenttype[0].version 0
+datatype[7].documenttype[0].inherits[0]
+datatype[7].documenttype[0].headerstruct -1686125086
+datatype[7].documenttype[0].bodystruct 2030224503
+datatype[8].id -792441727
+datatype[8].arraytype[0]
+datatype[8].weightedsettype[0]
+datatype[8].structtype[1]
+datatype[8].structtype[0].name mapindocindoc.header
+datatype[8].structtype[0].version 0
+datatype[8].structtype[0].field[0]
+datatype[8].documenttype[0]
+datatype[9].id 145123030
+datatype[9].arraytype[0]
+datatype[9].weightedsettype[0]
+datatype[9].structtype[1]
+datatype[9].structtype[0].name mapindocindoc.body
+datatype[9].structtype[0].version 0
+datatype[9].structtype[0].field[1]
+datatype[9].structtype[0].field[0].name stringinmap
+datatype[9].structtype[0].field[0].id[0]
+datatype[9].structtype[0].field[0].datatype 2
+datatype[9].documenttype[0]
+datatype[10].id -1456256770
+datatype[10].arraytype[0]
+datatype[10].weightedsettype[0]
+datatype[10].structtype[0]
+datatype[10].documenttype[1]
+datatype[10].documenttype[0].name mapindocindoc
+datatype[10].documenttype[0].version 0
+datatype[10].documenttype[0].inherits[0]
+datatype[10].documenttype[0].headerstruct -792441727
+datatype[10].documenttype[0].bodystruct 145123030
diff --git a/document/src/tests/data/crossplatform-java-cpp-doctypes.cfg b/document/src/tests/data/crossplatform-java-cpp-doctypes.cfg
new file mode 100644
index 00000000000..6d33df468dd
--- /dev/null
+++ b/document/src/tests/data/crossplatform-java-cpp-doctypes.cfg
@@ -0,0 +1,202 @@
+enablecompression false
+documenttype[2]
+documenttype[0].id 1306012852
+documenttype[0].name "serializetest"
+documenttype[0].version 0
+documenttype[0].headerstruct -260050933
+documenttype[0].bodystruct 1026122976
+documenttype[0].inherits[0]
+documenttype[0].datatype[6]
+documenttype[0].datatype[0].id 9999
+documenttype[0].datatype[0].type MAP
+documenttype[0].datatype[0].array.element.id 0
+documenttype[0].datatype[0].map.key.id 2
+documenttype[0].datatype[0].map.value.id 2
+documenttype[0].datatype[0].wset.key.id 0
+documenttype[0].datatype[0].wset.createifnonexistent false
+documenttype[0].datatype[0].wset.removeifzero false
+documenttype[0].datatype[0].annotationref.annotation.id 0
+documenttype[0].datatype[0].sstruct.name ""
+documenttype[0].datatype[0].sstruct.version 0
+documenttype[0].datatype[0].sstruct.compression.type NONE
+documenttype[0].datatype[0].sstruct.compression.level 0
+documenttype[0].datatype[0].sstruct.compression.threshold 95
+documenttype[0].datatype[0].sstruct.compression.minsize 200
+documenttype[0].datatype[0].sstruct.field[0]
+documenttype[0].datatype[1].id 1001
+documenttype[0].datatype[1].type ARRAY
+documenttype[0].datatype[1].array.element.id 1
+documenttype[0].datatype[1].map.key.id 0
+documenttype[0].datatype[1].map.value.id 0
+documenttype[0].datatype[1].wset.key.id 0
+documenttype[0].datatype[1].wset.createifnonexistent false
+documenttype[0].datatype[1].wset.removeifzero false
+documenttype[0].datatype[1].annotationref.annotation.id 0
+documenttype[0].datatype[1].sstruct.name ""
+documenttype[0].datatype[1].sstruct.version 0
+documenttype[0].datatype[1].sstruct.compression.type NONE
+documenttype[0].datatype[1].sstruct.compression.level 0
+documenttype[0].datatype[1].sstruct.compression.threshold 95
+documenttype[0].datatype[1].sstruct.compression.minsize 200
+documenttype[0].datatype[1].sstruct.field[0]
+documenttype[0].datatype[2].id 2001
+documenttype[0].datatype[2].type ARRAY
+documenttype[0].datatype[2].array.element.id 1001
+documenttype[0].datatype[2].map.key.id 0
+documenttype[0].datatype[2].map.value.id 0
+documenttype[0].datatype[2].wset.key.id 0
+documenttype[0].datatype[2].wset.createifnonexistent false
+documenttype[0].datatype[2].wset.removeifzero false
+documenttype[0].datatype[2].annotationref.annotation.id 0
+documenttype[0].datatype[2].sstruct.name ""
+documenttype[0].datatype[2].sstruct.version 0
+documenttype[0].datatype[2].sstruct.compression.type NONE
+documenttype[0].datatype[2].sstruct.compression.level 0
+documenttype[0].datatype[2].sstruct.compression.threshold 95
+documenttype[0].datatype[2].sstruct.compression.minsize 200
+documenttype[0].datatype[2].sstruct.field[0]
+documenttype[0].datatype[3].id 437829
+documenttype[0].datatype[3].type WSET
+documenttype[0].datatype[3].array.element.id 0
+documenttype[0].datatype[3].map.key.id 0
+documenttype[0].datatype[3].map.value.id 0
+documenttype[0].datatype[3].wset.key.id 2
+documenttype[0].datatype[3].wset.createifnonexistent false
+documenttype[0].datatype[3].wset.removeifzero false
+documenttype[0].datatype[3].annotationref.annotation.id 0
+documenttype[0].datatype[3].sstruct.name ""
+documenttype[0].datatype[3].sstruct.version 0
+documenttype[0].datatype[3].sstruct.compression.type NONE
+documenttype[0].datatype[3].sstruct.compression.level 0
+documenttype[0].datatype[3].sstruct.compression.threshold 95
+documenttype[0].datatype[3].sstruct.compression.minsize 200
+documenttype[0].datatype[3].sstruct.field[0]
+documenttype[0].datatype[4].id -260050933
+documenttype[0].datatype[4].type STRUCT
+documenttype[0].datatype[4].array.element.id 0
+documenttype[0].datatype[4].map.key.id 0
+documenttype[0].datatype[4].map.value.id 0
+documenttype[0].datatype[4].wset.key.id 0
+documenttype[0].datatype[4].wset.createifnonexistent false
+documenttype[0].datatype[4].wset.removeifzero false
+documenttype[0].datatype[4].annotationref.annotation.id 0
+documenttype[0].datatype[4].sstruct.name "serializetest.header"
+documenttype[0].datatype[4].sstruct.version 0
+documenttype[0].datatype[4].sstruct.compression.type NONE
+documenttype[0].datatype[4].sstruct.compression.level 0
+documenttype[0].datatype[4].sstruct.compression.threshold 90
+documenttype[0].datatype[4].sstruct.compression.minsize 0
+documenttype[0].datatype[4].sstruct.field[4]
+documenttype[0].datatype[4].sstruct.field[0].name "floatfield"
+documenttype[0].datatype[4].sstruct.field[0].id 1055999199
+documenttype[0].datatype[4].sstruct.field[0].id_v6 657399019
+documenttype[0].datatype[4].sstruct.field[0].datatype 1
+documenttype[0].datatype[4].sstruct.field[1].name "longfield"
+documenttype[0].datatype[4].sstruct.field[1].id 1589309697
+documenttype[0].datatype[4].sstruct.field[1].id_v6 477609536
+documenttype[0].datatype[4].sstruct.field[1].datatype 4
+documenttype[0].datatype[4].sstruct.field[2].name "stringfield"
+documenttype[0].datatype[4].sstruct.field[2].id 1182460484
+documenttype[0].datatype[4].sstruct.field[2].id_v6 779638844
+documenttype[0].datatype[4].sstruct.field[2].datatype 2
+documenttype[0].datatype[4].sstruct.field[3].name "urifield"
+documenttype[0].datatype[4].sstruct.field[3].id 628407450
+documenttype[0].datatype[4].sstruct.field[3].id_v6 756285986
+documenttype[0].datatype[4].sstruct.field[3].datatype 10
+documenttype[0].datatype[5].id 1026122976
+documenttype[0].datatype[5].type STRUCT
+documenttype[0].datatype[5].array.element.id 0
+documenttype[0].datatype[5].map.key.id 0
+documenttype[0].datatype[5].map.value.id 0
+documenttype[0].datatype[5].wset.key.id 0
+documenttype[0].datatype[5].wset.createifnonexistent false
+documenttype[0].datatype[5].wset.removeifzero false
+documenttype[0].datatype[5].annotationref.annotation.id 0
+documenttype[0].datatype[5].sstruct.name "serializetest.body"
+documenttype[0].datatype[5].sstruct.version 0
+documenttype[0].datatype[5].sstruct.compression.type NONE
+documenttype[0].datatype[5].sstruct.compression.level 0
+documenttype[0].datatype[5].sstruct.compression.threshold 90
+documenttype[0].datatype[5].sstruct.compression.minsize 0
+documenttype[0].datatype[5].sstruct.field[9]
+documenttype[0].datatype[5].sstruct.field[0].name "arrayofarrayoffloatfield"
+documenttype[0].datatype[5].sstruct.field[0].id 2061447601
+documenttype[0].datatype[5].sstruct.field[0].id_v6 903107040
+documenttype[0].datatype[5].sstruct.field[0].datatype 2001
+documenttype[0].datatype[5].sstruct.field[1].name "arrayoffloatfield"
+documenttype[0].datatype[5].sstruct.field[1].id 1870932758
+documenttype[0].datatype[5].sstruct.field[1].id_v6 86189529
+documenttype[0].datatype[5].sstruct.field[1].datatype 1001
+documenttype[0].datatype[5].sstruct.field[2].name "bytefield"
+documenttype[0].datatype[5].sstruct.field[2].id 1924064342
+documenttype[0].datatype[5].sstruct.field[2].id_v6 2050488857
+documenttype[0].datatype[5].sstruct.field[2].datatype 16
+documenttype[0].datatype[5].sstruct.field[3].name "docfield"
+documenttype[0].datatype[5].sstruct.field[3].id 728739268
+documenttype[0].datatype[5].sstruct.field[3].id_v6 1725514984
+documenttype[0].datatype[5].sstruct.field[3].datatype 8
+documenttype[0].datatype[5].sstruct.field[4].name "doublefield"
+documenttype[0].datatype[5].sstruct.field[4].id 421343958
+documenttype[0].datatype[5].sstruct.field[4].id_v6 944141865
+documenttype[0].datatype[5].sstruct.field[4].datatype 5
+documenttype[0].datatype[5].sstruct.field[5].name "intfield"
+documenttype[0].datatype[5].sstruct.field[5].id 435380425
+documenttype[0].datatype[5].sstruct.field[5].id_v6 545156740
+documenttype[0].datatype[5].sstruct.field[5].datatype 0
+documenttype[0].datatype[5].sstruct.field[6].name "mapfield"
+documenttype[0].datatype[5].sstruct.field[6].id 162466023
+documenttype[0].datatype[5].sstruct.field[6].id_v6 1028589263
+documenttype[0].datatype[5].sstruct.field[6].datatype 9999
+documenttype[0].datatype[5].sstruct.field[7].name "rawfield"
+documenttype[0].datatype[5].sstruct.field[7].id 172982133
+documenttype[0].datatype[5].sstruct.field[7].id_v6 84029972
+documenttype[0].datatype[5].sstruct.field[7].datatype 3
+documenttype[0].datatype[5].sstruct.field[8].name "wsfield"
+documenttype[0].datatype[5].sstruct.field[8].id 1683714185
+documenttype[0].datatype[5].sstruct.field[8].id_v6 2988081
+documenttype[0].datatype[5].sstruct.field[8].datatype 437829
+documenttype[0].annotationtype[0]
+documenttype[1].id 1447635645
+documenttype[1].name "docindoc"
+documenttype[1].version 0
+documenttype[1].headerstruct -1686125086
+documenttype[1].bodystruct 2030224503
+documenttype[1].inherits[0]
+documenttype[1].datatype[2]
+documenttype[1].datatype[0].id -1686125086
+documenttype[1].datatype[0].type STRUCT
+documenttype[1].datatype[0].array.element.id 0
+documenttype[1].datatype[0].map.key.id 0
+documenttype[1].datatype[0].map.value.id 0
+documenttype[1].datatype[0].wset.key.id 0
+documenttype[1].datatype[0].wset.createifnonexistent false
+documenttype[1].datatype[0].wset.removeifzero false
+documenttype[1].datatype[0].annotationref.annotation.id 0
+documenttype[1].datatype[0].sstruct.name "docindoc.header"
+documenttype[1].datatype[0].sstruct.version 0
+documenttype[1].datatype[0].sstruct.compression.type NONE
+documenttype[1].datatype[0].sstruct.compression.level 0
+documenttype[1].datatype[0].sstruct.compression.threshold 90
+documenttype[1].datatype[0].sstruct.compression.minsize 0
+documenttype[1].datatype[0].sstruct.field[0]
+documenttype[1].datatype[1].id 2030224503
+documenttype[1].datatype[1].type STRUCT
+documenttype[1].datatype[1].array.element.id 0
+documenttype[1].datatype[1].map.key.id 0
+documenttype[1].datatype[1].map.value.id 0
+documenttype[1].datatype[1].wset.key.id 0
+documenttype[1].datatype[1].wset.createifnonexistent false
+documenttype[1].datatype[1].wset.removeifzero false
+documenttype[1].datatype[1].annotationref.annotation.id 0
+documenttype[1].datatype[1].sstruct.name "docindoc.body"
+documenttype[1].datatype[1].sstruct.version 0
+documenttype[1].datatype[1].sstruct.compression.type NONE
+documenttype[1].datatype[1].sstruct.compression.level 0
+documenttype[1].datatype[1].sstruct.compression.threshold 90
+documenttype[1].datatype[1].sstruct.compression.minsize 0
+documenttype[1].datatype[1].sstruct.field[1]
+documenttype[1].datatype[1].sstruct.field[0].name "stringindocfield"
+documenttype[1].datatype[1].sstruct.field[0].id 102054923
+documenttype[1].datatype[1].sstruct.field[0].id_v6 917294979
+documenttype[1].datatype[1].sstruct.field[0].datatype 2
+documenttype[1].annotationtype[0]
diff --git a/document/src/tests/data/crossplatform-java-cpp-document.cfg b/document/src/tests/data/crossplatform-java-cpp-document.cfg
new file mode 100644
index 00000000000..f12dae77fc0
--- /dev/null
+++ b/document/src/tests/data/crossplatform-java-cpp-document.cfg
@@ -0,0 +1,128 @@
+enablecompression false
+datatype[10]
+datatype[0].id 9999
+datatype[0].arraytype[0]
+datatype[0].maptype[1]
+datatype[0].maptype[0].keytype 2
+datatype[0].maptype[0].valtype 2
+datatype[0].weightedsettype[0]
+datatype[0].structtype[0]
+datatype[0].documenttype[0]
+datatype[1].id 1001
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 1
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 2001
+datatype[2].arraytype[1]
+datatype[2].arraytype[0].datatype 1001
+datatype[2].weightedsettype[0]
+datatype[2].structtype[0]
+datatype[2].documenttype[0]
+datatype[3].id 437829
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[1]
+datatype[3].weightedsettype[0].datatype 2
+datatype[3].weightedsettype[0].createifnonexistant false
+datatype[3].weightedsettype[0].removeifzero false
+datatype[3].structtype[0]
+datatype[3].documenttype[0]
+datatype[4].id -1686125086
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[1]
+datatype[4].structtype[0].name docindoc.header
+datatype[4].structtype[0].version 0
+datatype[4].structtype[0].field[0]
+datatype[4].documenttype[0]
+datatype[5].id 2030224503
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].name docindoc.body
+datatype[5].structtype[0].version 0
+datatype[5].structtype[0].field[1]
+datatype[5].structtype[0].field[0].name stringindocfield
+datatype[5].structtype[0].field[0].id[0]
+datatype[5].structtype[0].field[0].datatype 2
+datatype[5].documenttype[0]
+datatype[6].id 1447635645
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[0]
+datatype[6].structtype[0]
+datatype[6].documenttype[1]
+datatype[6].documenttype[0].name docindoc
+datatype[6].documenttype[0].version 0
+datatype[6].documenttype[0].inherits[0]
+datatype[6].documenttype[0].headerstruct -1686125086
+datatype[6].documenttype[0].bodystruct 2030224503
+datatype[7].id -260050933
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[1]
+datatype[7].structtype[0].name serializetest.header
+datatype[7].structtype[0].version 0
+datatype[7].structtype[0].field[4]
+datatype[7].structtype[0].field[0].name floatfield
+datatype[7].structtype[0].field[0].id[0]
+datatype[7].structtype[0].field[0].datatype 1
+datatype[7].structtype[0].field[1].name stringfield
+datatype[7].structtype[0].field[1].id[0]
+datatype[7].structtype[0].field[1].datatype 2
+datatype[7].structtype[0].field[2].name longfield
+datatype[7].structtype[0].field[2].id[0]
+datatype[7].structtype[0].field[2].datatype 4
+datatype[7].structtype[0].field[3].name urifield
+datatype[7].structtype[0].field[3].id[0]
+datatype[7].structtype[0].field[3].datatype 10
+datatype[7].documenttype[0]
+datatype[8].id 1026122976
+datatype[8].arraytype[0]
+datatype[8].weightedsettype[0]
+datatype[8].structtype[1]
+datatype[8].structtype[0].name serializetest.body
+datatype[8].structtype[0].version 0
+datatype[8].structtype[0].field[10]
+datatype[8].structtype[0].field[0].name intfield
+datatype[8].structtype[0].field[0].id[0]
+datatype[8].structtype[0].field[0].datatype 0
+datatype[8].structtype[0].field[1].name rawfield
+datatype[8].structtype[0].field[1].id[0]
+datatype[8].structtype[0].field[1].datatype 3
+datatype[8].structtype[0].field[2].name doublefield
+datatype[8].structtype[0].field[2].id[0]
+datatype[8].structtype[0].field[2].datatype 5
+datatype[8].structtype[0].field[3].name contentfield
+datatype[8].structtype[0].field[3].id[0]
+datatype[8].structtype[0].field[3].datatype 2
+datatype[8].structtype[0].field[4].name bytefield
+datatype[8].structtype[0].field[4].id[0]
+datatype[8].structtype[0].field[4].datatype 16
+datatype[8].structtype[0].field[5].name arrayoffloatfield
+datatype[8].structtype[0].field[5].id[0]
+datatype[8].structtype[0].field[5].datatype 1001
+datatype[8].structtype[0].field[6].name arrayofarrayoffloatfield
+datatype[8].structtype[0].field[6].id[0]
+datatype[8].structtype[0].field[6].datatype 2001
+datatype[8].structtype[0].field[7].name docfield
+datatype[8].structtype[0].field[7].id[0]
+datatype[8].structtype[0].field[7].datatype 8
+datatype[8].structtype[0].field[8].name wsfield
+datatype[8].structtype[0].field[8].id[0]
+datatype[8].structtype[0].field[8].datatype 437829
+datatype[8].structtype[0].field[9].name mapfield
+datatype[8].structtype[0].field[9].id[0]
+datatype[8].structtype[0].field[9].datatype 9999
+datatype[8].documenttype[0]
+datatype[9].id 1306012852
+datatype[9].arraytype[0]
+datatype[9].weightedsettype[0]
+datatype[9].structtype[0]
+datatype[9].documenttype[1]
+datatype[9].documenttype[0].name serializetest
+datatype[9].documenttype[0].version 0
+datatype[9].documenttype[0].inherits[0]
+datatype[9].documenttype[0].headerstruct -260050933
+datatype[9].documenttype[0].bodystruct 1026122976
+
diff --git a/document/src/tests/data/defaultdoctypes.cfg b/document/src/tests/data/defaultdoctypes.cfg
new file mode 100644
index 00000000000..8d9f71cfeb9
--- /dev/null
+++ b/document/src/tests/data/defaultdoctypes.cfg
@@ -0,0 +1,150 @@
+enablecompression false
+documenttype[1]
+documenttype[0].id -1175657560
+documenttype[0].name "testdoc"
+documenttype[0].version 0
+documenttype[0].headerstruct 5000
+documenttype[0].bodystruct 5001
+documenttype[0].inherits[0]
+documenttype[0].datatype[5]
+documenttype[0].datatype[0].id 1000
+documenttype[0].datatype[0].type ARRAY
+documenttype[0].datatype[0].array.element.id 0
+documenttype[0].datatype[0].map.key.id 0
+documenttype[0].datatype[0].map.value.id 0
+documenttype[0].datatype[0].wset.key.id 0
+documenttype[0].datatype[0].wset.createifnonexistent false
+documenttype[0].datatype[0].wset.removeifzero false
+documenttype[0].datatype[0].annotationref.annotation.id 0
+documenttype[0].datatype[0].sstruct.name ""
+documenttype[0].datatype[0].sstruct.version 0
+documenttype[0].datatype[0].sstruct.compression.type NONE
+documenttype[0].datatype[0].sstruct.compression.level 0
+documenttype[0].datatype[0].sstruct.compression.threshold 95
+documenttype[0].datatype[0].sstruct.compression.minsize 200
+documenttype[0].datatype[0].sstruct.field[0]
+documenttype[0].datatype[1].id 1003
+documenttype[0].datatype[1].type ARRAY
+documenttype[0].datatype[1].array.element.id 3
+documenttype[0].datatype[1].map.key.id 0
+documenttype[0].datatype[1].map.value.id 0
+documenttype[0].datatype[1].wset.key.id 0
+documenttype[0].datatype[1].wset.createifnonexistent false
+documenttype[0].datatype[1].wset.removeifzero false
+documenttype[0].datatype[1].annotationref.annotation.id 0
+documenttype[0].datatype[1].sstruct.name ""
+documenttype[0].datatype[1].sstruct.version 0
+documenttype[0].datatype[1].sstruct.compression.type NONE
+documenttype[0].datatype[1].sstruct.compression.level 0
+documenttype[0].datatype[1].sstruct.compression.threshold 95
+documenttype[0].datatype[1].sstruct.compression.minsize 200
+documenttype[0].datatype[1].sstruct.field[0]
+documenttype[0].datatype[2].id 2002
+documenttype[0].datatype[2].type WSET
+documenttype[0].datatype[2].array.element.id 0
+documenttype[0].datatype[2].map.key.id 0
+documenttype[0].datatype[2].map.value.id 0
+documenttype[0].datatype[2].wset.key.id 2
+documenttype[0].datatype[2].wset.createifnonexistent false
+documenttype[0].datatype[2].wset.removeifzero false
+documenttype[0].datatype[2].annotationref.annotation.id 0
+documenttype[0].datatype[2].sstruct.name ""
+documenttype[0].datatype[2].sstruct.version 0
+documenttype[0].datatype[2].sstruct.compression.type NONE
+documenttype[0].datatype[2].sstruct.compression.level 0
+documenttype[0].datatype[2].sstruct.compression.threshold 95
+documenttype[0].datatype[2].sstruct.compression.minsize 200
+documenttype[0].datatype[2].sstruct.field[0]
+documenttype[0].datatype[3].id 5000
+documenttype[0].datatype[3].type STRUCT
+documenttype[0].datatype[3].array.element.id 0
+documenttype[0].datatype[3].map.key.id 0
+documenttype[0].datatype[3].map.value.id 0
+documenttype[0].datatype[3].wset.key.id 0
+documenttype[0].datatype[3].wset.createifnonexistent false
+documenttype[0].datatype[3].wset.removeifzero false
+documenttype[0].datatype[3].annotationref.annotation.id 0
+documenttype[0].datatype[3].sstruct.name "testdoc.header"
+documenttype[0].datatype[3].sstruct.version 0
+documenttype[0].datatype[3].sstruct.compression.type NONE
+documenttype[0].datatype[3].sstruct.compression.level 0
+documenttype[0].datatype[3].sstruct.compression.threshold 90
+documenttype[0].datatype[3].sstruct.compression.minsize 0
+documenttype[0].datatype[3].sstruct.field[3]
+documenttype[0].datatype[3].sstruct.field[0].name "doubleattr"
+documenttype[0].datatype[3].sstruct.field[0].id 149562827
+documenttype[0].datatype[3].sstruct.field[0].id_v6 630428365
+documenttype[0].datatype[3].sstruct.field[0].datatype 5
+documenttype[0].datatype[3].sstruct.field[1].name "floatattr"
+documenttype[0].datatype[3].sstruct.field[1].id 1633158509
+documenttype[0].datatype[3].sstruct.field[1].id_v6 1853512789
+documenttype[0].datatype[3].sstruct.field[1].datatype 1
+documenttype[0].datatype[3].sstruct.field[2].name "intattr"
+documenttype[0].datatype[3].sstruct.field[2].id 2079212392
+documenttype[0].datatype[3].sstruct.field[2].id_v6 1840069917
+documenttype[0].datatype[3].sstruct.field[2].datatype 0
+documenttype[0].datatype[4].id 5001
+documenttype[0].datatype[4].type STRUCT
+documenttype[0].datatype[4].array.element.id 0
+documenttype[0].datatype[4].map.key.id 0
+documenttype[0].datatype[4].map.value.id 0
+documenttype[0].datatype[4].wset.key.id 0
+documenttype[0].datatype[4].wset.createifnonexistent false
+documenttype[0].datatype[4].wset.removeifzero false
+documenttype[0].datatype[4].annotationref.annotation.id 0
+documenttype[0].datatype[4].sstruct.name "testdoc.body"
+documenttype[0].datatype[4].sstruct.version 0
+documenttype[0].datatype[4].sstruct.compression.type NONE
+documenttype[0].datatype[4].sstruct.compression.level 0
+documenttype[0].datatype[4].sstruct.compression.threshold 90
+documenttype[0].datatype[4].sstruct.compression.minsize 0
+documenttype[0].datatype[4].sstruct.field[12]
+documenttype[0].datatype[4].sstruct.field[0].name "arrayattr"
+documenttype[0].datatype[4].sstruct.field[0].id 505184256
+documenttype[0].datatype[4].sstruct.field[0].id_v6 1771774378
+documenttype[0].datatype[4].sstruct.field[0].datatype 1000
+documenttype[0].datatype[4].sstruct.field[1].name "byteattr"
+documenttype[0].datatype[4].sstruct.field[1].id 2134674304
+documenttype[0].datatype[4].sstruct.field[1].id_v6 973038129
+documenttype[0].datatype[4].sstruct.field[1].datatype 16
+documenttype[0].datatype[4].sstruct.field[2].name "docfield"
+documenttype[0].datatype[4].sstruct.field[2].id 728739268
+documenttype[0].datatype[4].sstruct.field[2].id_v6 1725514984
+documenttype[0].datatype[4].sstruct.field[2].datatype 8
+documenttype[0].datatype[4].sstruct.field[3].name "longattr"
+documenttype[0].datatype[4].sstruct.field[3].id 790774460
+documenttype[0].datatype[4].sstruct.field[3].id_v6 192291556
+documenttype[0].datatype[4].sstruct.field[3].datatype 4
+documenttype[0].datatype[4].sstruct.field[4].name "minattr"
+documenttype[0].datatype[4].sstruct.field[4].id 1829587385
+documenttype[0].datatype[4].sstruct.field[4].id_v6 741727195
+documenttype[0].datatype[4].sstruct.field[4].datatype 0
+documenttype[0].datatype[4].sstruct.field[5].name "minattr2"
+documenttype[0].datatype[4].sstruct.field[5].id 128399609
+documenttype[0].datatype[4].sstruct.field[5].id_v6 920037076
+documenttype[0].datatype[4].sstruct.field[5].datatype 0
+documenttype[0].datatype[4].sstruct.field[6].name "rawarrayattr"
+documenttype[0].datatype[4].sstruct.field[6].id 730665199
+documenttype[0].datatype[4].sstruct.field[6].id_v6 1023469062
+documenttype[0].datatype[4].sstruct.field[6].datatype 1003
+documenttype[0].datatype[4].sstruct.field[7].name "rawattr"
+documenttype[0].datatype[4].sstruct.field[7].id 141092158
+documenttype[0].datatype[4].sstruct.field[7].id_v6 1247497470
+documenttype[0].datatype[4].sstruct.field[7].datatype 3
+documenttype[0].datatype[4].sstruct.field[8].name "stringattr"
+documenttype[0].datatype[4].sstruct.field[8].id 1869816099
+documenttype[0].datatype[4].sstruct.field[8].id_v6 1592475234
+documenttype[0].datatype[4].sstruct.field[8].datatype 2
+documenttype[0].datatype[4].sstruct.field[9].name "stringattr2"
+documenttype[0].datatype[4].sstruct.field[9].id 1054720742
+documenttype[0].datatype[4].sstruct.field[9].id_v6 1869816099
+documenttype[0].datatype[4].sstruct.field[9].datatype 2
+documenttype[0].datatype[4].sstruct.field[10].name "stringweightedsetattr"
+documenttype[0].datatype[4].sstruct.field[10].id 1791723468
+documenttype[0].datatype[4].sstruct.field[10].id_v6 676317895
+documenttype[0].datatype[4].sstruct.field[10].datatype 2002
+documenttype[0].datatype[4].sstruct.field[11].name "uri"
+documenttype[0].datatype[4].sstruct.field[11].id 31928604
+documenttype[0].datatype[4].sstruct.field[11].id_v6 1003424810
+documenttype[0].datatype[4].sstruct.field[11].datatype 2
+documenttype[0].annotationtype[0]
diff --git a/document/src/tests/data/defaultdocument.cfg b/document/src/tests/data/defaultdocument.cfg
new file mode 100644
index 00000000000..9780f43def6
--- /dev/null
+++ b/document/src/tests/data/defaultdocument.cfg
@@ -0,0 +1,94 @@
+enablecompression false
+datatype[6]
+datatype[0].id 1000
+datatype[0].arraytype[1]
+datatype[0].arraytype[0].datatype 0
+datatype[0].weightedsettype[0]
+datatype[0].structtype[0]
+datatype[0].documenttype[0]
+datatype[1].id 1003
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 3
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 2002
+datatype[2].arraytype[0]
+datatype[2].weightedsettype[1]
+datatype[2].weightedsettype[0].datatype 2
+datatype[2].weightedsettype[0].createifnonexistant false
+datatype[2].weightedsettype[0].removeifzero false
+datatype[2].structtype[0]
+datatype[2].documenttype[0]
+datatype[3].id 5000
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].name testdoc.header
+datatype[3].structtype[0].version 0
+datatype[3].structtype[0].field[3]
+datatype[3].structtype[0].field[0].name intattr
+datatype[3].structtype[0].field[0].id[0]
+datatype[3].structtype[0].field[0].datatype 0
+datatype[3].structtype[0].field[1].name doubleattr
+datatype[3].structtype[0].field[1].id[0]
+datatype[3].structtype[0].field[1].datatype 5
+datatype[3].structtype[0].field[2].name floatattr
+datatype[3].structtype[0].field[2].id[0]
+datatype[3].structtype[0].field[2].datatype 1
+datatype[3].documenttype[0]
+datatype[4].id 5001
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[1]
+datatype[4].structtype[0].name testdoc.body
+datatype[4].structtype[0].version 0
+datatype[4].structtype[0].field[11]
+datatype[4].structtype[0].field[0].name stringattr
+datatype[4].structtype[0].field[0].id[0]
+datatype[4].structtype[0].field[0].datatype 2
+datatype[4].structtype[0].field[1].name stringattr2
+datatype[4].structtype[0].field[1].id[0]
+datatype[4].structtype[0].field[1].datatype 2
+datatype[4].structtype[0].field[2].name longattr
+datatype[4].structtype[0].field[2].id[0]
+datatype[4].structtype[0].field[2].datatype 4
+datatype[4].structtype[0].field[3].name byteattr
+datatype[4].structtype[0].field[3].id[0]
+datatype[4].structtype[0].field[3].datatype 16
+datatype[4].structtype[0].field[4].name rawattr
+datatype[4].structtype[0].field[4].id[0]
+datatype[4].structtype[0].field[4].datatype 3
+datatype[4].structtype[0].field[5].name minattr
+datatype[4].structtype[0].field[5].id[0]
+datatype[4].structtype[0].field[5].datatype 0
+datatype[4].structtype[0].field[6].name minattr2
+datatype[4].structtype[0].field[6].id[0]
+datatype[4].structtype[0].field[6].datatype 0
+datatype[4].structtype[0].field[7].name arrayattr
+datatype[4].structtype[0].field[7].id[0]
+datatype[4].structtype[0].field[7].datatype 1000
+datatype[4].structtype[0].field[8].name rawarrayattr
+datatype[4].structtype[0].field[8].id[0]
+datatype[4].structtype[0].field[8].datatype 1003
+datatype[4].structtype[0].field[9].name stringweightedsetattr
+datatype[4].structtype[0].field[9].id[0]
+datatype[4].structtype[0].field[9].datatype 2002
+datatype[4].structtype[0].field[10].name uri
+datatype[4].structtype[0].field[10].id[0]
+datatype[4].structtype[0].field[10].datatype 2
+datatype[4].structtype[0].field[11].name docfield
+datatype[4].structtype[0].field[11].id[0]
+datatype[4].structtype[0].field[11].datatype 8
+datatype[4].documenttype[0]
+datatype[5].id 5002
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[0]
+datatype[5].documenttype[1]
+datatype[5].documenttype[0].name testdoc
+datatype[5].documenttype[0].version 0
+datatype[5].documenttype[0].inherits[0]
+datatype[5].documenttype[0].headerstruct 5000
+datatype[5].documenttype[0].bodystruct 5001
+
diff --git a/document/src/tests/data/docmancfg.txt b/document/src/tests/data/docmancfg.txt
new file mode 100644
index 00000000000..9507211edf3
--- /dev/null
+++ b/document/src/tests/data/docmancfg.txt
@@ -0,0 +1,170 @@
+enablecompression false
+datatype[16]
+datatype[0].id 101
+datatype[0].arraytype[1]
+datatype[0].arraytype[0].datatype 1
+datatype[0].weightedsettype[0]
+datatype[0].structtype[0]
+datatype[0].documenttype[0]
+datatype[1].id 102
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 2
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 103
+datatype[2].arraytype[0]
+datatype[2].weightedsettype[0]
+datatype[2].structtype[1]
+datatype[2].structtype[0].name teststruct
+datatype[2].structtype[0].version 0
+datatype[2].structtype[0].field[2]
+datatype[2].structtype[0].field[0].name Foo
+datatype[2].structtype[0].field[0].id[0]
+datatype[2].structtype[0].field[0].datatype 2
+datatype[2].structtype[0].field[1].name Bar
+datatype[2].structtype[0].field[1].id[0]
+datatype[2].structtype[0].field[1].datatype 2
+datatype[2].documenttype[0]
+datatype[3].id 104
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].name teststruct
+datatype[3].structtype[0].version 1
+datatype[3].structtype[0].field[2]
+datatype[3].structtype[0].field[0].name Foooo
+datatype[3].structtype[0].field[0].id[0]
+datatype[3].structtype[0].field[0].datatype 2
+datatype[3].structtype[0].field[1].name Bar
+datatype[3].structtype[0].field[1].id[0]
+datatype[3].structtype[0].field[1].datatype 2
+datatype[3].documenttype[0]
+datatype[4].id 1000
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[1]
+datatype[4].structtype[0].name crawler_type.header
+datatype[4].structtype[0].version 0
+datatype[4].structtype[0].field[5]
+datatype[4].structtype[0].field[0].name URI
+datatype[4].structtype[0].field[0].id[1]
+datatype[4].structtype[0].field[0].id[0].id 0
+datatype[4].structtype[0].field[0].datatype 2
+datatype[4].structtype[0].field[1].name CONTEXT
+datatype[4].structtype[0].field[1].id[1]
+datatype[4].structtype[0].field[1].id[0].id 1
+datatype[4].structtype[0].field[1].datatype 2
+datatype[4].structtype[0].field[2].name CONTENT
+datatype[4].structtype[0].field[2].id[1]
+datatype[4].structtype[0].field[2].id[0].id 2
+datatype[4].structtype[0].field[2].datatype 3
+datatype[4].structtype[0].field[3].name CONTENT_TYPE
+datatype[4].structtype[0].field[3].id[1]
+datatype[4].structtype[0].field[3].id[0].id 3
+datatype[4].structtype[0].field[3].datatype 2
+datatype[4].structtype[0].field[4].name LAST_MODIFIED
+datatype[4].structtype[0].field[4].id[1]
+datatype[4].structtype[0].field[4].id[0].id 4
+datatype[4].structtype[0].field[4].datatype 0
+datatype[4].documenttype[0]
+datatype[5].id 1001
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].name crawler_type.body
+datatype[5].structtype[0].version 0
+datatype[5].structtype[0].field[0]
+datatype[5].documenttype[0]
+datatype[6].id 1002
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[0]
+datatype[6].structtype[0]
+datatype[6].documenttype[1]
+datatype[6].documenttype[0].name crawler_type
+datatype[6].documenttype[0].version 0
+datatype[6].documenttype[0].inherits[0]
+datatype[6].documenttype[0].headerstruct 1000
+datatype[6].documenttype[0].bodystruct 1001
+datatype[7].id 2000
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[1]
+datatype[7].structtype[0].name multimedia_type.header
+datatype[7].structtype[0].version 0
+datatype[7].structtype[0].field[7]
+datatype[7].structtype[0].field[0].name URL_KEYWORDS
+datatype[7].structtype[0].field[0].id[1]
+datatype[7].structtype[0].field[0].id[0].id 147
+datatype[7].structtype[0].field[0].datatype 102
+datatype[7].structtype[0].field[1].name MULTIMEDIA_LINKTYPES
+datatype[7].structtype[0].field[1].id[1]
+datatype[7].structtype[0].field[1].id[0].id 148
+datatype[7].structtype[0].field[1].datatype 102
+datatype[7].structtype[0].field[2].name THUMBNAIL_URLS
+datatype[7].structtype[0].field[2].id[1]
+datatype[7].structtype[0].field[2].id[0].id 149
+datatype[7].structtype[0].field[2].datatype 102
+datatype[7].structtype[0].field[3].name FROM_LINK_LIST
+datatype[7].structtype[0].field[3].id[1]
+datatype[7].structtype[0].field[3].id[0].id 150
+datatype[7].structtype[0].field[3].datatype 101
+datatype[7].structtype[0].field[4].name FROM_LINK_LANGUAGE_LIST
+datatype[7].structtype[0].field[4].id[1]
+datatype[7].structtype[0].field[4].id[0].id 151
+datatype[7].structtype[0].field[4].datatype 102
+datatype[7].structtype[0].field[5].name TESTSTRUCT1
+datatype[7].structtype[0].field[5].id[1]
+datatype[7].structtype[0].field[5].id[0].id 153
+datatype[7].structtype[0].field[5].datatype 103
+datatype[7].structtype[0].field[6].name TESTSTRUCT2
+datatype[7].structtype[0].field[6].id[1]
+datatype[7].structtype[0].field[6].id[0].id 154
+datatype[7].structtype[0].field[6].datatype 104
+datatype[7].documenttype[0]
+datatype[8].id 2001
+datatype[8].structtype[1]
+datatype[8].structtype[0].name multimedia_type.body
+datatype[8].structtype[0].version 0
+datatype[8].structtype[0].field[0]
+datatype[9].id 2002
+datatype[9].documenttype[1]
+datatype[9].documenttype[0].name multimedia_type
+datatype[9].documenttype[0].version 0
+datatype[9].documenttype[0].inherits[1]
+datatype[9].documenttype[0].inherits[0].name crawler_type
+datatype[9].documenttype[0].inherits[0].version 0
+datatype[9].documenttype[0].headerstruct 2000
+datatype[9].documenttype[0].bodystruct 2001
+datatype[10].id 2003
+datatype[10].maptype[1]
+datatype[10].maptype[0].keytype 2
+datatype[10].maptype[0].valtype 2
+datatype[11].id 2004
+datatype[11].maptype[1]
+datatype[11].maptype[0].keytype 0
+datatype[11].maptype[0].valtype 0
+datatype[12].id 2005
+datatype[12].maptype[1]
+datatype[12].maptype[0].keytype 2003
+datatype[12].maptype[0].valtype 2004
+datatype[13].id 2006
+datatype[13].structtype[1]
+datatype[13].structtype[0].name map.header
+datatype[13].structtype[0].version 0
+datatype[13].structtype[0].field[1]
+datatype[13].structtype[0].field[0].name mymap
+datatype[13].structtype[0].field[0].id[1]
+datatype[13].structtype[0].field[0].id[0].id 155
+datatype[13].structtype[0].field[0].datatype 2005
+datatype[14].id 2007
+datatype[14].structtype[1]
+datatype[14].structtype[0].name map.body
+datatype[14].structtype[0].version 0
+datatype[14].structtype[0].field[0]
+datatype[15].id 2008
+datatype[15].documenttype[1]
+datatype[15].documenttype[0].name mapdoc
+datatype[15].documenttype[0].version 0
+datatype[15].documenttype[0].headerstruct 2006
+datatype[15].documenttype[0].bodystruct 2007
diff --git a/document/src/tests/data/docselectcfg1.txt b/document/src/tests/data/docselectcfg1.txt
new file mode 100644
index 00000000000..aeb20ea42ea
--- /dev/null
+++ b/document/src/tests/data/docselectcfg1.txt
@@ -0,0 +1,16 @@
+documenttype testdoc
+documenttypeversion -1
+documentselect[3]
+documentselect[0].type and
+documentselect[0].subcount 2
+documentselect[0].field ""
+documentselect[0].value ""
+documentselect[1].type equals
+documentselect[1].subcount 0
+documentselect[1].field "stringattr"
+documentselect[1].value "hei"
+documentselect[2].type equals
+documentselect[2].subcount 0
+documentselect[2].field "intattr"
+documentselect[2].value "100"
+
diff --git a/document/src/tests/data/docselectcfg2.txt b/document/src/tests/data/docselectcfg2.txt
new file mode 100644
index 00000000000..b96e12331f1
--- /dev/null
+++ b/document/src/tests/data/docselectcfg2.txt
@@ -0,0 +1,35 @@
+documenttype testdoc
+documenttypeversion 0
+documentselect[8]
+documentselect[0].type or
+documentselect[0].subcount 2
+documentselect[0].field ""
+documentselect[0].value ""
+documentselect[1].type and
+documentselect[1].subcount 2
+documentselect[1].field ""
+documentselect[1].value ""
+documentselect[2].type not
+documentselect[2].subcount 1
+documentselect[2].field ""
+documentselect[2].value ""
+documentselect[3].type equals
+documentselect[3].subcount 0
+documentselect[3].field "stringattr"
+documentselect[3].value "hei"
+documentselect[4].type equals
+documentselect[4].subcount 0
+documentselect[4].field "intattr"
+documentselect[4].value "100"
+documentselect[5].type and
+documentselect[5].subcount 2
+documentselect[5].field ""
+documentselect[5].value ""
+documentselect[6].type equals
+documentselect[6].subcount 0
+documentselect[6].field "URI"
+documentselect[6].value "http://www.ntnu.no/"
+documentselect[7].type lt
+documentselect[7].subcount 0
+documentselect[7].field "floatattr"
+documentselect[7].value "100.5"
diff --git a/document/src/tests/data/doctypesconfigtest.cfg b/document/src/tests/data/doctypesconfigtest.cfg
new file mode 100644
index 00000000000..36666bc7f3e
--- /dev/null
+++ b/document/src/tests/data/doctypesconfigtest.cfg
@@ -0,0 +1,184 @@
+enablecompression false
+documenttype[3]
+documenttype[0].id -332662462
+documenttype[0].name "base1"
+documenttype[0].version 0
+documenttype[0].headerstruct 762677181
+documenttype[0].bodystruct -1448787694
+documenttype[0].inherits[0]
+documenttype[0].datatype[3]
+documenttype[0].datatype[0].id 1000
+documenttype[0].datatype[0].type ARRAY
+documenttype[0].datatype[0].array.element.id 0
+documenttype[0].datatype[0].map.key.id 0
+documenttype[0].datatype[0].map.value.id 0
+documenttype[0].datatype[0].wset.key.id 0
+documenttype[0].datatype[0].wset.createifnonexistent false
+documenttype[0].datatype[0].wset.removeifzero false
+documenttype[0].datatype[0].annotationref.annotation.id 0
+documenttype[0].datatype[0].sstruct.name ""
+documenttype[0].datatype[0].sstruct.version 0
+documenttype[0].datatype[0].sstruct.compression.type NONE
+documenttype[0].datatype[0].sstruct.compression.level 0
+documenttype[0].datatype[0].sstruct.compression.threshold 95
+documenttype[0].datatype[0].sstruct.compression.minsize 200
+documenttype[0].datatype[0].sstruct.field[0]
+documenttype[0].datatype[1].id 762677181
+documenttype[0].datatype[1].type STRUCT
+documenttype[0].datatype[1].array.element.id 0
+documenttype[0].datatype[1].map.key.id 0
+documenttype[0].datatype[1].map.value.id 0
+documenttype[0].datatype[1].wset.key.id 0
+documenttype[0].datatype[1].wset.createifnonexistent false
+documenttype[0].datatype[1].wset.removeifzero false
+documenttype[0].datatype[1].annotationref.annotation.id 0
+documenttype[0].datatype[1].sstruct.name "base1.header"
+documenttype[0].datatype[1].sstruct.version 0
+documenttype[0].datatype[1].sstruct.compression.type NONE
+documenttype[0].datatype[1].sstruct.compression.level 0
+documenttype[0].datatype[1].sstruct.compression.threshold 90
+documenttype[0].datatype[1].sstruct.compression.minsize 0
+documenttype[0].datatype[1].sstruct.field[3]
+documenttype[0].datatype[1].sstruct.field[0].name "field1"
+documenttype[0].datatype[1].sstruct.field[0].id 36262354
+documenttype[0].datatype[1].sstruct.field[0].id_v6 1141913055
+documenttype[0].datatype[1].sstruct.field[0].datatype 0
+documenttype[0].datatype[1].sstruct.field[1].name "field2"
+documenttype[0].datatype[1].sstruct.field[1].id 1388988771
+documenttype[0].datatype[1].sstruct.field[1].id_v6 1215205431
+documenttype[0].datatype[1].sstruct.field[1].datatype 0
+documenttype[0].datatype[1].sstruct.field[2].name "fieldarray1"
+documenttype[0].datatype[1].sstruct.field[2].id 1030920758
+documenttype[0].datatype[1].sstruct.field[2].id_v6 1073985444
+documenttype[0].datatype[1].sstruct.field[2].datatype 1000
+documenttype[0].datatype[2].id -1448787694
+documenttype[0].datatype[2].type STRUCT
+documenttype[0].datatype[2].array.element.id 0
+documenttype[0].datatype[2].map.key.id 0
+documenttype[0].datatype[2].map.value.id 0
+documenttype[0].datatype[2].wset.key.id 0
+documenttype[0].datatype[2].wset.createifnonexistent false
+documenttype[0].datatype[2].wset.removeifzero false
+documenttype[0].datatype[2].annotationref.annotation.id 0
+documenttype[0].datatype[2].sstruct.name "base1.body"
+documenttype[0].datatype[2].sstruct.version 0
+documenttype[0].datatype[2].sstruct.compression.type NONE
+documenttype[0].datatype[2].sstruct.compression.level 0
+documenttype[0].datatype[2].sstruct.compression.threshold 90
+documenttype[0].datatype[2].sstruct.compression.minsize 0
+documenttype[0].datatype[2].sstruct.field[0]
+documenttype[0].annotationtype[0]
+documenttype[1].id -332661500
+documenttype[1].name "base2"
+documenttype[1].version 1
+documenttype[1].headerstruct 566163677
+documenttype[1].bodystruct 294022642
+documenttype[1].inherits[0]
+documenttype[1].datatype[2]
+documenttype[1].datatype[0].id 566163677
+documenttype[1].datatype[0].type STRUCT
+documenttype[1].datatype[0].array.element.id 0
+documenttype[1].datatype[0].map.key.id 0
+documenttype[1].datatype[0].map.value.id 0
+documenttype[1].datatype[0].wset.key.id 0
+documenttype[1].datatype[0].wset.createifnonexistent false
+documenttype[1].datatype[0].wset.removeifzero false
+documenttype[1].datatype[0].annotationref.annotation.id 0
+documenttype[1].datatype[0].sstruct.name "base2.header"
+documenttype[1].datatype[0].sstruct.version 1
+documenttype[1].datatype[0].sstruct.compression.type NONE
+documenttype[1].datatype[0].sstruct.compression.level 0
+documenttype[1].datatype[0].sstruct.compression.threshold 90
+documenttype[1].datatype[0].sstruct.compression.minsize 0
+documenttype[1].datatype[0].sstruct.field[1]
+documenttype[1].datatype[0].sstruct.field[0].name "field4"
+documenttype[1].datatype[0].sstruct.field[0].id 1556675886
+documenttype[1].datatype[0].sstruct.field[0].id_v6 1677332587
+documenttype[1].datatype[0].sstruct.field[0].datatype 2
+documenttype[1].datatype[1].id 294022642
+documenttype[1].datatype[1].type STRUCT
+documenttype[1].datatype[1].array.element.id 0
+documenttype[1].datatype[1].map.key.id 0
+documenttype[1].datatype[1].map.value.id 0
+documenttype[1].datatype[1].wset.key.id 0
+documenttype[1].datatype[1].wset.createifnonexistent false
+documenttype[1].datatype[1].wset.removeifzero false
+documenttype[1].datatype[1].annotationref.annotation.id 0
+documenttype[1].datatype[1].sstruct.name "base2.body"
+documenttype[1].datatype[1].sstruct.version 1
+documenttype[1].datatype[1].sstruct.compression.type NONE
+documenttype[1].datatype[1].sstruct.compression.level 0
+documenttype[1].datatype[1].sstruct.compression.threshold 90
+documenttype[1].datatype[1].sstruct.compression.minsize 0
+documenttype[1].datatype[1].sstruct.field[1]
+documenttype[1].datatype[1].sstruct.field[0].name "field3"
+documenttype[1].datatype[1].sstruct.field[0].id 722536103
+documenttype[1].datatype[1].sstruct.field[0].id_v6 1036475953
+documenttype[1].datatype[1].sstruct.field[0].datatype 2
+documenttype[1].annotationtype[0]
+documenttype[2].id 787712161
+documenttype[2].name "derived"
+documenttype[2].version 2
+documenttype[2].headerstruct 296510914
+documenttype[2].bodystruct -1359887401
+documenttype[2].inherits[2]
+documenttype[2].inherits[0].id -332662462
+documenttype[2].inherits[1].id -332661500
+documenttype[2].datatype[2]
+documenttype[2].datatype[0].id 296510914
+documenttype[2].datatype[0].type STRUCT
+documenttype[2].datatype[0].array.element.id 0
+documenttype[2].datatype[0].map.key.id 0
+documenttype[2].datatype[0].map.value.id 0
+documenttype[2].datatype[0].wset.key.id 0
+documenttype[2].datatype[0].wset.createifnonexistent false
+documenttype[2].datatype[0].wset.removeifzero false
+documenttype[2].datatype[0].annotationref.annotation.id 0
+documenttype[2].datatype[0].sstruct.name "derived.header"
+documenttype[2].datatype[0].sstruct.version 2
+documenttype[2].datatype[0].sstruct.compression.type NONE
+documenttype[2].datatype[0].sstruct.compression.level 0
+documenttype[2].datatype[0].sstruct.compression.threshold 90
+documenttype[2].datatype[0].sstruct.compression.minsize 0
+documenttype[2].datatype[0].sstruct.field[4]
+documenttype[2].datatype[0].sstruct.field[0].name "field1"
+documenttype[2].datatype[0].sstruct.field[0].id 36262354
+documenttype[2].datatype[0].sstruct.field[0].id_v6 1141913055
+documenttype[2].datatype[0].sstruct.field[0].datatype 0
+documenttype[2].datatype[0].sstruct.field[1].name "field2"
+documenttype[2].datatype[0].sstruct.field[1].id 1388988771
+documenttype[2].datatype[0].sstruct.field[1].id_v6 1215205431
+documenttype[2].datatype[0].sstruct.field[1].datatype 0
+documenttype[2].datatype[0].sstruct.field[2].name "field4"
+documenttype[2].datatype[0].sstruct.field[2].id 1556675886
+documenttype[2].datatype[0].sstruct.field[2].id_v6 1677332587
+documenttype[2].datatype[0].sstruct.field[2].datatype 2
+documenttype[2].datatype[0].sstruct.field[3].name "fieldarray1"
+documenttype[2].datatype[0].sstruct.field[3].id 1030920758
+documenttype[2].datatype[0].sstruct.field[3].id_v6 1073985444
+documenttype[2].datatype[0].sstruct.field[3].datatype 1000
+documenttype[2].datatype[1].id -1359887401
+documenttype[2].datatype[1].type STRUCT
+documenttype[2].datatype[1].array.element.id 0
+documenttype[2].datatype[1].map.key.id 0
+documenttype[2].datatype[1].map.value.id 0
+documenttype[2].datatype[1].wset.key.id 0
+documenttype[2].datatype[1].wset.createifnonexistent false
+documenttype[2].datatype[1].wset.removeifzero false
+documenttype[2].datatype[1].annotationref.annotation.id 0
+documenttype[2].datatype[1].sstruct.name "derived.body"
+documenttype[2].datatype[1].sstruct.version 2
+documenttype[2].datatype[1].sstruct.compression.type NONE
+documenttype[2].datatype[1].sstruct.compression.level 0
+documenttype[2].datatype[1].sstruct.compression.threshold 90
+documenttype[2].datatype[1].sstruct.compression.minsize 0
+documenttype[2].datatype[1].sstruct.field[2]
+documenttype[2].datatype[1].sstruct.field[0].name "field3"
+documenttype[2].datatype[1].sstruct.field[0].id 722536103
+documenttype[2].datatype[1].sstruct.field[0].id_v6 1036475953
+documenttype[2].datatype[1].sstruct.field[0].datatype 2
+documenttype[2].datatype[1].sstruct.field[1].name "field5"
+documenttype[2].datatype[1].sstruct.field[1].id 1352306621
+documenttype[2].datatype[1].sstruct.field[1].id_v6 1511574032
+documenttype[2].datatype[1].sstruct.field[1].datatype 1
+documenttype[2].annotationtype[0]
diff --git a/document/src/tests/data/document-cpp-currentversion-lz4-9.dat b/document/src/tests/data/document-cpp-currentversion-lz4-9.dat
new file mode 100644
index 00000000000..5f31ac0ab78
--- /dev/null
+++ b/document/src/tests/data/document-cpp-currentversion-lz4-9.dat
Binary files differ
diff --git a/document/src/tests/data/document-cpp-currentversion-uncompressed.dat b/document/src/tests/data/document-cpp-currentversion-uncompressed.dat
new file mode 100644
index 00000000000..6a5c5e90b84
--- /dev/null
+++ b/document/src/tests/data/document-cpp-currentversion-uncompressed.dat
Binary files differ
diff --git a/document/src/tests/data/document-cpp-v7-uncompressed.dat b/document/src/tests/data/document-cpp-v7-uncompressed.dat
new file mode 100644
index 00000000000..5f073e33438
--- /dev/null
+++ b/document/src/tests/data/document-cpp-v7-uncompressed.dat
Binary files differ
diff --git a/document/src/tests/data/document-cpp-v8-uncompressed.dat b/document/src/tests/data/document-cpp-v8-uncompressed.dat
new file mode 100644
index 00000000000..429334fc102
--- /dev/null
+++ b/document/src/tests/data/document-cpp-v8-uncompressed.dat
Binary files differ
diff --git a/document/src/tests/data/embeddeddocument.cfg b/document/src/tests/data/embeddeddocument.cfg
new file mode 100644
index 00000000000..baefc0d7338
--- /dev/null
+++ b/document/src/tests/data/embeddeddocument.cfg
@@ -0,0 +1,102 @@
+enablecompression false
+datatype[8]
+datatype[0].id 1001
+datatype[0].arraytype[1]
+datatype[0].arraytype[0].datatype 1
+datatype[0].weightedsettype[0]
+datatype[0].structtype[0]
+datatype[0].documenttype[0]
+datatype[1].id 2001
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 1001
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id -1686125086
+datatype[2].arraytype[0]
+datatype[2].weightedsettype[0]
+datatype[2].structtype[1]
+datatype[2].structtype[0].name docindoc.header
+datatype[2].structtype[0].version 0
+datatype[2].structtype[0].field[0]
+datatype[2].documenttype[0]
+datatype[3].id 2030224503
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].name docindoc.body
+datatype[3].structtype[0].version 0
+datatype[3].structtype[0].field[1]
+datatype[3].structtype[0].field[0].name stringindoc
+datatype[3].structtype[0].field[0].id[0]
+datatype[3].structtype[0].field[0].datatype 2
+datatype[3].documenttype[0]
+datatype[4].id 1447635645
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[0]
+datatype[4].documenttype[1]
+datatype[4].documenttype[0].name docindoc
+datatype[4].documenttype[0].version 0
+datatype[4].documenttype[0].inherits[0]
+datatype[4].documenttype[0].headerstruct -1686125086
+datatype[4].documenttype[0].bodystruct 2030224503
+datatype[5].id -260050933
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].name serializetest.header
+datatype[5].structtype[0].version 0
+datatype[5].structtype[0].field[4]
+datatype[5].structtype[0].field[0].name floatfield
+datatype[5].structtype[0].field[0].id[0]
+datatype[5].structtype[0].field[0].datatype 1
+datatype[5].structtype[0].field[1].name stringfield
+datatype[5].structtype[0].field[1].id[0]
+datatype[5].structtype[0].field[1].datatype 2
+datatype[5].structtype[0].field[2].name longfield
+datatype[5].structtype[0].field[2].id[0]
+datatype[5].structtype[0].field[2].datatype 4
+datatype[5].structtype[0].field[3].name urifield
+datatype[5].structtype[0].field[3].id[0]
+datatype[5].structtype[0].field[3].datatype 10
+datatype[5].documenttype[0]
+datatype[6].id 1026122976
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[0]
+datatype[6].structtype[1]
+datatype[6].structtype[0].name serializetest.body
+datatype[6].structtype[0].version 0
+datatype[6].structtype[0].field[7]
+datatype[6].structtype[0].field[0].name intfield
+datatype[6].structtype[0].field[0].id[0]
+datatype[6].structtype[0].field[0].datatype 0
+datatype[6].structtype[0].field[1].name rawfield
+datatype[6].structtype[0].field[1].id[0]
+datatype[6].structtype[0].field[1].datatype 3
+datatype[6].structtype[0].field[2].name doublefield
+datatype[6].structtype[0].field[2].id[0]
+datatype[6].structtype[0].field[2].datatype 5
+datatype[6].structtype[0].field[3].name bytefield
+datatype[6].structtype[0].field[3].id[0]
+datatype[6].structtype[0].field[3].datatype 16
+datatype[6].structtype[0].field[4].name arrayoffloatfield
+datatype[6].structtype[0].field[4].id[0]
+datatype[6].structtype[0].field[4].datatype 1001
+datatype[6].structtype[0].field[5].name arrayofarrayoffloatfield
+datatype[6].structtype[0].field[5].id[0]
+datatype[6].structtype[0].field[5].datatype 2001
+datatype[6].structtype[0].field[6].name docfield
+datatype[6].structtype[0].field[6].id[0]
+datatype[6].structtype[0].field[6].datatype 8
+datatype[6].documenttype[0]
+datatype[7].id 1306012852
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[0]
+datatype[7].documenttype[1]
+datatype[7].documenttype[0].name serializetest
+datatype[7].documenttype[0].version 0
+datatype[7].documenttype[0].inherits[0]
+datatype[7].documenttype[0].headerstruct -260050933
+datatype[7].documenttype[0].bodystruct 1026122976
diff --git a/document/src/tests/data/inheritancetest.cfg b/document/src/tests/data/inheritancetest.cfg
new file mode 100644
index 00000000000..1b2a704de92
--- /dev/null
+++ b/document/src/tests/data/inheritancetest.cfg
@@ -0,0 +1,354 @@
+enablecompression false
+documenttype[5]
+documenttype[0].bodystruct 380234823
+documenttype[0].headerstruct 1811691954
+documenttype[0].id -602119955
+documenttype[0].name "common"
+documenttype[0].version 0
+documenttype[0].annotationtype[0]
+documenttype[0].datatype[3]
+documenttype[0].datatype[0].id 1811691954
+documenttype[0].datatype[0].type STRUCT
+documenttype[0].datatype[0].sstruct.name "common.header"
+documenttype[0].datatype[0].sstruct.version 0
+documenttype[0].datatype[0].sstruct.field[3]
+documenttype[0].datatype[0].sstruct.field[0].datatype 0
+documenttype[0].datatype[0].sstruct.field[0].id 1501857786
+documenttype[0].datatype[0].sstruct.field[0].id_v6 1023904001
+documenttype[0].datatype[0].sstruct.field[0].name "popularity"
+documenttype[0].datatype[0].sstruct.field[1].datatype 2
+documenttype[0].datatype[0].sstruct.field[1].id 567626448
+documenttype[0].datatype[0].sstruct.field[1].id_v6 29129762
+documenttype[0].datatype[0].sstruct.field[1].name "title"
+documenttype[0].datatype[0].sstruct.field[2].datatype 0
+documenttype[0].datatype[0].sstruct.field[2].id 624630872
+documenttype[0].datatype[0].sstruct.field[2].id_v6 1508520108
+documenttype[0].datatype[0].sstruct.field[2].name "year"
+documenttype[0].datatype[1].id 380234823
+documenttype[0].datatype[1].type STRUCT
+documenttype[0].datatype[1].sstruct.name "common.body"
+documenttype[0].datatype[1].sstruct.version 0
+documenttype[0].datatype[1].sstruct.field[1]
+documenttype[0].datatype[1].sstruct.field[0].datatype 1
+documenttype[0].datatype[1].sstruct.field[0].id 1225171018
+documenttype[0].datatype[1].sstruct.field[0].id_v6 205748629
+documenttype[0].datatype[1].sstruct.field[0].name "price"
+documenttype[0].datatype[2].id 874542262
+documenttype[0].datatype[2].type STRUCT
+documenttype[0].datatype[2].sstruct.name "address"
+documenttype[0].datatype[2].sstruct.version 0
+documenttype[0].datatype[2].sstruct.field[2]
+documenttype[0].datatype[2].sstruct.field[0].datatype 2
+documenttype[0].datatype[2].sstruct.field[0].id 725636280
+documenttype[0].datatype[2].sstruct.field[0].id_v6 2127904
+documenttype[0].datatype[2].sstruct.field[0].name "city"
+documenttype[0].datatype[2].sstruct.field[1].datatype 2
+documenttype[0].datatype[2].sstruct.field[1].id 1954764240
+documenttype[0].datatype[2].sstruct.field[1].id_v6 1527533866
+documenttype[0].datatype[2].sstruct.field[1].name "street"
+documenttype[0].inherits[1]
+documenttype[0].inherits[0].id 8
+documenttype[1].bodystruct 993120973
+documenttype[1].headerstruct -1910204744
+documenttype[1].id 1412693671
+documenttype[1].name "music"
+documenttype[1].version 0
+documenttype[1].annotationtype[0]
+documenttype[1].datatype[5]
+documenttype[1].datatype[0].id 1328286588
+documenttype[1].datatype[0].type WSET
+documenttype[1].datatype[0].wset.createifnonexistent false
+documenttype[1].datatype[0].wset.removeifzero false
+documenttype[1].datatype[0].wset.key.id 2
+documenttype[1].datatype[1].id 993120973
+documenttype[1].datatype[1].type STRUCT
+documenttype[1].datatype[1].sstruct.name "music.body"
+documenttype[1].datatype[1].sstruct.version 0
+documenttype[1].datatype[1].sstruct.field[1]
+documenttype[1].datatype[1].sstruct.field[0].datatype 5
+documenttype[1].datatype[1].sstruct.field[0].id 552396752
+documenttype[1].datatype[1].sstruct.field[0].id_v6 713868363
+documenttype[1].datatype[1].sstruct.field[0].name "randomdouble"
+documenttype[1].datatype[2].id 874542262
+documenttype[1].datatype[2].type STRUCT
+documenttype[1].datatype[2].sstruct.name "address"
+documenttype[1].datatype[2].sstruct.version 0
+documenttype[1].datatype[2].sstruct.field[2]
+documenttype[1].datatype[2].sstruct.field[0].datatype 2
+documenttype[1].datatype[2].sstruct.field[0].id 725636280
+documenttype[1].datatype[2].sstruct.field[0].id_v6 2127904
+documenttype[1].datatype[2].sstruct.field[0].name "city"
+documenttype[1].datatype[2].sstruct.field[1].datatype 2
+documenttype[1].datatype[2].sstruct.field[1].id 1954764240
+documenttype[1].datatype[2].sstruct.field[1].id_v6 1527533866
+documenttype[1].datatype[2].sstruct.field[1].name "street"
+documenttype[1].datatype[3].id -1857943347
+documenttype[1].datatype[3].type ARRAY
+documenttype[1].datatype[3].array.element.id 874542262
+documenttype[1].datatype[4].id -1910204744
+documenttype[1].datatype[4].type STRUCT
+documenttype[1].datatype[4].sstruct.name "music.header"
+documenttype[1].datatype[4].sstruct.version 0
+documenttype[1].datatype[4].sstruct.field[6]
+documenttype[1].datatype[4].sstruct.field[0].datatype -1857943347
+documenttype[1].datatype[4].sstruct.field[0].id 785651450
+documenttype[1].datatype[4].sstruct.field[0].id_v6 1210851114
+documenttype[1].datatype[4].sstruct.field[0].name "addresses"
+documenttype[1].datatype[4].sstruct.field[1].datatype 1328286588
+documenttype[1].datatype[4].sstruct.field[1].id 1692189119
+documenttype[1].datatype[4].sstruct.field[1].id_v6 866139998
+documenttype[1].datatype[4].sstruct.field[1].name "albums"
+documenttype[1].datatype[4].sstruct.field[2].datatype 2
+documenttype[1].datatype[4].sstruct.field[2].id 51903611
+documenttype[1].datatype[4].sstruct.field[2].id_v6 1854279480
+documenttype[1].datatype[4].sstruct.field[2].name "artist"
+documenttype[1].datatype[4].sstruct.field[3].datatype 2
+documenttype[1].datatype[4].sstruct.field[3].id 306708139
+documenttype[1].datatype[4].sstruct.field[3].id_v6 1047427207
+documenttype[1].datatype[4].sstruct.field[3].name "phrase"
+documenttype[1].datatype[4].sstruct.field[4].datatype 874542262
+documenttype[1].datatype[4].sstruct.field[4].id 776401886
+documenttype[1].datatype[4].sstruct.field[4].id_v6 1741595836
+documenttype[1].datatype[4].sstruct.field[4].name "randomAddr"
+documenttype[1].datatype[4].sstruct.field[5].datatype 10
+documenttype[1].datatype[4].sstruct.field[5].id 2095970198
+documenttype[1].datatype[4].sstruct.field[5].id_v6 1826424031
+documenttype[1].datatype[4].sstruct.field[5].name "url"
+documenttype[1].inherits[2]
+documenttype[1].inherits[0].id -602119955
+documenttype[1].inherits[1].id 8
+documenttype[2].bodystruct -727249584
+documenttype[2].headerstruct -1623901061
+documenttype[2].id 1722744388
+documenttype[2].name "music_search"
+documenttype[2].version 0
+documenttype[2].annotationtype[0]
+documenttype[2].datatype[5]
+documenttype[2].datatype[0].id 1328286588
+documenttype[2].datatype[0].type WSET
+documenttype[2].datatype[0].wset.createifnonexistent false
+documenttype[2].datatype[0].wset.removeifzero false
+documenttype[2].datatype[0].wset.key.id 2
+documenttype[2].datatype[1].id 874542262
+documenttype[2].datatype[1].type STRUCT
+documenttype[2].datatype[1].sstruct.name "address"
+documenttype[2].datatype[1].sstruct.version 0
+documenttype[2].datatype[1].sstruct.field[2]
+documenttype[2].datatype[1].sstruct.field[0].datatype 2
+documenttype[2].datatype[1].sstruct.field[0].id 725636280
+documenttype[2].datatype[1].sstruct.field[0].id_v6 2127904
+documenttype[2].datatype[1].sstruct.field[0].name "city"
+documenttype[2].datatype[1].sstruct.field[1].datatype 2
+documenttype[2].datatype[1].sstruct.field[1].id 1954764240
+documenttype[2].datatype[1].sstruct.field[1].id_v6 1527533866
+documenttype[2].datatype[1].sstruct.field[1].name "street"
+documenttype[2].datatype[2].id -727249584
+documenttype[2].datatype[2].type STRUCT
+documenttype[2].datatype[2].sstruct.name "music_search.body"
+documenttype[2].datatype[2].sstruct.version 0
+documenttype[2].datatype[2].sstruct.field[2]
+documenttype[2].datatype[2].sstruct.field[0].datatype 1
+documenttype[2].datatype[2].sstruct.field[0].id 1225171018
+documenttype[2].datatype[2].sstruct.field[0].id_v6 205748629
+documenttype[2].datatype[2].sstruct.field[0].name "price"
+documenttype[2].datatype[2].sstruct.field[1].datatype 5
+documenttype[2].datatype[2].sstruct.field[1].id 552396752
+documenttype[2].datatype[2].sstruct.field[1].id_v6 713868363
+documenttype[2].datatype[2].sstruct.field[1].name "randomdouble"
+documenttype[2].datatype[3].id -1857943347
+documenttype[2].datatype[3].type ARRAY
+documenttype[2].datatype[3].array.element.id 874542262
+documenttype[2].datatype[4].id -1623901061
+documenttype[2].datatype[4].type STRUCT
+documenttype[2].datatype[4].sstruct.name "music_search.header"
+documenttype[2].datatype[4].sstruct.version 0
+documenttype[2].datatype[4].sstruct.field[11]
+documenttype[2].datatype[4].sstruct.field[00].datatype -1857943347
+documenttype[2].datatype[4].sstruct.field[00].id 785651450
+documenttype[2].datatype[4].sstruct.field[00].id_v6 1210851114
+documenttype[2].datatype[4].sstruct.field[00].name "addresses"
+documenttype[2].datatype[4].sstruct.field[01].datatype 1328286588
+documenttype[2].datatype[4].sstruct.field[01].id 1692189119
+documenttype[2].datatype[4].sstruct.field[01].id_v6 866139998
+documenttype[2].datatype[4].sstruct.field[01].name "albums"
+documenttype[2].datatype[4].sstruct.field[02].datatype 2
+documenttype[2].datatype[4].sstruct.field[02].id 51903611
+documenttype[2].datatype[4].sstruct.field[02].id_v6 1854279480
+documenttype[2].datatype[4].sstruct.field[02].name "artist"
+documenttype[2].datatype[4].sstruct.field[03].datatype 2
+documenttype[2].datatype[4].sstruct.field[03].id 306708139
+documenttype[2].datatype[4].sstruct.field[03].id_v6 1047427207
+documenttype[2].datatype[4].sstruct.field[03].name "phrase"
+documenttype[2].datatype[4].sstruct.field[04].datatype 0
+documenttype[2].datatype[4].sstruct.field[04].id 1501857786
+documenttype[2].datatype[4].sstruct.field[04].id_v6 1023904001
+documenttype[2].datatype[4].sstruct.field[04].name "popularity"
+documenttype[2].datatype[4].sstruct.field[05].datatype 874542262
+documenttype[2].datatype[4].sstruct.field[05].id 776401886
+documenttype[2].datatype[4].sstruct.field[05].id_v6 1741595836
+documenttype[2].datatype[4].sstruct.field[05].name "randomAddr"
+documenttype[2].datatype[4].sstruct.field[06].datatype 2
+documenttype[2].datatype[4].sstruct.field[06].id 1883197392
+documenttype[2].datatype[4].sstruct.field[06].id_v6 699950698
+documenttype[2].datatype[4].sstruct.field[06].name "rankfeatures"
+documenttype[2].datatype[4].sstruct.field[07].datatype 2
+documenttype[2].datatype[4].sstruct.field[07].id 1840337115
+documenttype[2].datatype[4].sstruct.field[07].id_v6 1981648971
+documenttype[2].datatype[4].sstruct.field[07].name "summaryfeatures"
+documenttype[2].datatype[4].sstruct.field[08].datatype 2
+documenttype[2].datatype[4].sstruct.field[08].id 567626448
+documenttype[2].datatype[4].sstruct.field[08].id_v6 29129762
+documenttype[2].datatype[4].sstruct.field[08].name "title"
+documenttype[2].datatype[4].sstruct.field[09].datatype 111553393
+documenttype[2].datatype[4].sstruct.field[09].id 2119414873
+documenttype[2].datatype[4].sstruct.field[09].id_v6 1826424031
+documenttype[2].datatype[4].sstruct.field[09].name "url"
+documenttype[2].datatype[4].sstruct.field[10].datatype 0
+documenttype[2].datatype[4].sstruct.field[10].id 624630872
+documenttype[2].datatype[4].sstruct.field[10].id_v6 1508520108
+documenttype[2].datatype[4].sstruct.field[10].name "year"
+documenttype[2].inherits[1]
+documenttype[2].inherits[0].id 8
+documenttype[3].bodystruct 1051291304
+documenttype[3].headerstruct -1843091501
+documenttype[3].id 64693740
+documenttype[3].name "books"
+documenttype[3].version 0
+documenttype[3].annotationtype[0]
+documenttype[3].datatype[5]
+documenttype[3].datatype[0].id 2125154557
+documenttype[3].datatype[0].type MAP
+documenttype[3].datatype[0].map.key.id 2
+documenttype[3].datatype[0].map.value.id 1
+documenttype[3].datatype[1].id 1051291304
+documenttype[3].datatype[1].type STRUCT
+documenttype[3].datatype[1].sstruct.name "books.body"
+documenttype[3].datatype[1].sstruct.version 0
+documenttype[3].datatype[1].sstruct.field[3]
+documenttype[3].datatype[1].sstruct.field[0].datatype 2125154557
+documenttype[3].datatype[1].sstruct.field[0].id 492235657
+documenttype[3].datatype[1].sstruct.field[0].id_v6 1647256015
+documenttype[3].datatype[1].sstruct.field[0].name "bindPrice"
+documenttype[3].datatype[1].sstruct.field[1].datatype 16
+documenttype[3].datatype[1].sstruct.field[1].id 1891624676
+documenttype[3].datatype[1].sstruct.field[1].id_v6 531118233
+documenttype[3].datatype[1].sstruct.field[1].name "grade"
+documenttype[3].datatype[1].sstruct.field[2].datatype 4
+documenttype[3].datatype[1].sstruct.field[2].id 782940634
+documenttype[3].datatype[1].sstruct.field[2].id_v6 698610188
+documenttype[3].datatype[1].sstruct.field[2].name "pages"
+documenttype[3].datatype[2].id 874542262
+documenttype[3].datatype[2].type STRUCT
+documenttype[3].datatype[2].sstruct.name "address"
+documenttype[3].datatype[2].sstruct.version 0
+documenttype[3].datatype[2].sstruct.field[2]
+documenttype[3].datatype[2].sstruct.field[0].datatype 2
+documenttype[3].datatype[2].sstruct.field[0].id 725636280
+documenttype[3].datatype[2].sstruct.field[0].id_v6 2127904
+documenttype[3].datatype[2].sstruct.field[0].name "city"
+documenttype[3].datatype[2].sstruct.field[1].datatype 2
+documenttype[3].datatype[2].sstruct.field[1].id 1954764240
+documenttype[3].datatype[2].sstruct.field[1].id_v6 1527533866
+documenttype[3].datatype[2].sstruct.field[1].name "street"
+documenttype[3].datatype[3].id -1857943347
+documenttype[3].datatype[3].type ARRAY
+documenttype[3].datatype[3].array.element.id 874542262
+documenttype[3].datatype[4].id -1843091501
+documenttype[3].datatype[4].type STRUCT
+documenttype[3].datatype[4].sstruct.name "books.header"
+documenttype[3].datatype[4].sstruct.version 0
+documenttype[3].datatype[4].sstruct.field[2]
+documenttype[3].datatype[4].sstruct.field[0].datatype -1857943347
+documenttype[3].datatype[4].sstruct.field[0].id 785651450
+documenttype[3].datatype[4].sstruct.field[0].id_v6 1210851114
+documenttype[3].datatype[4].sstruct.field[0].name "addresses"
+documenttype[3].datatype[4].sstruct.field[1].datatype 2
+documenttype[3].datatype[4].sstruct.field[1].id 644499292
+documenttype[3].datatype[4].sstruct.field[1].id_v6 177126295
+documenttype[3].datatype[4].sstruct.field[1].name "author"
+documenttype[3].inherits[2]
+documenttype[3].inherits[0].id -602119955
+documenttype[3].inherits[1].id 8
+documenttype[4].bodystruct -2128841451
+documenttype[4].headerstruct 66045696
+documenttype[4].id 1789857631
+documenttype[4].name "books_search"
+documenttype[4].version 0
+documenttype[4].annotationtype[0]
+documenttype[4].datatype[5]
+documenttype[4].datatype[0].id 2125154557
+documenttype[4].datatype[0].type MAP
+documenttype[4].datatype[0].map.key.id 2
+documenttype[4].datatype[0].map.value.id 1
+documenttype[4].datatype[1].id -2128841451
+documenttype[4].datatype[1].type STRUCT
+documenttype[4].datatype[1].sstruct.name "books_search.body"
+documenttype[4].datatype[1].sstruct.version 0
+documenttype[4].datatype[1].sstruct.field[3]
+documenttype[4].datatype[1].sstruct.field[0].datatype 2125154557
+documenttype[4].datatype[1].sstruct.field[0].id 492235657
+documenttype[4].datatype[1].sstruct.field[0].id_v6 1647256015
+documenttype[4].datatype[1].sstruct.field[0].name "bindPrice"
+documenttype[4].datatype[1].sstruct.field[1].datatype 16
+documenttype[4].datatype[1].sstruct.field[1].id 1891624676
+documenttype[4].datatype[1].sstruct.field[1].id_v6 531118233
+documenttype[4].datatype[1].sstruct.field[1].name "grade"
+documenttype[4].datatype[1].sstruct.field[2].datatype 1
+documenttype[4].datatype[1].sstruct.field[2].id 1225171018
+documenttype[4].datatype[1].sstruct.field[2].id_v6 205748629
+documenttype[4].datatype[1].sstruct.field[2].name "price"
+documenttype[4].datatype[2].id 874542262
+documenttype[4].datatype[2].type STRUCT
+documenttype[4].datatype[2].sstruct.name "address"
+documenttype[4].datatype[2].sstruct.version 0
+documenttype[4].datatype[2].sstruct.field[2]
+documenttype[4].datatype[2].sstruct.field[0].datatype 2
+documenttype[4].datatype[2].sstruct.field[0].id 725636280
+documenttype[4].datatype[2].sstruct.field[0].id_v6 2127904
+documenttype[4].datatype[2].sstruct.field[0].name "city"
+documenttype[4].datatype[2].sstruct.field[1].datatype 2
+documenttype[4].datatype[2].sstruct.field[1].id 1954764240
+documenttype[4].datatype[2].sstruct.field[1].id_v6 1527533866
+documenttype[4].datatype[2].sstruct.field[1].name "street"
+documenttype[4].datatype[3].id -1857943347
+documenttype[4].datatype[3].type ARRAY
+documenttype[4].datatype[3].array.element.id 874542262
+documenttype[4].datatype[4].id 66045696
+documenttype[4].datatype[4].type STRUCT
+documenttype[4].datatype[4].sstruct.name "books_search.header"
+documenttype[4].datatype[4].sstruct.version 0
+documenttype[4].datatype[4].sstruct.field[8]
+documenttype[4].datatype[4].sstruct.field[0].datatype -1857943347
+documenttype[4].datatype[4].sstruct.field[0].id 785651450
+documenttype[4].datatype[4].sstruct.field[0].id_v6 1210851114
+documenttype[4].datatype[4].sstruct.field[0].name "addresses"
+documenttype[4].datatype[4].sstruct.field[1].datatype 2
+documenttype[4].datatype[4].sstruct.field[1].id 644499292
+documenttype[4].datatype[4].sstruct.field[1].id_v6 177126295
+documenttype[4].datatype[4].sstruct.field[1].name "author"
+documenttype[4].datatype[4].sstruct.field[2].datatype 4
+documenttype[4].datatype[4].sstruct.field[2].id 782940634
+documenttype[4].datatype[4].sstruct.field[2].id_v6 698610188
+documenttype[4].datatype[4].sstruct.field[2].name "pages"
+documenttype[4].datatype[4].sstruct.field[3].datatype 0
+documenttype[4].datatype[4].sstruct.field[3].id 1501857786
+documenttype[4].datatype[4].sstruct.field[3].id_v6 1023904001
+documenttype[4].datatype[4].sstruct.field[3].name "popularity"
+documenttype[4].datatype[4].sstruct.field[4].datatype 2
+documenttype[4].datatype[4].sstruct.field[4].id 1883197392
+documenttype[4].datatype[4].sstruct.field[4].id_v6 699950698
+documenttype[4].datatype[4].sstruct.field[4].name "rankfeatures"
+documenttype[4].datatype[4].sstruct.field[5].datatype 2
+documenttype[4].datatype[4].sstruct.field[5].id 1840337115
+documenttype[4].datatype[4].sstruct.field[5].id_v6 1981648971
+documenttype[4].datatype[4].sstruct.field[5].name "summaryfeatures"
+documenttype[4].datatype[4].sstruct.field[6].datatype 2
+documenttype[4].datatype[4].sstruct.field[6].id 567626448
+documenttype[4].datatype[4].sstruct.field[6].id_v6 29129762
+documenttype[4].datatype[4].sstruct.field[6].name "title"
+documenttype[4].datatype[4].sstruct.field[7].datatype 0
+documenttype[4].datatype[4].sstruct.field[7].id 624630872
+documenttype[4].datatype[4].sstruct.field[7].id_v6 1508520108
+documenttype[4].datatype[4].sstruct.field[7].name "year"
+documenttype[4].inherits[1]
+documenttype[4].inherits[0].id 8
diff --git a/document/src/tests/data/serialize-fieldpathupdate-cpp.dat b/document/src/tests/data/serialize-fieldpathupdate-cpp.dat
new file mode 100644
index 00000000000..cf5f20f7a0c
--- /dev/null
+++ b/document/src/tests/data/serialize-fieldpathupdate-cpp.dat
Binary files differ
diff --git a/document/src/tests/data/serialize-fieldpathupdate-java.dat b/document/src/tests/data/serialize-fieldpathupdate-java.dat
new file mode 100644
index 00000000000..cf5f20f7a0c
--- /dev/null
+++ b/document/src/tests/data/serialize-fieldpathupdate-java.dat
Binary files differ
diff --git a/document/src/tests/data/serializejava-compressed.dat b/document/src/tests/data/serializejava-compressed.dat
new file mode 100644
index 00000000000..5c8721c097c
--- /dev/null
+++ b/document/src/tests/data/serializejava-compressed.dat
Binary files differ
diff --git a/document/src/tests/data/serializejava.dat b/document/src/tests/data/serializejava.dat
new file mode 100644
index 00000000000..3dd9f8fcd52
--- /dev/null
+++ b/document/src/tests/data/serializejava.dat
Binary files differ
diff --git a/document/src/tests/data/serializejavawithannotations.dat b/document/src/tests/data/serializejavawithannotations.dat
new file mode 100644
index 00000000000..a648b72404f
--- /dev/null
+++ b/document/src/tests/data/serializejavawithannotations.dat
Binary files differ
diff --git a/document/src/tests/data/serializeupdatecpp.dat b/document/src/tests/data/serializeupdatecpp.dat
new file mode 100644
index 00000000000..73d83b2ab45
--- /dev/null
+++ b/document/src/tests/data/serializeupdatecpp.dat
Binary files differ
diff --git a/document/src/tests/data/serializeupdatejava.dat b/document/src/tests/data/serializeupdatejava.dat
new file mode 100644
index 00000000000..e2a98d42fb1
--- /dev/null
+++ b/document/src/tests/data/serializeupdatejava.dat
Binary files differ
diff --git a/document/src/tests/data/serializev6.dat b/document/src/tests/data/serializev6.dat
new file mode 100644
index 00000000000..555acb9ecc7
--- /dev/null
+++ b/document/src/tests/data/serializev6.dat
Binary files differ
diff --git a/document/src/tests/data/variablesizedocument.cfg b/document/src/tests/data/variablesizedocument.cfg
new file mode 100644
index 00000000000..959f6b7b12d
--- /dev/null
+++ b/document/src/tests/data/variablesizedocument.cfg
@@ -0,0 +1,34 @@
+enablecompression false
+datatype[3]
+datatype[0].id -1633334123
+datatype[0].arraytype[0]
+datatype[0].weightedsettype[0]
+datatype[0].structtype[1]
+datatype[0].structtype[0].name varsize.header
+datatype[0].structtype[0].version 0
+datatype[0].structtype[0].field[1]
+datatype[0].structtype[0].field[0].name headerstring
+datatype[0].structtype[0].field[0].id[0]
+datatype[0].structtype[0].field[0].datatype 2
+datatype[0].documenttype[0]
+datatype[1].id -785359894
+datatype[1].arraytype[0]
+datatype[1].weightedsettype[0]
+datatype[1].structtype[1]
+datatype[1].structtype[0].name varsize.body
+datatype[1].structtype[0].version 0
+datatype[1].structtype[0].field[1]
+datatype[1].structtype[0].field[0].name bodystring
+datatype[1].structtype[0].field[0].id[0]
+datatype[1].structtype[0].field[0].datatype 2
+datatype[1].documenttype[0]
+datatype[2].id 211908458
+datatype[2].arraytype[0]
+datatype[2].weightedsettype[0]
+datatype[2].structtype[0]
+datatype[2].documenttype[1]
+datatype[2].documenttype[0].name varsize
+datatype[2].documenttype[0].version 0
+datatype[2].documenttype[0].inherits[0]
+datatype[2].documenttype[0].headerstruct -1633334123
+datatype[2].documenttype[0].bodystruct -785359894
diff --git a/document/src/tests/data/versionscfg.txt b/document/src/tests/data/versionscfg.txt
new file mode 100644
index 00000000000..a79e6040bc4
--- /dev/null
+++ b/document/src/tests/data/versionscfg.txt
@@ -0,0 +1,176 @@
+enablecompression false
+datatype[14]
+datatype[0].id 101
+datatype[0].arraytype[1]
+datatype[0].arraytype[0].datatype 1
+datatype[0].weightedsettype[0]
+datatype[0].structtype[0]
+datatype[0].documenttype[0]
+datatype[1].id 102
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 2
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 1000
+datatype[2].arraytype[0]
+datatype[2].weightedsettype[0]
+datatype[2].structtype[1]
+datatype[2].structtype[0].name crawler_type.header
+datatype[2].structtype[0].version 0
+datatype[2].structtype[0].field[5]
+datatype[2].structtype[0].field[0].name URI
+datatype[2].structtype[0].field[0].id[0]
+datatype[2].structtype[0].field[0].datatype 2
+datatype[2].structtype[0].field[1].name CONTEXT
+datatype[2].structtype[0].field[1].id[0]
+datatype[2].structtype[0].field[1].datatype 2
+datatype[2].structtype[0].field[2].name CONTENT
+datatype[2].structtype[0].field[2].id[0]
+datatype[2].structtype[0].field[2].datatype 3
+datatype[2].structtype[0].field[3].name CONTENT_TYPE
+datatype[2].structtype[0].field[3].id[0]
+datatype[2].structtype[0].field[3].datatype 2
+datatype[2].structtype[0].field[4].name LAST_MODIFIED
+datatype[2].structtype[0].field[4].id[0]
+datatype[2].structtype[0].field[4].datatype 0
+datatype[2].documenttype[0]
+datatype[3].id 1001
+datatype[3].arraytype[0]
+datatype[3].weightedsettype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].name crawler_type.body
+datatype[3].structtype[0].version 0
+datatype[3].structtype[0].field[0]
+datatype[3].documenttype[0]
+datatype[4].id 1002
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[0]
+datatype[4].documenttype[1]
+datatype[4].documenttype[0].name crawler_type
+datatype[4].documenttype[0].version 0
+datatype[4].documenttype[0].inherits[0]
+datatype[4].documenttype[0].headerstruct 1000
+datatype[4].documenttype[0].bodystruct 1001
+datatype[5].id 2000
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].name crawler_type.header
+datatype[5].structtype[0].version 2
+datatype[5].structtype[0].field[3]
+datatype[5].structtype[0].field[0].name URI
+datatype[5].structtype[0].field[0].id[0]
+datatype[5].structtype[0].field[0].datatype 2
+datatype[5].structtype[0].field[1].name CONTEXT
+datatype[5].structtype[0].field[1].id[0]
+datatype[5].structtype[0].field[1].datatype 2
+datatype[5].structtype[0].field[2].name CONTENT
+datatype[5].structtype[0].field[2].id[0]
+datatype[5].structtype[0].field[2].datatype 3
+datatype[5].documenttype[0]
+datatype[6].id 2001
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[0]
+datatype[6].structtype[1]
+datatype[6].structtype[0].name crawler_type.body
+datatype[6].structtype[0].version 2
+datatype[6].structtype[0].field[0]
+datatype[6].documenttype[0]
+datatype[7].id 2002
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[0]
+datatype[7].documenttype[1]
+datatype[7].documenttype[0].name crawler_type
+datatype[7].documenttype[0].version 2
+datatype[7].documenttype[0].inherits[0]
+datatype[7].documenttype[0].headerstruct 2000
+datatype[7].documenttype[0].bodystruct 2001
+datatype[8].id 3000
+datatype[8].arraytype[0]
+datatype[8].weightedsettype[0]
+datatype[8].structtype[1]
+datatype[8].structtype[0].name multimedia_type.header
+datatype[8].structtype[0].version 0
+datatype[8].structtype[0].field[5]
+datatype[8].structtype[0].field[0].name URL_KEYWORDS
+datatype[8].structtype[0].field[0].id[0]
+datatype[8].structtype[0].field[0].datatype 102
+datatype[8].structtype[0].field[1].name MULTIMEDIA_LINKTYPES
+datatype[8].structtype[0].field[1].id[0]
+datatype[8].structtype[0].field[1].datatype 102
+datatype[8].structtype[0].field[2].name THUMBNAIL_URLS
+datatype[8].structtype[0].field[2].id[0]
+datatype[8].structtype[0].field[2].datatype 102
+datatype[8].structtype[0].field[3].name FROM_LINK_LIST
+datatype[8].structtype[0].field[3].id[0]
+datatype[8].structtype[0].field[3].datatype 101
+datatype[8].structtype[0].field[4].name FROM_LINK_LANGUAGE_LIST
+datatype[8].structtype[0].field[4].id[0]
+datatype[8].structtype[0].field[4].datatype 102
+datatype[8].documenttype[0]
+datatype[9].id 3001
+datatype[9].arraytype[0]
+datatype[9].weightedsettype[0]
+datatype[9].structtype[1]
+datatype[9].structtype[0].name multimedia_type.body
+datatype[9].structtype[0].version 0
+datatype[9].structtype[0].field[0]
+datatype[9].documenttype[0]
+datatype[10].id 3002
+datatype[10].arraytype[0]
+datatype[10].weightedsettype[0]
+datatype[10].structtype[0]
+datatype[10].documenttype[1]
+datatype[10].documenttype[0].name multimedia_type
+datatype[10].documenttype[0].version 0
+datatype[10].documenttype[0].inherits[1]
+datatype[10].documenttype[0].inherits[0].name crawler_type
+datatype[10].documenttype[0].inherits[0].version 0
+datatype[10].documenttype[0].headerstruct 3000
+datatype[10].documenttype[0].bodystruct 3001
+datatype[11].id 4000
+datatype[11].arraytype[0]
+datatype[11].weightedsettype[0]
+datatype[11].structtype[1]
+datatype[11].structtype[0].name multimedia_type.header
+datatype[11].structtype[0].version 2
+datatype[11].structtype[0].field[5]
+datatype[11].structtype[0].field[0].name URL_KEYWORDS
+datatype[11].structtype[0].field[0].id[0]
+datatype[11].structtype[0].field[0].datatype 102
+datatype[11].structtype[0].field[1].name MULTIMEDIA_LINKTYPES
+datatype[11].structtype[0].field[1].id[0]
+datatype[11].structtype[0].field[1].datatype 102
+datatype[11].structtype[0].field[2].name THUMBNAIL_URIS
+datatype[11].structtype[0].field[2].id[0]
+datatype[11].structtype[0].field[2].datatype 102
+datatype[11].structtype[0].field[3].name FROM_LINK_LIST
+datatype[11].structtype[0].field[3].id[0]
+datatype[11].structtype[0].field[3].datatype 101
+datatype[11].structtype[0].field[4].name FROM_LINK_LANGUAGE_LIST
+datatype[11].structtype[0].field[4].id[0]
+datatype[11].structtype[0].field[4].datatype 102
+datatype[11].documenttype[0]
+datatype[12].id 4001
+datatype[12].arraytype[0]
+datatype[12].weightedsettype[0]
+datatype[12].structtype[1]
+datatype[12].structtype[0].name multimedia_type.body
+datatype[12].structtype[0].version 2
+datatype[12].structtype[0].field[0]
+datatype[12].documenttype[0]
+datatype[13].id 4002
+datatype[13].arraytype[0]
+datatype[13].weightedsettype[0]
+datatype[13].structtype[0]
+datatype[13].documenttype[1]
+datatype[13].documenttype[0].name multimedia_type
+datatype[13].documenttype[0].version 2
+datatype[13].documenttype[0].inherits[1]
+datatype[13].documenttype[0].inherits[0].name crawler_type
+datatype[13].documenttype[0].inherits[0].version 2
+datatype[13].documenttype[0].headerstruct 4000
+datatype[13].documenttype[0].bodystruct 4001
diff --git a/document/src/tests/datatype/.gitignore b/document/src/tests/datatype/.gitignore
new file mode 100644
index 00000000000..bbfc05cb12e
--- /dev/null
+++ b/document/src/tests/datatype/.gitignore
@@ -0,0 +1,5 @@
+*.So
+*_test
+.depend
+Makefile
+document_datatype_test_app
diff --git a/document/src/tests/datatype/CMakeLists.txt b/document/src/tests/datatype/CMakeLists.txt
new file mode 100644
index 00000000000..399bccdf691
--- /dev/null
+++ b/document/src/tests/datatype/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_datatype_test_app
+ SOURCES
+ datatype_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_datatype_test_app COMMAND document_datatype_test_app)
diff --git a/document/src/tests/datatype/datatype_test.cpp b/document/src/tests/datatype/datatype_test.cpp
new file mode 100644
index 00000000000..d084e4f90ef
--- /dev/null
+++ b/document/src/tests/datatype/datatype_test.cpp
@@ -0,0 +1,64 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for datatype.
+
+#include <vespa/log/log.h>
+LOG_SETUP("datatype_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/base/field.h>
+#include <vespa/document/datatype/arraydatatype.h>
+#include <vespa/document/datatype/structdatatype.h>
+#include <vespa/document/fieldvalue/longfieldvalue.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace document;
+
+namespace {
+
+TEST("require that ArrayDataType can be assigned to.") {
+ ArrayDataType type1(*DataType::STRING);
+ ArrayDataType type2(*DataType::INT);
+ type1 = type1;
+ EXPECT_EQUAL(*DataType::STRING, type1.getNestedType());
+ type1 = type2;
+ EXPECT_EQUAL(*DataType::INT, type1.getNestedType());
+}
+
+TEST("require that ArrayDataType can be cloned.") {
+ ArrayDataType type1(*DataType::STRING);
+ std::unique_ptr<ArrayDataType> type2(type1.clone());
+ ASSERT_TRUE(type2.get());
+ EXPECT_EQUAL(*DataType::STRING, type2->getNestedType());
+}
+
+TEST("require that assignment operator works for LongFieldValue") {
+ LongFieldValue val;
+ val = "1";
+ EXPECT_EQUAL(1, val.getValue());
+ val = 2;
+ EXPECT_EQUAL(2, val.getValue());
+ val = static_cast<int64_t>(3);
+ EXPECT_EQUAL(3, val.getValue());
+ val = 4.0f;
+ EXPECT_EQUAL(4, val.getValue());
+ val = 5.0;
+ EXPECT_EQUAL(5, val.getValue());
+}
+
+TEST("require that StructDataType can redeclare identical fields.") {
+ StructDataType s("foo");
+ Field field1("field1", 42, *DataType::STRING, true);
+ Field field2("field2", 42, *DataType::STRING, true);
+
+ s.addField(field1);
+ s.addField(field1); // ok
+ s.addInheritedField(field1); // ok
+ EXPECT_EXCEPTION(s.addField(field2), vespalib::IllegalArgumentException,
+ "Field id in use by field Field(field1");
+ s.addInheritedField(field2);
+ EXPECT_FALSE(s.hasField(field2.getName()));
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/documentcalculatortestcase.cpp b/document/src/tests/documentcalculatortestcase.cpp
new file mode 100644
index 00000000000..00de5427961
--- /dev/null
+++ b/document/src/tests/documentcalculatortestcase.cpp
@@ -0,0 +1,202 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/base/testdocrepo.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/vespalib/io/fileutil.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/document/base/documentcalculator.h>
+#include <vespa/document/fieldvalue/intfieldvalue.h>
+#include <vespa/document/fieldvalue/longfieldvalue.h>
+#include <vespa/document/fieldvalue/floatfieldvalue.h>
+
+#include <fstream>
+
+namespace document {
+
+class DocumentCalculatorTest : public CppUnit::TestFixture {
+ TestDocRepo _testRepo;
+
+public:
+ const DocumentTypeRepo &getRepo() { return _testRepo.getTypeRepo(); }
+
+ void setUp() {}
+ void tearDown() {}
+
+ void testConstant();
+ void testSimple();
+ void testVariables();
+ void testFields();
+ void testDivideByZero();
+ void testModByZero();
+ void testFieldsDivZero();
+ void testFieldNotSet();
+ void testFieldNotFound();
+ void testByteSubtractionZeroResult();
+
+ CPPUNIT_TEST_SUITE(DocumentCalculatorTest);
+ CPPUNIT_TEST(testConstant);
+ CPPUNIT_TEST(testSimple);
+ CPPUNIT_TEST(testVariables);
+ CPPUNIT_TEST(testFields);
+ CPPUNIT_TEST(testDivideByZero);
+ CPPUNIT_TEST(testModByZero);
+ CPPUNIT_TEST(testFieldsDivZero);
+ CPPUNIT_TEST(testFieldNotSet);
+ CPPUNIT_TEST(testFieldNotFound);
+ CPPUNIT_TEST(testByteSubtractionZeroResult);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DocumentCalculatorTest);
+
+
+void
+DocumentCalculatorTest::testConstant() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(), "4.0");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ CPPUNIT_ASSERT_EQUAL(4.0, calc.evaluate(doc, variables));
+}
+
+void
+DocumentCalculatorTest::testSimple() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(), "(3 + 5) / 2");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ CPPUNIT_ASSERT_EQUAL(4.0, calc.evaluate(doc, variables));
+}
+
+void
+DocumentCalculatorTest::testVariables() {
+ DocumentCalculator::VariableMap variables;
+ variables["x"] = 3.0;
+ variables["y"] = 5.0;
+ DocumentCalculator calc(getRepo(), "($x + $y) / 2");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ CPPUNIT_ASSERT_EQUAL(4.0, calc.evaluate(doc, variables));
+}
+
+void
+DocumentCalculatorTest::testFields() {
+ DocumentCalculator::VariableMap variables;
+ variables["x"] = 3.0;
+ variables["y"] = 5.0;
+ DocumentCalculator calc(getRepo(), "(testdoctype1.headerval + testdoctype1"
+ ".hfloatval) / testdoctype1.headerlongval");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ doc.setValue(doc.getField("headerval"), IntFieldValue(5));
+ doc.setValue(doc.getField("hfloatval"), FloatFieldValue(3.0));
+ doc.setValue(doc.getField("headerlongval"), LongFieldValue(2));
+ CPPUNIT_ASSERT_EQUAL(4.0, calc.evaluate(doc, variables));
+}
+
+void
+DocumentCalculatorTest::testFieldsDivZero() {
+ DocumentCalculator::VariableMap variables;
+ variables["x"] = 3.0;
+ variables["y"] = 5.0;
+ DocumentCalculator calc(getRepo(), "(testdoctype1.headerval + testdoctype1"
+ ".hfloatval) / testdoctype1.headerlongval");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ doc.setValue(doc.getField("headerval"), IntFieldValue(5));
+ doc.setValue(doc.getField("hfloatval"), FloatFieldValue(3.0));
+ doc.setValue(doc.getField("headerlongval"), LongFieldValue(0));
+ try {
+ calc.evaluate(doc, variables);
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException& e) {
+ // OK
+ }
+}
+
+void
+DocumentCalculatorTest::testDivideByZero() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(), "(3 + 5) / 0");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ try {
+ calc.evaluate(doc, variables);
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException& e) {
+ // OK
+ }
+}
+
+void
+DocumentCalculatorTest::testModByZero() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(), "(3 + 5) % 0");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ try {
+ calc.evaluate(doc, variables);
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException& e) {
+ // OK
+ }
+}
+
+void
+DocumentCalculatorTest::testFieldNotSet() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(), "(testdoctype1.headerval + testdoctype1"
+ ".hfloatval) / testdoctype1.headerlongval");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ doc.setValue(doc.getField("hfloatval"), FloatFieldValue(3.0));
+ doc.setValue(doc.getField("headerlongval"), LongFieldValue(2));
+ try {
+ calc.evaluate(doc, variables);
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException&) {
+ // OK
+ }
+}
+
+void
+DocumentCalculatorTest::testFieldNotFound() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(),
+ "(testdoctype1.mynotfoundfield + testdoctype1"
+ ".hfloatval) / testdoctype1.headerlongval");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ doc.setValue(doc.getField("hfloatval"), FloatFieldValue(3.0));
+ doc.setValue(doc.getField("headerlongval"), LongFieldValue(2));
+ try {
+ calc.evaluate(doc, variables);
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException&) {
+ // OK
+ }
+}
+
+void
+DocumentCalculatorTest::testByteSubtractionZeroResult() {
+ DocumentCalculator::VariableMap variables;
+ DocumentCalculator calc(getRepo(), "testdoctype1.byteval - 3");
+
+ Document doc(*_testRepo.getDocumentType("testdoctype1"),
+ DocumentId("doc:test:foo"));
+ doc.setValue(doc.getField("byteval"), ByteFieldValue(3));
+ CPPUNIT_ASSERT_EQUAL(0.0, calc.evaluate(doc, variables));
+}
+
+}
diff --git a/document/src/tests/documentidtest.cpp b/document/src/tests/documentidtest.cpp
new file mode 100644
index 00000000000..fd60c6c436b
--- /dev/null
+++ b/document/src/tests/documentidtest.cpp
@@ -0,0 +1,183 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <sstream>
+#include <string>
+#include <vespa/document/bucket/bucketidfactory.h>
+#include <vespa/document/base/documentid.h>
+#include <vespa/document/datatype/documenttype.h>
+#include <vespa/vespalib/util/md5.h>
+
+using document::VespaDocumentDeserializer;
+using document::VespaDocumentSerializer;
+using vespalib::nbostream;
+
+namespace document {
+
+struct DocumentIdTest : public CppUnit::TestFixture {
+ void generateJavaComplianceFile();
+ void testOutput();
+ void testEqualityOperator();
+ void testCopying();
+ void testParseId();
+ void checkNtnuGlobalId();
+ void testDocGlobalId();
+ void freestandingLocationFromGroupNameFuncMatchesIdLocation();
+
+ CPPUNIT_TEST_SUITE(DocumentIdTest);
+ CPPUNIT_TEST(testEqualityOperator);
+ CPPUNIT_TEST(testOutput);
+ CPPUNIT_TEST(testCopying);
+ CPPUNIT_TEST(generateJavaComplianceFile);
+ CPPUNIT_TEST(testParseId);
+ CPPUNIT_TEST(checkNtnuGlobalId);
+ CPPUNIT_TEST(testDocGlobalId);
+ CPPUNIT_TEST(freestandingLocationFromGroupNameFuncMatchesIdLocation);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DocumentIdTest);
+
+namespace {
+ void writeGlobalIdBucketId(std::ostream& out, const std::string& id) {
+ BucketIdFactory factory;
+ out << id << " - " << document::DocumentId(id).getGlobalId()
+ << " - " << factory.getBucketId(document::DocumentId(id)).toString()
+ << "\n";
+ }
+}
+
+void DocumentIdTest::generateJavaComplianceFile()
+{
+ { // Generate file with globalids and bucket ID of various document ids,
+ // which java will use to ensure equal implementations.
+ std::ostringstream ost;
+ writeGlobalIdBucketId(ost, "doc:ns:specific");
+ writeGlobalIdBucketId(ost, "doc:another:specific");
+ writeGlobalIdBucketId(ost, "doc:ns:another");
+ writeGlobalIdBucketId(ost, "userdoc:ns:100:specific");
+ writeGlobalIdBucketId(ost, "userdoc:np:100:another");
+ writeGlobalIdBucketId(ost, "userdoc:ns:101:specific");
+ writeGlobalIdBucketId(ost, "groupdoc:ns:agroup:specific");
+ writeGlobalIdBucketId(ost, "groupdoc:np:agroup:another");
+ writeGlobalIdBucketId(ost, "groupdoc:ns:another:specific");
+ for (uint32_t i=0; i<20; ++i) {
+ std::ostringstream ost2;
+ ost2 << i;
+ writeGlobalIdBucketId(ost, "doc:ns:"+ost2.str());
+ }
+ writeGlobalIdBucketId(ost, "id:ns:type::specific");
+ writeGlobalIdBucketId(ost, "id:another:type::specific");
+ writeGlobalIdBucketId(ost, "id:ns:type::another");
+ writeGlobalIdBucketId(ost, "id:ns:type:n=100:specific");
+ writeGlobalIdBucketId(ost, "id:np:type:n=100:another");
+ writeGlobalIdBucketId(ost, "id:ns:type:n=101:specific");
+ writeGlobalIdBucketId(ost, "id:ns:type:g=agroup:specific");
+ writeGlobalIdBucketId(ost, "id:np:type:g=agroup:another");
+ writeGlobalIdBucketId(ost, "id:ns:type:g=another:specific");
+ FastOS_File file;
+ CPPUNIT_ASSERT(file.OpenWriteOnlyTruncate("cpp-globalidbucketids.txt"));
+ std::string content(ost.str());
+ CPPUNIT_ASSERT(file.CheckedWrite(content.c_str(), content.size()));
+ CPPUNIT_ASSERT(file.Close());
+ }
+}
+
+
+void DocumentIdTest::testOutput()
+{
+ DocumentId id(DocIdString("crawler", "http://www.yahoo.com"));
+
+ std::ostringstream ost;
+ ost << id;
+ std::string expected("doc:crawler:http://www.yahoo.com");
+ CPPUNIT_ASSERT_EQUAL(expected, ost.str());
+
+ CPPUNIT_ASSERT_EQUAL(vespalib::string(expected), id.toString());
+
+ expected = "DocumentId(id = doc:crawler:http://www.yahoo.com, "
+ "gid(0x928baffb39cf32004542fb60))";
+ CPPUNIT_ASSERT_EQUAL(expected, static_cast<Printable&>(id).toString(true));
+}
+
+namespace {
+ template<class T>
+ std::string getNotEqualMessage(const T& t1, const T& t2) {
+ std::ostringstream ost;
+ ost << "Expected instances to be different. This was not the case:\n"
+ << t1 << "\n" << t2 << "\n";
+ return ost.str();
+ }
+}
+
+void DocumentIdTest::testEqualityOperator()
+{
+ std::string uri(DocIdString("crawler", "http://www.yahoo.com").toString());
+
+ DocumentId id1(uri);
+ DocumentId id2(uri);
+ DocumentId id3("doc:crawler:http://www.yahoo.no/");
+
+ CPPUNIT_ASSERT_EQUAL(id1, id2);
+ CPPUNIT_ASSERT_MESSAGE(getNotEqualMessage(id1, id3), !(id1 == id3));
+}
+
+void DocumentIdTest::testCopying()
+{
+ std::string uri(DocIdString("crawler", "http://www.yahoo.com/").toString());
+
+ DocumentId id1(uri);
+ DocumentId id2(id1);
+ DocumentId id3("doc:ns:foo");
+ id3 = id2;
+
+ CPPUNIT_ASSERT_EQUAL(id1, id2);
+ CPPUNIT_ASSERT_EQUAL(id1, id3);
+}
+
+void
+DocumentIdTest::testParseId()
+{
+ // Moved to base/documentid_test.cpp
+}
+
+void
+DocumentIdTest::checkNtnuGlobalId()
+{
+ DocumentId id("doc:crawler:http://www.ntnu.no/");
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("gid(0xb8863740be14221c0ac77896)"),
+ id.getGlobalId().toString());
+}
+
+void
+DocumentIdTest::testDocGlobalId()
+{
+ // Test that location of doc scheme documents are set correctly, such
+ // that the location is the first bytes of the original GID.
+ std::string id("doc:crawler:http://www.ntnu.no/");
+ DocumentId did(id);
+
+ unsigned char key[16];
+ fastc_md5sum(reinterpret_cast<const unsigned char*>(id.c_str()),
+ id.size(), key);
+
+ CPPUNIT_ASSERT_EQUAL(GlobalId(key), did.getGlobalId());
+}
+
+void
+DocumentIdTest::freestandingLocationFromGroupNameFuncMatchesIdLocation()
+{
+ CPPUNIT_ASSERT_EQUAL(
+ DocumentId("id::foo:g=zoid:bar").getScheme().getLocation(),
+ GroupDocIdString::locationFromGroupName("zoid"));
+ CPPUNIT_ASSERT_EQUAL(
+ DocumentId("id::bar:g=doink:baz").getScheme().getLocation(),
+ GroupDocIdString::locationFromGroupName("doink"));
+}
+
+} // document
diff --git a/document/src/tests/documentselectparsertest.cpp b/document/src/tests/documentselectparsertest.cpp
new file mode 100644
index 00000000000..357e6eb2a68
--- /dev/null
+++ b/document/src/tests/documentselectparsertest.cpp
@@ -0,0 +1,1278 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+//#include <boost/regex/icu.hpp>
+#include <vespa/fastos/fastos.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <iostream>
+#include <memory>
+#include <vespa/document/update/assignvalueupdate.h>
+#include <vespa/document/base/testdocman.h>
+#include <vespa/document/base/testdocrepo.h>
+#include <vespa/document/select/parser.h>
+#include <vespa/document/select/visitor.h>
+#include <vespa/document/select/bodyfielddetector.h>
+#include <vespa/document/select/valuenode.h>
+#include <vespa/document/select/branch.h>
+#include <vespa/document/select/simpleparser.h>
+#include <vespa/document/select/constant.h>
+#include <vespa/document/select/invalidconstant.h>
+#include <vespa/document/select/doctype.h>
+#include <vespa/document/select/compare.h>
+
+using namespace document::config_builder;
+
+namespace document {
+
+class DocumentSelectParserTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(DocumentSelectParserTest);
+ CPPUNIT_TEST(testParseTerminals);
+ CPPUNIT_TEST(testParseBranches);
+ CPPUNIT_TEST(testOperators);
+ CPPUNIT_TEST(testVisitor);
+ CPPUNIT_TEST(testUtf8);
+ CPPUNIT_TEST(testBodyFieldDetection);
+ CPPUNIT_TEST(testDocumentUpdates);
+ CPPUNIT_TEST_SUITE_END();
+
+ BucketIdFactory _bucketIdFactory;
+ std::unique_ptr<select::Parser> _parser;
+ std::vector<Document::SP > _doc;
+ std::vector<DocumentUpdate::SP > _update;
+
+ Document::SP createDoc(
+ const std::string& doctype, const std::string& id, uint32_t hint,
+ double hfloat, const std::string& hstr, const std::string& cstr,
+ uint64_t hlong = 0);
+
+ DocumentUpdate::SP createUpdate(
+ const std::string& doctype, const std::string& id, uint32_t hint,
+ const std::string& hstr);
+
+ template <typename ContainsType>
+ select::ResultList doParse(const vespalib::stringref& expr,
+ const ContainsType& t);
+public:
+
+ DocumentSelectParserTest()
+ : _bucketIdFactory() {}
+
+ void setUp();
+ void tearDown() {}
+ void createDocs();
+
+ void testParseTerminals();
+ void testParseBranches();
+ void testOperators();
+ void testOperators0();
+ void testOperators1();
+ void testOperators2();
+ void testOperators3();
+ void testOperators4();
+ void testOperators5();
+ void testOperators6();
+ void testOperators7();
+ void testOperators8();
+ void testOperators9();
+ void testVisitor();
+ void testUtf8();
+ void testBodyFieldDetection();
+ void testDocumentUpdates();
+ void testDocumentUpdates0();
+ void testDocumentUpdates1();
+ void testDocumentUpdates2();
+ void testDocumentUpdates3();
+ void testDocumentUpdates4();
+ void testDocumentUpdates5();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DocumentSelectParserTest);
+
+namespace {
+ DocumentTypeRepo::SP _repo;
+}
+
+void DocumentSelectParserTest::setUp()
+{
+ DocumenttypesConfigBuilderHelper builder(TestDocRepo::getDefaultConfig());
+ builder.document(535424777, "notandor",
+ Struct("notandor.header"), Struct("notandor.body"));
+ builder.document(1348665801, "ornotand",
+ Struct("ornotand.header"), Struct("ornotand.body"));
+ builder.document(-1848670693, "andornot",
+ Struct("andornot.header"), Struct("andornot.body"));
+ builder.document(-1193328712, "idid",
+ Struct("idid.header"), Struct("idid.body"));
+ builder.document(-1673092522, "usergroup",
+ Struct("usergroup.header"),
+ Struct("usergroup.body"));
+ _repo.reset(new DocumentTypeRepo(builder.config()));
+
+ _parser.reset(new select::Parser(*_repo, _bucketIdFactory));
+}
+
+Document::SP DocumentSelectParserTest::createDoc(
+ const std::string& doctype, const std::string& id, uint32_t hint,
+ double hfloat, const std::string& hstr, const std::string& cstr,
+ uint64_t hlong)
+{
+ const DocumentType* type = _repo->getDocumentType(doctype);
+ Document::SP doc(new Document(*type, DocumentId(id)));
+ doc->setValue(doc->getField("headerval"), IntFieldValue(hint));
+
+ if (hlong != 0) {
+ doc->setValue(doc->getField("headerlongval"), LongFieldValue(hlong));
+ }
+ doc->setValue(doc->getField("hfloatval"), FloatFieldValue(hfloat));
+ doc->setValue(doc->getField("hstringval"), StringFieldValue(hstr.c_str()));
+ doc->setValue(doc->getField("content"), StringFieldValue(cstr.c_str()));
+ return doc;
+}
+
+DocumentUpdate::SP DocumentSelectParserTest::createUpdate(
+ const std::string& doctype, const std::string& id, uint32_t hint,
+ const std::string& hstr)
+{
+ const DocumentType* type = _repo->getDocumentType(doctype);
+ DocumentUpdate::SP doc(
+ new DocumentUpdate(*type, DocumentId(id)));
+ doc->addUpdate(FieldUpdate(doc->getType().getField("headerval"))
+ .addUpdate(AssignValueUpdate(IntFieldValue(hint))));
+ doc->addUpdate(FieldUpdate(doc->getType().getField("hstringval"))
+ .addUpdate(AssignValueUpdate(StringFieldValue(hstr))));
+ return doc;
+}
+
+void
+DocumentSelectParserTest::createDocs()
+{
+ _doc.clear();
+ _doc.push_back(createDoc(
+ "testdoctype1", "doc:myspace:anything", 24, 2.0, "foo", "bar", 0)); // DOC 0
+ _doc.push_back(createDoc(
+ "testdoctype1", "doc:anotherspace:foo", 13, 4.1, "bar", "foo", 0)); // DOC 1
+ // Add some arrays and structs to doc 1
+ {
+ StructFieldValue sval(_doc.back()->getField("mystruct").getDataType());
+ sval.set("key", 14);
+ sval.set("value", "structval");
+ _doc.back()->setValue("mystruct", sval);
+ ArrayFieldValue
+ aval(_doc.back()->getField("structarray").getDataType());
+ {
+ StructFieldValue sval1(aval.getNestedType());
+ sval1.set("key", 15);
+ sval1.set("value", "structval1");
+ StructFieldValue sval2(aval.getNestedType());
+ sval2.set("key", 16);
+ sval2.set("value", "structval2");
+ aval.add(sval1);
+ aval.add(sval2);
+ }
+ _doc.back()->setValue("structarray", aval);
+
+ MapFieldValue mval(_doc.back()->getField("mymap").getDataType());
+ mval.put(document::IntFieldValue(3), document::StringFieldValue("a"));
+ mval.put(document::IntFieldValue(5), document::StringFieldValue("b"));
+ mval.put(document::IntFieldValue(7), document::StringFieldValue("c"));
+ _doc.back()->setValue("mymap", mval);
+
+ MapFieldValue
+ amval(_doc.back()->getField("structarrmap").getDataType());
+ amval.put(StringFieldValue("foo"), aval);
+
+ ArrayFieldValue
+ abval(_doc.back()->getField("structarray").getDataType());
+ {
+ StructFieldValue sval1(aval.getNestedType());
+ sval1.set("key", 17);
+ sval1.set("value", "structval3");
+ StructFieldValue sval2(aval.getNestedType());
+ sval2.set("key", 18);
+ sval2.set("value", "structval4");
+ abval.add(sval1);
+ abval.add(sval2);
+ }
+
+ amval.put(StringFieldValue("bar"), abval);
+ _doc.back()->setValue("structarrmap", amval);
+
+ WeightedSetFieldValue wsval(
+ _doc.back()->getField("stringweightedset").getDataType());
+ wsval.add("foo");
+ wsval.add("val1");
+ wsval.add("val2");
+ wsval.add("val3");
+ wsval.add("val4");
+ _doc.back()->setValue("stringweightedset", wsval);
+
+ WeightedSetFieldValue wsbytes(
+ _doc.back()->getField("byteweightedset").getDataType());
+ wsbytes.add(ByteFieldValue(5));
+ wsbytes.add(ByteFieldValue(75));
+ wsbytes.add(ByteFieldValue(255));
+ wsbytes.add(ByteFieldValue(0));
+ _doc.back()->setValue("byteweightedset", wsbytes);
+ }
+
+ _doc.push_back(createDoc(
+ "testdoctype1", "userdoc:myspace:1234:footype1", 15, 1.0, "some", "some", 0)); // DOC 2
+ // Add empty struct and array
+ {
+ StructFieldValue sval(_doc.back()->getField("mystruct").getDataType());
+ _doc.back()->setValue("mystruct", sval);
+ ArrayFieldValue aval(
+ _doc.back()->getField("structarray").getDataType());
+ _doc.back()->setValue("structarray", aval);
+ }
+ _doc.push_back(createDoc(
+ "testdoctype1", "groupdoc:myspace:yahoo:bar", 14, 2.4, "Yet", "\xE4\xB8\xBA\xE4\xBB\x80", 0)); // DOC 3
+ _doc.push_back(createDoc(
+ "testdoctype2", "doc:myspace:inheriteddoc", 10, 1.4, "inherited", "")); // DOC 4
+ _doc.push_back(createDoc(
+ "testdoctype1", "userdoc:footype:123456789:aardvark",
+ 10, 1.4, "inherited", "", 0)); // DOC 5
+ _doc.push_back(createDoc(
+ "testdoctype1", "userdoc:footype:1234:highlong",
+ 10, 1.4, "inherited", "", 2651257743)); // DOC 6
+ _doc.push_back(createDoc(
+ "testdoctype1", "userdoc:footype:1234:highlong",
+ 10, 1.4, "inherited", "", -2651257743)); // DOC 7
+ _doc.push_back(createDoc( // DOC 8
+ "testdoctype1", "orderdoc(4,4):footype:1234:12:highlong",
+ 10, 1.4, "inherited", "", -2651257743));
+ _doc.push_back(createDoc( // DOC 9
+ "testdoctype1", "orderdoc(4,4):footype:mygroup:12:highlong",
+ 10, 1.4, "inherited", "", -2651257743));
+ _doc.push_back(createDoc( // DOC 10. As DOC 0 but with version 2.
+ "testdoctype1", "doc:myspace:anything", 24, 2.0, "foo", "bar", 0));
+ _doc.push_back(createDoc(
+ "testdoctype1", "id:footype:testdoctype1:n=12345:foo",
+ 10, 1.4, "inherited", "", 42)); // DOC 11
+ _doc.push_back(createDoc(
+ "testdoctype1", "id:myspace:testdoctype1:g=xyzzy:foo",
+ 10, 1.4, "inherited", "", 42)); // DOC 12
+
+ _update.clear();
+ _update.push_back(createUpdate(
+ "testdoctype1", "doc:myspace:anything", 20, "hmm"));
+ _update.push_back(createUpdate(
+ "testdoctype1", "doc:anotherspace:foo", 10, "foo"));
+ _update.push_back(createUpdate(
+ "testdoctype1", "userdoc:myspace:1234:footype1", 0, "foo"));
+ _update.push_back(createUpdate(
+ "testdoctype1", "groupdoc:myspace:yahoo:bar", 3, "\xE4\xBA\xB8\xE4\xBB\x80"));
+ _update.push_back(createUpdate(
+ "testdoctype2", "doc:myspace:inheriteddoc", 10, "bar"));
+}
+
+namespace {
+void doVerifyParse(select::Node *node, const std::string &query,
+ const char *expected) {
+ std::string message("Query "+query+" failed to parse.");
+ CPPUNIT_ASSERT_MESSAGE(message, node != 0);
+ std::ostringstream actual;
+ actual << *node;
+
+ std::string exp(expected != 0 ? std::string(expected) : query);
+ CPPUNIT_ASSERT_EQUAL(exp, actual.str());
+ // Test that cloning gives the same result
+ std::unique_ptr<select::Node> clonedNode(node->clone());
+ std::ostringstream clonedStr;
+ clonedStr << *clonedNode;
+ CPPUNIT_ASSERT_EQUAL(exp, clonedStr.str());
+}
+
+void verifySimpleParse(const std::string& query, const char* expected = 0) {
+ BucketIdFactory factory;
+ select::simple::SelectionParser parser(factory);
+ std::string message("Query "+query+" failed to parse.");
+ CPPUNIT_ASSERT_MESSAGE(message, parser.parse(query));
+ std::unique_ptr<select::Node> node(parser.getNode());
+ doVerifyParse(node.get(), query, expected);
+}
+
+void verifyParse(const std::string& query, const char* expected = 0) {
+ BucketIdFactory factory;
+ select::Parser parser(*_repo, factory);
+ std::unique_ptr<select::Node> node(parser.parse(query));
+ doVerifyParse(node.get(), query, expected);
+}
+
+ void verifyFailedParse(const std::string& query, const std::string& error) {
+ try{
+ BucketIdFactory factory;
+ TestDocRepo test_repo;
+ select::Parser parser(test_repo.getTypeRepo(), factory);
+ std::unique_ptr<select::Node> node(parser.parse(query));
+ CPPUNIT_FAIL("Expected exception parsing query '"+query+"'");
+ } catch (select::ParsingFailedException& e) {
+ std::string message(e.what());
+ if (message.size() > error.size())
+ message = message.substr(0, error.size());
+ std::string failure("Expected: " + error + "\n- Actual : "
+ + std::string(e.what()));
+ CPPUNIT_ASSERT_MESSAGE(failure, error == message);
+ }
+ }
+}
+
+void DocumentSelectParserTest::testParseTerminals()
+{
+ createDocs();
+
+ // Test number value
+ verifyParse("", "true");
+ verifyParse("testdoctype1.headerval == 123");
+ verifyParse("testdoctype1.headerval == +123.53", "testdoctype1.headerval == 123.53");
+ verifyParse("testdoctype1.headerval == -123.5");
+ verifyParse("testdoctype1.headerval == 234123.523e3",
+ "testdoctype1.headerval == 2.34124e+08");
+ verifyParse("testdoctype1.headerval == -234123.523E-3",
+ "testdoctype1.headerval == -234.124");
+ verifyFailedParse("testdoctype1.headerval == aaa", "ParsingFailedException: "
+ "Unexpected token at position 23 ('== aaa') in query "
+ "'testdoctype1.headerval == aaa', at fullParse in ");
+ // Test string value
+ verifyParse("testdoctype1.headerval == \"test\"");
+ std::unique_ptr<select::Node> node(
+ _parser->parse("testdoctype1.headerval == \"test\""));
+ const select::Compare& compnode(
+ dynamic_cast<const select::Compare&>(*node));
+ const select::FieldValueNode& fnode(
+ dynamic_cast<const select::FieldValueNode&>(compnode.getLeft()));
+ const select::StringValueNode& vnode(
+ dynamic_cast<const select::StringValueNode&>(compnode.getRight()));
+ /*
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("testdoctype1"),
+ fnode.getDocType()->getName());
+ */
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("headerval"), fnode.getFieldName());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("test"), vnode.getValue());
+ // Test whitespace
+ verifyParse("testdoctype1.headerval == \"te st \"");
+ verifyParse(" \t testdoctype1.headerval\t== \t \"test\"\t",
+ "testdoctype1.headerval == \"test\"");
+ // Test escaping
+ verifyParse("testdoctype1.headerval == \"tab\\ttest\"");
+ verifyParse("testdoctype1.headerval == \"tab\\x09test\"",
+ "testdoctype1.headerval == \"tab\\ttest\"");
+ verifyParse("testdoctype1.headerval == \"tab\\x055test\"");
+ verifyFailedParse("testdoctype1.headerval == \"tab\\x0notcomplete\"",
+ "ParsingFailedException: Unexpected token at position 23 "
+ "('== \"tab\\x0') in query 'testdoctype1.headerval == \"tab\\x0notcomplete\"', "
+ "at fullParse in ");
+ verifyFailedParse("testdoctype1.headerval == \"tab\\ysf\"",
+ "ParsingFailedException: Unexpected token at position 23 "
+ "('== \"tab\\ys') in query 'testdoctype1.headerval == \"tab\\ysf\"', "
+ "at fullParse in ");
+ node = _parser->parse("testdoctype1.headerval == \"\\tt\\x48 \\n\"");
+ select::Compare& escapednode(dynamic_cast<select::Compare&>(*node));
+ const select::StringValueNode& escval(
+ dynamic_cast<const select::StringValueNode&>(escapednode.getRight()));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("\ttH \n"), escval.getValue());
+ // Test illegal operator
+ verifyFailedParse("testdoctype1.headerval <> 12", "ParsingFailedException: Unexpected"
+ " token at position 23 ('<> 12') in query 'testdoctype1.headerval <> 12', at");
+ // Test <= <, > >=
+ verifyParse("testdoctype1.headerval >= 123");
+ verifyParse("testdoctype1.headerval > 123");
+ verifyParse("testdoctype1.headerval <= 123");
+ verifyParse("testdoctype1.headerval < 123");
+ verifyParse("testdoctype1.headerval != 123");
+
+ // Test defined
+ verifyParse("testdoctype1.headerval", "testdoctype1.headerval != null");
+
+ // Test bools
+ verifyParse("TRUE");
+ verifyParse("FALSE");
+ verifyParse("true");
+ verifyParse("false");
+ verifyParse("faLSe");
+ verifyFailedParse("fal se", "ParsingFailedException: Unexpected token at "
+ "position 4 ('se') in query 'fal se', at");
+
+ // Test document types
+ verifyParse("testdoctype1");
+ verifyFailedParse("mytype", "ParsingFailedException: Document type mytype "
+ "not found");
+ verifyParse("_test_doctype3_");
+ verifyParse("_test_doctype3_._only_in_child_ == 0");
+
+ // Test document id with simple parser.
+ verifySimpleParse("id == \"userdoc:ns:mytest\"");
+ verifySimpleParse("id.namespace == \"myspace\"");
+ verifySimpleParse("id.scheme == \"userdoc\"");
+ verifySimpleParse("id.type == \"testdoctype1\"");
+ verifySimpleParse("id.group == \"yahoo.com\"");
+ verifySimpleParse("id.user == 1234");
+ verifySimpleParse("id.user == 0x12456ab", "id.user == 19158699");
+
+ // Test document id
+ verifyParse("id == \"userdoc:ns:mytest\"");
+ verifyParse("id.namespace == \"myspace\"");
+ verifyParse("id.scheme == \"userdoc\"");
+ verifyParse("id.type == \"testdoctype1\"");
+ verifyParse("id.user == 1234");
+ verifyParse("id.user == 0x12456ab", "id.user == 19158699");
+ verifyParse("id.group == \"yahoo.com\"");
+ verifyParse("id.order(10,5) < 100");
+
+ verifyParse("id.specific == \"mypart\"");
+ verifyParse("id.bucket == 1234");
+ verifyParse("id.bucket == 0x800000", "id.bucket == 8388608");
+ verifyParse("id.bucket == 0x80a000", "id.bucket == 8429568");
+ verifyParse("id.bucket == 0x80000000000000f2",
+ "id.bucket == -9223372036854775566");
+ verifyParse("id.gid == \"gid(0xd755743aea262650274d70f0)\"");
+
+ // Test search column
+ verifyParse("searchcolumn.10 == 2");
+
+ // Test other operators
+ verifyParse("id.scheme = \"*doc\"");
+ verifyParse("testdoctype1.hstringval =~ \"(john|barry|shrek)\"");
+
+ // Verify functions
+ verifyParse("id.hash() == 124");
+ verifyParse("id.specific.hash() == 124");
+ verifyParse("testdoctype1.hstringval.lowercase() == \"chang\"");
+ verifyParse("testdoctype1.hstringval.lowercase().hash() == 124");
+ verifyFailedParse("testdoctype1 == 8", "ParsingFailedException: Unexpected token"
+ " at position 13 ('== 8') in query 'testdoctype1 == 8', at fullParse in ");
+ verifyParse("testdoctype1.hintval > now()");
+ verifyParse("testdoctype1.hintval > now().abs()");
+
+ // Value grouping
+ verifyParse("(123) < (200)");
+ verifyParse("(\"hmm\") < (id.scheme)");
+
+ // Arithmetics
+ verifyParse("1 + 2 > 1");
+ verifyParse("1 - 2 > 1");
+ verifyParse("1 * 2 > 1");
+ verifyParse("1 / 2 > 1");
+ verifyParse("1 % 2 > 1");
+ verifyParse("(1 + 2) * (4 - 2) == 1");
+ verifyParse("23 + 643 / 34 % 10 > 34");
+
+ // CJK stuff
+ verifyParse("testdoctype1.hstringval = \"\xE4\xB8\xBA\xE4\xBB\x80\"",
+ "testdoctype1.hstringval = \"\\xe4\\xb8\\xba\\xe4\\xbb\\x80\"");
+
+ // Strange doctype names
+ verifyParse("notandor");
+ verifyParse("ornotand");
+ verifyParse("andornot");
+ verifyParse("idid");
+ verifyParse("usergroup");
+}
+
+void DocumentSelectParserTest::testParseBranches()
+{
+ createDocs();
+
+ verifyParse("TRUE or FALSE aNd FALSE oR TRUE");
+ verifyParse("TRUE and FALSE or FALSE and TRUE");
+ verifyParse("TRUE or FALSE and FALSE or TRUE");
+ verifyParse("(TRUE or FALSE) and (FALSE or TRUE)");
+ verifyParse("true or (not false) and not true");
+
+ // Test number branching with node branches
+ verifyParse("((243) < 300 and (\"FOO\").lowercase() == (\"foo\"))");
+
+ // Strange doctype names
+ verifyParse("notandor and ornotand");
+ verifyParse("ornotand or andornot");
+ verifyParse("not andornot");
+ verifyParse("idid or not usergroup");
+ verifyParse("not(andornot or idid)", "not (andornot or idid)");
+}
+
+template <typename ContainsType>
+select::ResultList
+DocumentSelectParserTest::doParse(const vespalib::stringref& expr,
+ const ContainsType& t)
+{
+ std::unique_ptr<select::Node> root(_parser->parse(expr));
+ select::ResultList result(root->contains(t));
+
+ std::unique_ptr<select::Node> cloned(root->clone());
+ select::ResultList clonedResult(cloned->contains(t));
+
+ std::unique_ptr<select::Node> traced(_parser->parse(expr));
+ std::ostringstream oss;
+ oss << "for expr: " << expr << "\n";
+ select::ResultList tracedResult(root->trace(t, oss));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(expr, result, clonedResult);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(oss.str(), result, tracedResult);
+
+ return result;
+}
+
+#define PARSE(expr, doc, result) \
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(expr, select::ResultList(select::Result::result), \
+ doParse(expr, (doc)));
+
+#define PARSEI(expr, doc, result) \
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(std::string("Doc: ") + expr, \
+ select::ResultList(select::Result::result), \
+ doParse(expr, (doc))); \
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(std::string("Doc id: ") + expr, \
+ select::ResultList(select::Result::result), \
+ doParse(expr, (doc).getId()));
+
+void DocumentSelectParserTest::testOperators()
+{
+ testOperators0();
+ testOperators1();
+ testOperators2();
+ testOperators3();
+ testOperators4();
+ testOperators5();
+ testOperators6();
+ testOperators7();
+ testOperators8();
+ testOperators9();
+}
+
+void DocumentSelectParserTest::testOperators0()
+{
+ createDocs();
+
+ /* Code for tracing result to see what went wrong
+ {
+ std::ostringstream ost;
+ _parser->parse("id.specific.hash() % 10 = 8")->trace(*_doc[0], ost);
+ ost << "\n\n";
+ _parser->parse("id.specific.hash() % 10 = 8")
+ ->trace(_doc[0]->getId(), ost);
+ std::cerr << ost.str() << "\n";
+ } // */
+
+ // Check that comparison operators work.
+ PARSE("", *_doc[0], True);
+ PARSE("30 < 10", *_doc[0], False);
+ PARSE("10 < 30", *_doc[0], True);
+ PARSE("30 < 10", *_doc[0], False);
+ PARSE("10 < 30", *_doc[0], True);
+ PARSE("30 <= 10", *_doc[0], False);
+ PARSE("10 <= 30", *_doc[0], True);
+ PARSE("30 <= 30", *_doc[0], True);
+ PARSE("10 >= 30", *_doc[0], False);
+ PARSE("30 >= 10", *_doc[0], True);
+ PARSE("30 >= 30", *_doc[0], True);
+
+ PARSE("10 > 30", *_doc[0], False);
+ PARSE("30 > 10", *_doc[0], True);
+ PARSE("30 == 10", *_doc[0], False);
+ PARSE("30 == 30", *_doc[0], True);
+ PARSE("30 != 10", *_doc[0], True);
+ PARSE("30 != 30", *_doc[0], False);
+ PARSE("\"foo\" != \"bar\"", *_doc[0], True);
+ PARSE("\"foo\" != \"foo\"", *_doc[0], False);
+ PARSE("\"foo\" == 'bar'", *_doc[0], False);
+ PARSE("\"foo\" == 'foo'", *_doc[0], True);
+ PARSE("\"bar\" = \"a\"", *_doc[0], False);
+ PARSE("\"bar\" = \"*a*\"", *_doc[0], True);
+ PARSE("\"bar\" = \"\"", *_doc[0], False);
+ PARSE("\"\" = \"\"", *_doc[0], True);
+ PARSE("\"bar\" =~ \"^a$\"", *_doc[0], False);
+ PARSE("\"bar\" =~ \"a\"", *_doc[0], True);
+ PARSE("\"bar\" =~ \"\"", *_doc[0], True);
+ PARSE("\"\" =~ \"\"", *_doc[0], True);
+ PARSE("30 = 10", *_doc[0], False);
+ PARSE("30 = 30", *_doc[0], True);
+}
+
+void DocumentSelectParserTest::testOperators1()
+{
+ createDocs();
+
+ // Mix of types should within numbers, but otherwise not match
+ PARSE("30 < 10.2", *_doc[0], False);
+ PARSE("10.2 < 30", *_doc[0], True);
+ PARSE("30 < \"foo\"", *_doc[0], Invalid);
+ PARSE("30 > \"foo\"", *_doc[0], Invalid);
+ PARSE("30 != \"foo\"", *_doc[0], Invalid);
+ PARSE("14.2 <= \"foo\"", *_doc[0], Invalid);
+ PARSE("null == null", *_doc[0], True);
+ PARSE("null = null", *_doc[0], True);
+ PARSE("\"bar\" == null", *_doc[0], False);
+ PARSE("14.3 == null", *_doc[0], False);
+ PARSE("null = 0", *_doc[0], False);
+
+ // Field values
+ PARSE("testdoctype1.headerval = 24", *_doc[0], True);
+ PARSE("testdoctype1.headerval = 24", *_doc[1], False);
+ PARSE("testdoctype1.headerval = 13", *_doc[0], False);
+ PARSE("testdoctype1.headerval = 13", *_doc[1], True);
+ PARSE("testdoctype1.hfloatval = 2.0", *_doc[0], True);
+ PARSE("testdoctype1.hfloatval = 1.0", *_doc[1], False);
+ PARSE("testdoctype1.hfloatval = 4.1", *_doc[0], False);
+ PARSE("testdoctype1.hfloatval > 4.09 and testdoctype1.hfloatval < 4.11",
+ *_doc[1], True);
+ PARSE("testdoctype1.content = \"bar\"", *_doc[0], True);
+ PARSE("testdoctype1.content = \"bar\"", *_doc[1], False);
+ PARSE("testdoctype1.content = \"foo\"", *_doc[0], False);
+ PARSE("testdoctype1.content = \"foo\"", *_doc[1], True);
+ PARSE("testdoctype1.hstringval == testdoctype1.content", *_doc[0], False);
+ PARSE("testdoctype1.hstringval == testdoctype1.content", *_doc[2], True);
+ PARSE("testdoctype1.byteweightedset == 7", *_doc[1], False);
+ PARSE("testdoctype1.byteweightedset == 5", *_doc[1], True);
+
+ // Document types
+ PARSE("testdoctype1", *_doc[0], True);
+ PARSE("testdoctype2", *_doc[0], False);
+
+ // Inherited doctypes
+ PARSE("testdoctype2", *_doc[4], True);
+ PARSE("testdoctype2", *_doc[3], False);
+ PARSE("testdoctype1", *_doc[4], True);
+ PARSE("testdoctype1.headerval = 10", *_doc[4], True);
+}
+
+void DocumentSelectParserTest::testOperators2()
+{
+ createDocs();
+
+ // Id values
+ PARSEI("id == \"doc:myspace:anything\"", *_doc[0], True);
+ PARSEI(" iD== \"doc:myspace:anything\" ", *_doc[0], True);
+ PARSEI("id == \"doc:myspa:nything\"", *_doc[0], False);
+ PARSEI("Id.scHeme == \"doc\"", *_doc[0], True);
+ PARSEI("id.scheme == \"userdoc\"", *_doc[0], False);
+ PARSEI("id.type == \"testdoctype1\"", *_doc[11], True);
+ PARSEI("id.type == \"wrong_type\"", *_doc[11], False);
+ PARSEI("id.type == \"unknown\"", *_doc[0], Invalid);
+ PARSEI("Id.namespaCe == \"myspace\"", *_doc[0], True);
+ PARSEI("id.NaMespace == \"pace\"", *_doc[0], False);
+ PARSEI("id.specific == \"anything\"", *_doc[0], True);
+ PARSEI("id.user=1234", *_doc[2], True);
+ PARSEI("id.user == 1234", *_doc[0], Invalid);
+ PARSEI("id.group == 1234", *_doc[3], Invalid);
+ PARSEI("id.group == \"yahoo\"", *_doc[3], True);
+ PARSEI("id.bucket == 1234", *_doc[0], False);
+ PARSEI("id.order(4,4) == 12", *_doc[8], True);
+ PARSEI("id.order(4,4) < 20", *_doc[8], True);
+ PARSEI("id.order(4,4) > 12", *_doc[8], False);
+ PARSEI("id.order(5,5) <= 12", *_doc[8], Invalid);
+ PARSEI("id.order(4,4) <= 12", *_doc[8], True);
+ PARSEI("id.order(4,4) == 12", *_doc[0], Invalid);
+ PARSEI("id.user=12345", *_doc[11], True);
+ PARSEI("id.group == \"xyzzy\"", *_doc[12], True);
+}
+
+void DocumentSelectParserTest::testOperators3()
+{
+ createDocs();
+ {
+ std::ostringstream ost;
+ ost << "id.bucket == " << BucketId(16, 4006).getId() ;
+ PARSEI(ost.str(), *_doc[0], True);
+ }
+ {
+ std::ostringstream ost;
+ ost << "id.bucket == " << BucketId(17, 4006).getId() ;
+ PARSEI(ost.str(), *_doc[0], False);
+ }
+ {
+ std::ostringstream ost;
+ ost << "id.bucket == " << BucketId(17, 69542).getId() ;
+ PARSEI(ost.str(), *_doc[0], True);
+ }
+ {
+ std::ostringstream ost;
+ ost << "id.bucket == " << BucketId(16, 1234).getId() ;
+ PARSEI(ost.str(), *_doc[0], False);
+ }
+
+ PARSEI("id.bucket == \"foo\"", *_doc[0], Invalid);
+
+ std::string gidmatcher = "id.gid == \"" + _doc[0]->getId().getGlobalId().toString() + "\"";
+ PARSEI(gidmatcher, *_doc[0], True);
+
+ PARSEI("id.user=123456789 and id = \"userdoc:footype:123456789:aardvark\"", *_doc[5], True);
+ PARSEI("id == \"userdoc:footype:123456789:badger\"", *_doc[5], False);
+
+ PARSEI("id.user = 1234", *_doc[8], True);
+ PARSEI("id.group == \"1234\"", *_doc[8], True);
+ PARSEI("id.group == \"mygroup\"", *_doc[9], True);
+
+ // Searchcolumn policy
+ PARSE("searchcolumn.10 == 8", *_doc[0], True);
+}
+
+void DocumentSelectParserTest::testOperators4()
+{
+ createDocs();
+
+ // Branch operators
+ PARSEI("true and false", *_doc[0], False);
+ PARSEI("true and true", *_doc[0], True);
+ PARSEI("true or false", *_doc[0], True);
+ PARSEI("false or false", *_doc[0], False);
+ PARSEI("false and true or true and true", *_doc[0], True);
+ PARSEI("false or true and true or false", *_doc[0], True);
+ PARSEI("not false", *_doc[0], True);
+ PARSEI("not true", *_doc[0], False);
+ PARSEI("true and not false or false", *_doc[0], True);
+ PARSEI("((243 < 300) and (\"FOO\".lowercase() == \"foo\"))", *_doc[0], True);
+
+ // Invalid branching. testdoctype1.content = 1 is invalid
+ PARSE("testdoctype1.content = 1 and true", *_doc[0], Invalid);
+ PARSE("testdoctype1.content = 1 or true", *_doc[0], True);
+ PARSE("testdoctype1.content = 1 and false", *_doc[0], False);
+ PARSE("testdoctype1.content = 1 or false", *_doc[0], Invalid);
+ PARSE("true and testdoctype1.content = 1", *_doc[0], Invalid);
+ PARSE("true or testdoctype1.content = 1", *_doc[0], True);
+ PARSE("false and testdoctype1.content = 1", *_doc[0], False);
+ PARSE("false or testdoctype1.content = 1", *_doc[0], Invalid);
+}
+
+void DocumentSelectParserTest::testOperators5()
+{
+ createDocs();
+
+ // Functions
+ PARSE("testdoctype1.hstringval.lowercase() == \"Yet\"", *_doc[3], False);
+ PARSE("testdoctype1.hstringval.lowercase() == \"yet\"", *_doc[3], True);
+ PARSE("testdoctype1.hfloatval.lowercase() == \"yet\"", *_doc[3], Invalid);
+ PARSEI("\"bar\".hash() == -2012135647395072713", *_doc[0], True);
+ PARSEI("\"bar\".hash().abs() == 2012135647395072713", *_doc[0], True);
+ PARSEI("null.hash() == 123", *_doc[0], Invalid);
+ PARSEI("(0.234).hash() == 123", *_doc[0], False);
+ PARSEI("(0.234).lowercase() == 123", *_doc[0], Invalid);
+ PARSE("\"foo\".hash() == 123", *_doc[0], False);
+ PARSEI("(234).hash() == 123", *_doc[0], False);
+ PARSE("now() > 1311862500", *_doc[10], True);
+ PARSE("now() < 1611862500", *_doc[10], True);
+ PARSE("now() < 1311862500", *_doc[10], False);
+ PARSE("now() > 1611862500", *_doc[10], False);
+
+ // Arithmetics
+ PARSEI("id.specific.hash() % 10 = 8", *_doc[0], True);
+ PARSEI("id.specific.hash() % 10 = 2", *_doc[0], False);
+ PARSEI("\"foo\" + \"bar\" = \"foobar\"", *_doc[0], True);
+ PARSEI("\"foo\" + 4 = 25", *_doc[0], Invalid);
+ PARSEI("34.0 % 4 = 4", *_doc[0], Invalid);
+ PARSEI("-6 % 10 = -6", *_doc[0], True);
+}
+
+void DocumentSelectParserTest::testOperators6()
+{
+ createDocs();
+
+ // CJK
+ // Assuming the characters " \ ? * is not used as part of CJK tokens
+ PARSE("testdoctype1.content=\"\xE4\xB8\xBA\xE4\xBB\x80\"", *_doc[3], True);
+ PARSE("testdoctype1.content=\"\xE4\xB7\xBA\xE4\xBB\x80\"", *_doc[3], False);
+
+ // Structs and arrays
+ PARSE("testdoctype1.mystruct", *_doc[0], False);
+ PARSE("testdoctype1.mystruct", *_doc[1], True);
+ PARSE("testdoctype1.mystruct", *_doc[2], False);
+ PARSE("testdoctype1.mystruct == testdoctype1.mystruct", *_doc[0], True);
+ PARSE("testdoctype1.mystruct == testdoctype1.mystruct", *_doc[1], True);
+ PARSE("testdoctype1.mystruct != testdoctype1.mystruct", *_doc[0], False);
+ PARSE("testdoctype1.mystruct != testdoctype1.mystruct", *_doc[1], False);
+ PARSE("testdoctype1.mystruct < testdoctype1.mystruct", *_doc[0], Invalid);
+ PARSE("testdoctype1.mystruct < testdoctype1.mystruct", *_doc[1], False);
+ PARSE("testdoctype1.mystruct < 5", *_doc[1], False);
+ // PARSE("testdoctype1.mystruct == \"foo\"", *_doc[1], Invalid);
+ PARSE("testdoctype1.mystruct.key == 14", *_doc[0], False);
+ PARSE("testdoctype1.mystruct.value == \"structval\"", *_doc[0], False);
+ PARSE("testdoctype1.mystruct.key == 14", *_doc[1], True);
+ PARSE("testdoctype1.mystruct.value == \"structval\"", *_doc[1], True);
+ PARSE("testdoctype1.structarray", *_doc[0], False);
+ PARSE("testdoctype1.structarray", *_doc[1], True);
+ PARSE("testdoctype1.structarray", *_doc[2], False);
+ PARSE("testdoctype1.structarray == testdoctype1.structarray",
+ *_doc[0], True);
+ PARSE("testdoctype1.structarray < testdoctype1.structarray",
+ *_doc[0], Invalid);
+ PARSE("testdoctype1.structarray == testdoctype1.structarray",
+ *_doc[1], True);
+ PARSE("testdoctype1.structarray < testdoctype1.structarray",
+ *_doc[1], False);
+ PARSE("testdoctype1.headerlongval<0", *_doc[6], False);
+ PARSE("testdoctype1.headerlongval<0", *_doc[7], True);
+}
+
+void DocumentSelectParserTest::testOperators7()
+{
+ createDocs();
+
+ PARSE("testdoctype1.structarray.key == 15", *_doc[0], False);
+ PARSE("testdoctype1.structarray[4].key == 15", *_doc[0], False);
+ PARSE("testdoctype1.structarray", *_doc[1], True);
+ PARSE("testdoctype1.structarray.key == 15", *_doc[1], True);
+ PARSE("testdoctype1.structarray[1].key == 16", *_doc[1], True);
+ PARSE("testdoctype1.structarray[1].key = 16", *_doc[1], True);
+ PARSE("testdoctype1.structarray.value == \"structval1\"", *_doc[0], False);
+ PARSE("testdoctype1.structarray[4].value == \"structval1\"", *_doc[0], False);
+ PARSE("testdoctype1.structarray.value == \"structval1\"", *_doc[1], True);
+ PARSE("testdoctype1.structarray[0].value == \"structval1\"", *_doc[1], True);
+ // Globbing of array-of-struct fields
+ PARSE("testdoctype1.structarray.key = 15", *_doc[0], False);
+ PARSE("testdoctype1.structarray.key = 15", *_doc[2], False);
+ PARSE("testdoctype1.structarray.key = 15", *_doc[1], True);
+ PARSE("testdoctype1.structarray.value = \"structval2\"", *_doc[2], Invalid); // Invalid due to lhs being NullValue
+ PARSE("testdoctype1.structarray.value = \"*ctval*\"", *_doc[1], True);
+ PARSE("testdoctype1.structarray[1].value = \"structval2\"", *_doc[1], True);
+ PARSE("testdoctype1.structarray[1].value = \"batman\"", *_doc[1], False);
+ // Regexp of array-of-struct fields
+ PARSE("testdoctype1.structarray.value =~ \"structval[1-9]\"", *_doc[1], True);
+ PARSE("testdoctype1.structarray.value =~ \"structval[a-z]\"", *_doc[1], False);
+ // Globbing/regexp of struct fields
+ PARSE("testdoctype1.mystruct.value = \"struc?val\"", *_doc[0], Invalid); // Invalid due to lhs being NullValue
+ PARSE("testdoctype1.mystruct.value = \"struc?val\"", *_doc[1], True);
+ PARSE("testdoctype1.mystruct.value =~ \"struct.*\"", *_doc[0], Invalid); // Ditto here
+ PARSE("testdoctype1.mystruct.value =~ \"struct.*\"", *_doc[1], True);
+
+ PARSE("testdoctype1.structarray[$x].key == 15 AND testdoctype1.structarray[$x].value == \"structval1\"", *_doc[1], True);
+ PARSE("testdoctype1.structarray[$x].key == 15 AND testdoctype1.structarray[$x].value == \"structval2\"", *_doc[1], False);
+ PARSE("testdoctype1.structarray[$x].key == 15 AND testdoctype1.structarray[$y].value == \"structval2\"", *_doc[1], True);
+}
+
+void DocumentSelectParserTest::testOperators8()
+{
+ createDocs();
+
+ PARSE("testdoctype1.mymap", *_doc[0], False);
+ PARSE("testdoctype1.mymap", *_doc[1], True);
+ PARSE("testdoctype1.mymap{3}", *_doc[1], True);
+ PARSE("testdoctype1.mymap{9}", *_doc[1], False);
+ PARSE("testdoctype1.mymap{3} == \"a\"", *_doc[1], True);
+ PARSE("testdoctype1.mymap{3} == \"b\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap{9} == \"b\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap.value == \"a\"", *_doc[1], True);
+ PARSE("testdoctype1.mymap.value == \"d\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap{3} = \"a\"", *_doc[1], True);
+ PARSE("testdoctype1.mymap{3} = \"b\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap{3} =~ \"a\"", *_doc[1], True);
+ PARSE("testdoctype1.mymap{3} =~ \"b\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap.value = \"a\"", *_doc[1], True);
+ PARSE("testdoctype1.mymap.value = \"d\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap.value =~ \"a\"", *_doc[1], True);
+ PARSE("testdoctype1.mymap.value =~ \"d\"", *_doc[1], False);
+ PARSE("testdoctype1.mymap == 3", *_doc[1], True);
+ PARSE("testdoctype1.mymap == 4", *_doc[1], False);
+ PARSE("testdoctype1.mymap = 3", *_doc[1], True); // Fallback to ==
+ PARSE("testdoctype1.mymap = 4", *_doc[1], False); // Fallback to ==
+
+ PARSE("testdoctype1.structarrmap{$x}[$y].key == 15 AND testdoctype1.structarrmap{$x}[$y].value == \"structval1\"", *_doc[1], True);
+ PARSE("testdoctype1.structarrmap.value[$y].key == 15 AND testdoctype1.structarrmap.value[$y].value == \"structval1\"", *_doc[1], True);
+ PARSE("testdoctype1.structarrmap{$x}[$y].key == 15 AND testdoctype1.structarrmap{$x}[$y].value == \"structval2\"", *_doc[1], False);
+ PARSE("testdoctype1.structarrmap.value[$y].key == 15 AND testdoctype1.structarrmap.value[$y].value == \"structval2\"", *_doc[1], False);
+ PARSE("testdoctype1.structarrmap{$x}[$y].key == 15 AND testdoctype1.structarrmap{$y}[$x].value == \"structval2\"", *_doc[1], False);
+}
+
+void DocumentSelectParserTest::testOperators9()
+{
+ createDocs();
+
+ PARSE("testdoctype1.stringweightedset", *_doc[1], True);
+ PARSE("testdoctype1.stringweightedset{val1}", *_doc[1], True);
+ PARSE("testdoctype1.stringweightedset{val1} == 1", *_doc[1], True);
+ PARSE("testdoctype1.stringweightedset{val1} == 2", *_doc[1], False);
+ PARSE("testdoctype1.stringweightedset == \"val1\"", *_doc[1], True);
+ PARSE("testdoctype1.stringweightedset = \"val*\"", *_doc[1], True);
+ PARSE("testdoctype1.stringweightedset =~ \"val[0-9]\"", *_doc[1], True);
+ PARSE("testdoctype1.stringweightedset == \"val5\"", *_doc[1], False);
+ PARSE("testdoctype1.stringweightedset = \"val5\"", *_doc[1], False);
+ PARSE("testdoctype1.stringweightedset =~ \"val5\"", *_doc[1], False);
+
+ PARSE("testdoctype1.structarrmap{$x}.key == 15 AND testdoctype1.stringweightedset{$x}", *_doc[1], True);
+ PARSE("testdoctype1.structarrmap{$x}.key == 17 AND testdoctype1.stringweightedset{$x}", *_doc[1], False);
+
+ PARSE("testdoctype1.structarray.key < 16", *_doc[1], True);
+ PARSE("testdoctype1.structarray.key < 15", *_doc[1], False);
+ PARSE("testdoctype1.structarray.key > 15", *_doc[1], True);
+ PARSE("testdoctype1.structarray.key > 16", *_doc[1], False);
+ PARSE("testdoctype1.structarray.key <= 15", *_doc[1], True);
+ PARSE("testdoctype1.structarray.key <= 14", *_doc[1], False);
+ PARSE("testdoctype1.structarray.key >= 16", *_doc[1], True);
+ PARSE("testdoctype1.structarray.key >= 17", *_doc[1], False);
+}
+
+namespace {
+
+ class TestVisitor : public select::Visitor {
+ private:
+ std::ostringstream data;
+
+ public:
+ virtual ~TestVisitor() {}
+
+ void visitConstant(const select::Constant& node)
+ {
+ data << "CONSTANT(" << node << ")";
+ }
+
+ virtual void
+ visitInvalidConstant(const select::InvalidConstant& node)
+ {
+ data << "INVALIDCONSTANT(" << node << ")";
+ }
+
+ void visitDocumentType(const select::DocType& node)
+ {
+ data << "DOCTYPE(" << node << ")";
+ }
+
+ void visitComparison(const select::Compare& node)
+ {
+ data << "COMPARE(" << node.getLeft() << " "
+ << node.getOperator() << " " << node.getRight() << ")";
+ }
+
+ void visitAndBranch(const select::And& node)
+ {
+ data << "AND(";
+ node.getLeft().visit(*this);
+ data << ", ";
+ node.getRight().visit(*this);
+ data << ")";
+ }
+
+ void visitOrBranch(const select::Or& node)
+ {
+ data << "OR(";
+ node.getLeft().visit(*this);
+ data << ", ";
+ node.getRight().visit(*this);
+ data << ")";
+ }
+
+ void visitNotBranch(const select::Not& node)
+ {
+ data << "NOT(";
+ node.getChild().visit(*this);
+ data << ")";
+ }
+
+ virtual void
+ visitArithmeticValueNode(const select::ArithmeticValueNode &)
+ {
+ }
+
+ virtual void
+ visitFunctionValueNode(const select::FunctionValueNode &)
+ {
+ }
+
+ virtual void
+ visitIdValueNode(const select::IdValueNode &)
+ {
+ }
+
+ virtual void
+ visitSearchColumnValueNode(const select::SearchColumnValueNode &)
+ {
+ }
+
+ virtual void
+ visitFieldValueNode(const select::FieldValueNode &)
+ {
+ }
+
+ virtual void
+ visitFloatValueNode(const select::FloatValueNode &)
+ {
+ }
+
+ virtual void
+ visitVariableValueNode(const select::VariableValueNode &)
+ {
+ }
+
+ virtual void
+ visitIntegerValueNode(const select::IntegerValueNode &)
+ {
+ }
+
+ virtual void
+ visitCurrentTimeValueNode(const select::CurrentTimeValueNode &)
+ {
+ }
+
+ virtual void
+ visitStringValueNode(const select::StringValueNode &)
+ {
+ }
+
+ virtual void
+ visitNullValueNode(const select::NullValueNode &)
+ {
+ }
+
+ virtual void
+ visitInvalidValueNode(const select::InvalidValueNode &)
+ {
+ }
+
+ std::string getVisitString() { return data.str(); }
+ };
+
+}
+
+void DocumentSelectParserTest::testVisitor()
+{
+ createDocs();
+
+ std::unique_ptr<select::Node> root(_parser->parse(
+ "true or testdoctype1 and (not id.user = 12 or testdoctype1.hstringval = \"ola\") and "
+ "testdoctype1.headerval"));
+
+ TestVisitor v;
+ root->visit(v);
+ std::string expected =
+ "OR(CONSTANT(true), "
+ "AND(DOCTYPE(testdoctype1), "
+ "AND(OR(NOT(COMPARE(id.user = 12)), "
+ "COMPARE(testdoctype1.hstringval = \"ola\")), "
+ "COMPARE(testdoctype1.headerval != null)"
+ ")"
+ ")"
+ ")";
+ CPPUNIT_ASSERT_EQUAL(expected, v.getVisitString());
+}
+
+void DocumentSelectParserTest::testBodyFieldDetection()
+{
+
+ {
+ std::unique_ptr<select::Node> root(_parser->parse("testdoctype1"));
+ select::BodyFieldDetector detector(*_repo);
+
+ root->visit(detector);
+ CPPUNIT_ASSERT(!detector.foundBodyField);
+ CPPUNIT_ASSERT(detector.foundHeaderField);
+ }
+
+ {
+ std::unique_ptr<select::Node> root(_parser->parse("testdoctype1 AND id.user=1234"));
+ select::BodyFieldDetector detector(*_repo);
+
+ root->visit(detector);
+ CPPUNIT_ASSERT(!detector.foundBodyField);
+ CPPUNIT_ASSERT(detector.foundHeaderField);
+ }
+
+ {
+ std::unique_ptr<select::Node> root(_parser->parse("testdoctype1.headerval=123"));
+ select::BodyFieldDetector detector(*_repo);
+
+ root->visit(detector);
+ CPPUNIT_ASSERT(!detector.foundBodyField);
+ CPPUNIT_ASSERT(detector.foundHeaderField);
+ }
+
+ {
+ std::unique_ptr<select::Node> root(_parser->parse("testdoctype1.content"));
+ select::BodyFieldDetector detector(*_repo);
+
+ root->visit(detector);
+ CPPUNIT_ASSERT(detector.foundBodyField);
+ }
+
+ {
+ std::unique_ptr<select::Node> root(_parser->parse(
+ "true or testdoctype1 and (not id.user = 12 or testdoctype1.hstringval = \"ola\") and "
+ "testdoctype1.headerval"));
+
+ select::BodyFieldDetector detector(*_repo);
+
+ root->visit(detector);
+ CPPUNIT_ASSERT(!detector.foundBodyField);
+ }
+
+}
+
+void DocumentSelectParserTest::testDocumentUpdates()
+{
+ testDocumentUpdates0();
+ testDocumentUpdates1();
+ testDocumentUpdates2();
+ testDocumentUpdates3();
+ testDocumentUpdates4();
+}
+
+void DocumentSelectParserTest::testDocumentUpdates0()
+{
+ createDocs();
+
+ /* Code for tracing result to see what went wrong
+ {
+ std::ostringstream ost;
+ _parser->parse("id.bucket == 4006")->trace(*_update[0], ost);
+ ost << "\n\n";
+ _parser->parse("id.bucket == 4006")
+ ->trace(_update[0]->getId(), ost);
+ std::cerr << ost.str() << "\n";
+ } // */
+ PARSEI("", *_update[0], True);
+ PARSEI("30 < 10", *_update[0], False);
+ PARSEI("10 < 30", *_update[0], True);
+ PARSEI("30 < 10", *_update[0], False);
+ PARSEI("10 < 30", *_update[0], True);
+ PARSEI("30 <= 10", *_update[0], False);
+ PARSEI("10 <= 30", *_update[0], True);
+ PARSEI("30 <= 30", *_update[0], True);
+ PARSEI("10 >= 30", *_update[0], False);
+ PARSEI("30 >= 10", *_update[0], True);
+ PARSEI("30 >= 30", *_update[0], True);
+ PARSEI("10 > 30", *_update[0], False);
+ PARSEI("30 > 10", *_update[0], True);
+ PARSEI("30 == 10", *_update[0], False);
+ PARSEI("30 == 30", *_update[0], True);
+ PARSEI("30 != 10", *_update[0], True);
+ PARSEI("30 != 30", *_update[0], False);
+ PARSEI("\"foo\" != \"bar\"", *_update[0], True);
+ PARSEI("\"foo\" != \"foo\"", *_update[0], False);
+ PARSEI("\"foo\" == \"bar\"", *_update[0], False);
+ PARSEI("\"foo\" == \"foo\"", *_update[0], True);
+ PARSEI("\"bar\" = \"a\"", *_update[0], False);
+ PARSEI("\"bar\" = \"*a*\"", *_update[0], True);
+ PARSEI("\"bar\" = \"\"", *_update[0], False);
+ PARSEI("\"\" = \"\"", *_update[0], True);
+ PARSEI("\"bar\" =~ \"^a$\"", *_update[0], False);
+ PARSEI("\"bar\" =~ \"a\"", *_update[0], True);
+ PARSEI("\"bar\" =~ \"\"", *_update[0], True);
+ PARSEI("\"\" =~ \"\"", *_update[0], True);
+ PARSEI("30 = 10", *_update[0], False);
+ PARSEI("30 = 30", *_update[0], True);
+}
+
+void DocumentSelectParserTest::testDocumentUpdates1()
+{
+ createDocs();
+
+ // Mix of types should within numbers, but otherwise not match
+ PARSEI("30 < 10.2", *_update[0], False);
+ PARSEI("10.2 < 30", *_update[0], True);
+ PARSEI("30 < \"foo\"", *_update[0], Invalid);
+ PARSEI("30 > \"foo\"", *_update[0], Invalid);
+ PARSEI("30 != \"foo\"", *_update[0], Invalid);
+ PARSEI("14.2 <= \"foo\"", *_update[0], Invalid);
+ PARSEI("null == null", *_update[0], True);
+ PARSEI("null = null", *_update[0], True);
+ PARSEI("\"bar\" == null", *_update[0], False);
+ PARSEI("14.3 == null", *_update[0], False);
+ PARSEI("null = 0", *_update[0], False);
+
+ // Field values
+ PARSE("testdoctype1.headerval = 24", *_update[0], Invalid);
+ PARSE("testdoctype1.hfloatval = 2.0", *_update[0], Invalid);
+ PARSE("testdoctype1.content = \"bar\"", *_update[0], Invalid);
+ PARSE("testdoctype1.hstringval == testdoctype1.content", *_update[0], Invalid);
+
+ // Document types
+ PARSE("testdoctype1", *_update[0], True);
+ PARSE("testdoctype2", *_update[0], False);
+
+ // Inherited doctypes
+ PARSE("testdoctype2", *_update[4], True);
+ PARSE("testdoctype2", *_update[3], False);
+ PARSE("testdoctype1", *_update[4], True);
+ PARSE("testdoctype1.headerval = 10", *_update[4], Invalid);
+}
+
+void DocumentSelectParserTest::testDocumentUpdates2()
+{
+ createDocs();
+
+ // Id values
+ PARSEI("id == \"doc:myspace:anything\"", *_update[0], True);
+ PARSEI(" iD== \"doc:myspace:anything\" ", *_update[0], True);
+ PARSEI("id == \"doc:myspa:nything\"", *_update[0], False);
+ PARSEI("Id.scHeme == \"doc\"", *_update[0], True);
+ PARSEI("id.scheme == \"userdoc\"", *_update[0], False);
+ PARSEI("Id.namespaCe == \"myspace\"", *_update[0], True);
+ PARSEI("id.NaMespace == \"pace\"", *_update[0], False);
+ PARSEI("id.specific == \"anything\"", *_update[0], True);
+ PARSEI("id.user=1234", *_update[2], True);
+ PARSEI("id.user == 1234", *_update[0], Invalid);
+ PARSEI("id.group == 1234", *_update[3], Invalid);
+ PARSEI("id.group == \"yahoo\"", *_update[3], True);
+ PARSEI("id.bucket == 1234", *_update[0], False);
+ {
+ std::ostringstream ost;
+ ost << "id.bucket == " << BucketId(16, 4006).getId();
+ PARSEI(ost.str(), *_update[0], True);
+ }
+ PARSEI("id.bucket == \"foo\"", *_update[0], Invalid);
+}
+
+void DocumentSelectParserTest::testDocumentUpdates3()
+{
+ createDocs();
+
+ // Branch operators
+ PARSEI("true and false", *_update[0], False);
+ PARSEI("true and true", *_update[0], True);
+ PARSEI("true or false", *_update[0], True);
+ PARSEI("false or false", *_update[0], False);
+ PARSEI("false and true or true and true", *_update[0], True);
+ PARSEI("false or true and true or false", *_update[0], True);
+ PARSEI("not false", *_update[0], True);
+ PARSEI("not true", *_update[0], False);
+ PARSEI("true and not false or false", *_update[0], True);
+ PARSEI("((243 < 300) and (\"FOO\".lowercase() == \"foo\"))", *_update[0], True);
+
+ // Invalid branching. testdoctype1.content = 1 is invalid
+ PARSE("testdoctype1.content = 1 and true", *_update[0], Invalid);
+ PARSE("testdoctype1.content = 1 or true", *_update[0], True);
+ PARSE("testdoctype1.content = 1 and false", *_update[0], False);
+ PARSE("testdoctype1.content = 1 or false", *_update[0], Invalid);
+ PARSE("true and testdoctype1.content = 1", *_update[0], Invalid);
+ PARSE("true or testdoctype1.content = 1", *_update[0], True);
+ PARSE("false and testdoctype1.content = 1", *_update[0], False);
+ PARSE("false or testdoctype1.content = 1", *_update[0], Invalid);
+}
+
+void DocumentSelectParserTest::testDocumentUpdates4()
+{
+ createDocs();
+
+ // Functions
+ PARSEI("\"bar\".hash() == -2012135647395072713", *_update[0], True);
+ PARSEI("\"bar\".hash().abs() == 2012135647395072713", *_update[0], True);
+ PARSEI("null.hash() == 123", *_update[0], Invalid);
+ PARSEI("(0.234).hash() == 123", *_update[0], False);
+ PARSEI("(0.234).lowercase() == 123", *_update[0], Invalid);
+ PARSEI("\"foo\".hash() == 123", *_update[0], False);
+ PARSEI("(234).hash() == 123", *_update[0], False);
+
+ // Arithmetics
+ PARSEI("id.specific.hash() % 10 = 8", *_update[0], True);
+ PARSEI("id.specific.hash() % 10 = 2", *_update[0], False);
+ PARSEI("\"foo\" + \"bar\" = \"foobar\"", *_update[0], True);
+ PARSEI("\"foo\" + 4 = 25", *_update[0], Invalid);
+ PARSEI("34.0 % 4 = 4", *_update[0], Invalid);
+ PARSEI("-6 % 10 = -6", *_update[0], True);
+}
+
+void DocumentSelectParserTest::testUtf8()
+{
+ createDocs();
+ std::string utf8name(u8"H\u00e5kon");
+ CPPUNIT_ASSERT_EQUAL(size_t(6), utf8name.size());
+
+ /// \todo TODO (was warning): UTF8 test for glob/regex support in selection language disabled. Known not to work
+// boost::u32regex rx = boost::make_u32regex("H.kon");
+// CPPUNIT_ASSERT_EQUAL(true, boost::u32regex_match(utf8name, rx));
+
+ _doc.push_back(createDoc(
+ "testdoctype1", "doc:myspace:utf8doc", 24, 2.0, utf8name, "bar"));
+// PARSE("testdoctype1.hstringval = \"H?kon\"", *_doc[_doc.size()-1], True);
+// PARSE("testdoctype1.hstringval =~ \"H.kon\"", *_doc[_doc.size()-1], True);
+}
+
+} // document
diff --git a/document/src/tests/documentselecttest.h b/document/src/tests/documentselecttest.h
new file mode 100644
index 00000000000..d8cb4d427e1
--- /dev/null
+++ b/document/src/tests/documentselecttest.h
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class DocumentSelect_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( DocumentSelect_Test);
+ CPPUNIT_TEST(testEquals);
+ CPPUNIT_TEST(testLt);
+ CPPUNIT_TEST(testGt);
+ CPPUNIT_TEST(testAnd);
+ CPPUNIT_TEST(testOr);
+ CPPUNIT_TEST(testNot);
+ CPPUNIT_TEST(testConfig1);
+ CPPUNIT_TEST(testConfig2);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp();
+
+ void tearDown();
+protected:
+ void testEquals();
+ void testLt();
+ void testGt();
+ void testAnd();
+ void testOr();
+ void testNot();
+ void testConfig1();
+ void testConfig2();
+};
+
+
diff --git a/document/src/tests/documenttestcase.cpp b/document/src/tests/documenttestcase.cpp
new file mode 100644
index 00000000000..475cf64128e
--- /dev/null
+++ b/document/src/tests/documenttestcase.cpp
@@ -0,0 +1,1434 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/base/testdocman.h>
+#include <vespa/document/base/testdocrepo.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/vespalib/io/fileutil.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/datatype/annotationreferencedatatype.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <fstream>
+
+using vespalib::nbostream;
+
+using namespace document::config_builder;
+
+namespace document {
+
+struct DocumentTest : public CppUnit::TestFixture {
+ void testTraversing();
+ void testFieldPath();
+ void testModifyDocument();
+ void testVariables();
+ void testSimpleUsage();
+ void testReadSerializedFile();
+ void testReadSerializedFileCompressed();
+ void testReadSerializedAllVersions();
+ void testGenerateSerializedFile();
+ void testGetURIFromSerialized();
+ void testBogusserialize();
+ void testCRC32();
+ void testHasChanged();
+ void testSplitSerialization();
+ void testSliceSerialize();
+ void testCompression();
+ void testCompressionConfigured();
+ void testUnknownEntries();
+ void testAnnotationDeserialization();
+ void testGetSerializedSize();
+ void testDeserializeMultiple();
+ void testSizeOf();
+
+ CPPUNIT_TEST_SUITE(DocumentTest);
+ CPPUNIT_TEST(testFieldPath);
+ CPPUNIT_TEST(testTraversing);
+ CPPUNIT_TEST(testModifyDocument);
+ CPPUNIT_TEST(testVariables);
+ CPPUNIT_TEST(testSimpleUsage);
+ CPPUNIT_TEST(testReadSerializedFile);
+ CPPUNIT_TEST(testReadSerializedFileCompressed);
+ CPPUNIT_TEST(testReadSerializedAllVersions);
+ CPPUNIT_TEST(testGenerateSerializedFile);
+ CPPUNIT_TEST(testGetURIFromSerialized);
+ CPPUNIT_TEST(testBogusserialize);
+ CPPUNIT_TEST(testCRC32);
+ CPPUNIT_TEST(testHasChanged);
+ CPPUNIT_TEST(testSplitSerialization);
+ CPPUNIT_TEST(testSliceSerialize);
+ CPPUNIT_TEST(testCompression);
+ CPPUNIT_TEST(testCompressionConfigured);
+ CPPUNIT_TEST(testUnknownEntries);
+ CPPUNIT_TEST(testAnnotationDeserialization);
+ CPPUNIT_TEST(testGetSerializedSize);
+ CPPUNIT_TEST(testDeserializeMultiple);
+ CPPUNIT_TEST(testSizeOf);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DocumentTest);
+
+void DocumentTest::testSizeOf()
+{
+ CPPUNIT_ASSERT_EQUAL(120ul, sizeof(Document));
+ CPPUNIT_ASSERT_EQUAL(64ul, sizeof(StructFieldValue));
+ CPPUNIT_ASSERT_EQUAL(16ul, sizeof(StructuredFieldValue));
+ CPPUNIT_ASSERT_EQUAL(112ul, sizeof(SerializableArray));
+}
+
+void DocumentTest::testFieldPath()
+{
+ FieldPathEntry empty;
+ empty.asString();
+ const vespalib::string testValues[] = { "{}", "", "",
+ "{}r", "", "r",
+ "{{}}", "{", "}",
+ "{{}}r", "{", "}r",
+ "{\"{}\"}", "{}", "",
+ "{\"{}\"}r", "{}", "r",
+ "{\"{\\a}\"}r", "{a}", "r",
+ "{\"{\\\"}\"}r", "{\"}", "r",
+ "{\"{\\\\}\"}r", "{\\}", "r",
+ "{$x}", "$x", "",
+ "{$x}[$y]", "$x", "[$y]",
+ "{$x}.ss", "$x", ".ss",
+ "{\"\"}", "", ""
+ };
+ for (size_t i(0); i < sizeof(testValues)/sizeof(testValues[0]); i+=3) {
+ vespalib::string tmp = testValues[i];
+ vespalib::string key = FieldPathEntry::parseKey(tmp);
+ CPPUNIT_ASSERT_EQUAL(testValues[i+1], key);
+ CPPUNIT_ASSERT_EQUAL(testValues[i+2], tmp);
+ }
+}
+
+void DocumentTest::testTraversing()
+{
+ class Handler : public FieldValue::IteratorHandler {
+ public:
+ const std::string & getResult() const { return _result; }
+ private:
+ virtual void onPrimitive(const Content&) { _result += 'P'; }
+ virtual void onCollectionStart(const Content&) { _result += '['; }
+ virtual void onCollectionEnd(const Content&) { _result += ']'; }
+ virtual void onStructStart(const Content&) { _result += '<'; }
+ virtual void onStructEnd(const Content&) { _result += '>'; }
+ std::string _result;
+ };
+
+ Field primitive1("primitive1", 1, *DataType::INT, true);
+ Field primitive2("primitive2", 2, *DataType::INT, true);
+ StructDataType struct1("struct1");
+ struct1.addField(primitive1);
+ struct1.addField(primitive2);
+
+ ArrayDataType iarr(*DataType::INT);
+ ArrayDataType sarr(struct1);
+ Field iarrF("iarray", 21, iarr, true);
+ Field sarrF("sarray", 22, sarr, true);
+
+ StructDataType struct2("struct2");
+ struct2.addField(primitive1);
+ struct2.addField(primitive2);
+ struct2.addField(iarrF);
+ struct2.addField(sarrF);
+ Field s2("ss", 12, struct2, true);
+
+ StructDataType struct3("struct3");
+ struct3.addField(primitive1);
+ struct3.addField(s2);
+
+ Field structl1s1("l1s1", 11, struct3, true);
+
+ DocumentType type("test");
+ type.addField(primitive1);
+ type.addField(structl1s1);
+
+ Document doc(type, DocumentId("doc::testdoc"));
+ doc.setValue(primitive1, IntFieldValue(1));
+
+ StructFieldValue l1s1(struct3);
+ l1s1.setValue(primitive1, IntFieldValue(2));
+
+ StructFieldValue l2s1(struct2);
+ l2s1.setValue(primitive1, IntFieldValue(3));
+ l2s1.setValue(primitive2, IntFieldValue(4));
+ ArrayFieldValue iarr1(iarr);
+ iarr1.add(IntFieldValue(11));
+ iarr1.add(IntFieldValue(12));
+ iarr1.add(IntFieldValue(13));
+ ArrayFieldValue sarr1(sarr);
+ StructFieldValue l3s1(struct1);
+ l3s1.setValue(primitive1, IntFieldValue(1));
+ l3s1.setValue(primitive2, IntFieldValue(2));
+ sarr1.add(l3s1);
+ sarr1.add(l3s1);
+ l2s1.setValue(iarrF, iarr1);
+ l2s1.setValue(sarrF, sarr1);
+
+ l1s1.setValue(s2, l2s1);
+ doc.setValue(structl1s1, l1s1);
+
+ Handler fullTraverser;
+ FieldPath empty;
+ doc.iterateNested(empty.begin(), empty.end(), fullTraverser);
+ CPPUNIT_ASSERT_EQUAL(fullTraverser.getResult(),
+ std::string("<P<P<PP[PPP][<PP><PP>]>>>"));
+}
+
+class VariableIteratorHandler : public FieldValue::IteratorHandler {
+public:
+ std::string retVal;
+
+ virtual void onPrimitive(const Content & fv) {
+ for (VariableMap::iterator iter = getVariables().begin(); iter != getVariables().end(); iter++) {
+ retVal += vespalib::make_string("%s: %s,", iter->first.c_str(), iter->second.toString().c_str());
+ }
+ retVal += " - " + fv.getValue().toString() + "\n";
+ };
+};
+
+void
+DocumentTest::testVariables()
+{
+ ArrayDataType iarr(*DataType::INT);
+ ArrayDataType iiarr(static_cast<DataType &>(iarr));
+ ArrayDataType iiiarr(static_cast<DataType &>(iiarr));
+
+ Field iiiarrF("iiiarray", 1, iiiarr, true);
+ DocumentType type("test");
+ type.addField(iiiarrF);
+
+ ArrayFieldValue iiiaV(iiiarr);
+ for (int i = 1; i < 4; i++) {
+ ArrayFieldValue iiaV(iiarr);
+ for (int j = 1; j < 4; j++) {
+ ArrayFieldValue iaV(iarr);
+ for (int k = 1; k < 4; k++) {
+ iaV.add(IntFieldValue(i * j * k));
+ }
+ iiaV.add(iaV);
+ }
+ iiiaV.add(iiaV);
+ }
+
+ Document doc(type, DocumentId("doc::testdoc"));
+ doc.setValue(iiiarrF, iiiaV);
+
+ {
+ VariableIteratorHandler handler;
+ FieldPath::UP path = type.buildFieldPath("iiiarray[$x][$y][$z]");
+ doc.iterateNested(path->begin(), path->end(), handler);
+
+ std::string fasit =
+ "x: 0,y: 0,z: 0, - 1\n"
+ "x: 0,y: 0,z: 1, - 2\n"
+ "x: 0,y: 0,z: 2, - 3\n"
+ "x: 0,y: 1,z: 0, - 2\n"
+ "x: 0,y: 1,z: 1, - 4\n"
+ "x: 0,y: 1,z: 2, - 6\n"
+ "x: 0,y: 2,z: 0, - 3\n"
+ "x: 0,y: 2,z: 1, - 6\n"
+ "x: 0,y: 2,z: 2, - 9\n"
+ "x: 1,y: 0,z: 0, - 2\n"
+ "x: 1,y: 0,z: 1, - 4\n"
+ "x: 1,y: 0,z: 2, - 6\n"
+ "x: 1,y: 1,z: 0, - 4\n"
+ "x: 1,y: 1,z: 1, - 8\n"
+ "x: 1,y: 1,z: 2, - 12\n"
+ "x: 1,y: 2,z: 0, - 6\n"
+ "x: 1,y: 2,z: 1, - 12\n"
+ "x: 1,y: 2,z: 2, - 18\n"
+ "x: 2,y: 0,z: 0, - 3\n"
+ "x: 2,y: 0,z: 1, - 6\n"
+ "x: 2,y: 0,z: 2, - 9\n"
+ "x: 2,y: 1,z: 0, - 6\n"
+ "x: 2,y: 1,z: 1, - 12\n"
+ "x: 2,y: 1,z: 2, - 18\n"
+ "x: 2,y: 2,z: 0, - 9\n"
+ "x: 2,y: 2,z: 1, - 18\n"
+ "x: 2,y: 2,z: 2, - 27\n";
+
+ CPPUNIT_ASSERT_EQUAL(fasit, handler.retVal);
+ }
+
+}
+
+class ModifyIteratorHandler : public FieldValue::IteratorHandler {
+public:
+ ModificationStatus doModify(FieldValue& fv) {
+ StringFieldValue* sfv = dynamic_cast<StringFieldValue*>(&fv);
+ if (sfv != NULL) {
+ *sfv = std::string("newvalue");
+ return MODIFIED;
+ }
+
+ return NOT_MODIFIED;
+ };
+
+ bool onComplex(const Content&) {
+ return false;
+ }
+};
+
+void
+DocumentTest::testModifyDocument()
+{
+ // Create test document type and content
+ Field primitive1("primitive1", 1, *DataType::INT, true);
+ Field primitive2("primitive2", 2, *DataType::INT, true);
+ StructDataType struct1("struct1");
+ struct1.addField(primitive1);
+ struct1.addField(primitive2);
+
+ ArrayDataType iarr(*DataType::INT);
+ ArrayDataType sarr(struct1);
+ Field iarrF("iarray", 21, iarr, true);
+ Field sarrF("sarray", 22, sarr, true);
+
+ MapDataType smap(*DataType::STRING, *DataType::STRING);
+ Field smapF("smap", 23, smap, true);
+
+ StructDataType struct2("struct2");
+ struct2.addField(primitive1);
+ struct2.addField(primitive2);
+ struct2.addField(iarrF);
+ struct2.addField(sarrF);
+ struct2.addField(smapF);
+ Field s2("ss", 12, struct2, true);
+
+ MapDataType structmap(*DataType::STRING, struct2);
+ Field structmapF("structmap", 24, structmap, true);
+
+ WeightedSetDataType wset(*DataType::STRING, false, false);
+ Field wsetF("wset", 25, wset, true);
+
+ WeightedSetDataType structwset(struct2, false, false);
+ Field structwsetF("structwset", 26, structwset, true);
+
+ StructDataType struct3("struct3");
+ struct3.addField(primitive1);
+ struct3.addField(s2);
+ struct3.addField(structmapF);
+ struct3.addField(wsetF);
+ struct3.addField(structwsetF);
+
+ Field structl1s1("l1s1", 11, struct3, true);
+
+ DocumentType type("test");
+ type.addField(primitive1);
+ type.addField(structl1s1);
+
+ Document::UP doc(new Document(type, DocumentId("doc::testdoc")));
+ doc->setValue(primitive1, IntFieldValue(1));
+
+ StructFieldValue l1s1(struct3);
+ l1s1.setValue(primitive1, IntFieldValue(2));
+
+ StructFieldValue l2s1(struct2);
+ l2s1.setValue(primitive1, IntFieldValue(3));
+ l2s1.setValue(primitive2, IntFieldValue(4));
+ StructFieldValue l2s2(struct2);
+ l2s2.setValue(primitive1, IntFieldValue(5));
+ l2s2.setValue(primitive2, IntFieldValue(6));
+ ArrayFieldValue iarr1(iarr);
+ iarr1.add(IntFieldValue(11));
+ iarr1.add(IntFieldValue(12));
+ iarr1.add(IntFieldValue(13));
+ ArrayFieldValue sarr1(sarr);
+ StructFieldValue l3s1(struct1);
+ l3s1.setValue(primitive1, IntFieldValue(1));
+ l3s1.setValue(primitive2, IntFieldValue(2));
+ sarr1.add(l3s1);
+ sarr1.add(l3s1);
+ MapFieldValue smap1(smap);
+ smap1.put(StringFieldValue("leonardo"), StringFieldValue("dicaprio"));
+ smap1.put(StringFieldValue("ellen"), StringFieldValue("page"));
+ smap1.put(StringFieldValue("joseph"), StringFieldValue("gordon-levitt"));
+ l2s1.setValue(smapF, smap1);
+ l2s1.setValue(iarrF, iarr1);
+ l2s1.setValue(sarrF, sarr1);
+
+ l1s1.setValue(s2, l2s1);
+ MapFieldValue structmap1(structmap);
+ structmap1.put(StringFieldValue("test"), l2s1);
+ l1s1.setValue(structmapF, structmap1);
+
+ WeightedSetFieldValue wset1(wset);
+ wset1.add("foo");
+ wset1.add("bar");
+ wset1.add("zoo");
+ l1s1.setValue(wsetF, wset1);
+
+ WeightedSetFieldValue wset2(structwset);
+ wset2.add(l2s1);
+ wset2.add(l2s2);
+ l1s1.setValue(structwsetF, wset2);
+
+ doc->setValue(structl1s1, l1s1);
+
+ // Begin test proper
+
+ doc->print(std::cerr, true, "");
+
+ ModifyIteratorHandler handler;
+
+ FieldPath::UP path
+ = doc->getDataType()->buildFieldPath("l1s1.structmap.value.smap{leonardo}");
+ doc->iterateNested(path->begin(), path->end(), handler);
+
+ doc->print(std::cerr, true, "");
+}
+
+void DocumentTest::testSimpleUsage()
+{
+ DocumentType::SP type(new DocumentType("test"));
+ Field intF("int", 1, *DataType::INT, true);
+ Field longF("long", 2, *DataType::LONG, true);
+ Field strF("content", 4, *DataType::STRING, false);
+
+ type->addField(intF);
+ type->addField(longF);
+ type->addField(strF);
+
+ DocumentTypeRepo repo(*type);
+ Document value(*repo.getDocumentType("test"), DocumentId("doc::testdoc"));
+
+ // Initially empty
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value.getSetFieldCount());
+ CPPUNIT_ASSERT(!value.hasValue(intF));
+
+ value.setValue(intF, IntFieldValue(1));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(1), value.getSetFieldCount());
+ CPPUNIT_ASSERT(value.hasValue(intF));
+
+ // Adding some more
+ value.setValue(longF, LongFieldValue(2));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(2), value.getSetFieldCount());
+ CPPUNIT_ASSERT_EQUAL(1, value.getValue(intF)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(2, value.getValue(longF)->getAsInt());
+
+ // Serialize & equality
+ std::unique_ptr<ByteBuffer> buffer(value.serialize());
+ buffer->flip();
+ Document value2(*repo.getDocumentType("test"),
+ DocumentId("userdoc::3:foo"));
+ CPPUNIT_ASSERT(value != value2);
+ value2.deserialize(repo, *buffer);
+ CPPUNIT_ASSERT(value2.hasValue(intF));
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ CPPUNIT_ASSERT_EQUAL(DocumentId("doc::testdoc"), value2.getId());
+
+ // Various ways of removing
+ {
+ // By value
+ buffer->setPos(0);
+ value2.deserialize(repo, *buffer);
+ value2.remove(intF);
+ CPPUNIT_ASSERT(!value2.hasValue(intF));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), value2.getSetFieldCount());
+
+ // Clearing all
+ buffer->setPos(0);
+ value2.deserialize(repo, *buffer);
+ value2.clear();
+ CPPUNIT_ASSERT(!value2.hasValue(intF));
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value2.getSetFieldCount());
+ }
+
+ // Updating
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ value2.setValue(strF, StringFieldValue("foo"));
+ CPPUNIT_ASSERT(value2.hasValue(strF));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("foo"),
+ value2.getValue(strF)->getAsString());
+ CPPUNIT_ASSERT(value != value2);
+ value2.assign(value);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ Document::UP valuePtr(value2.clone());
+ CPPUNIT_ASSERT_EQUAL(value, *valuePtr);
+
+ // Iterating
+ const Document& constVal(value);
+ for(Document::const_iterator it = constVal.begin();
+ it != constVal.end(); ++it)
+ {
+ constVal.getValue(it.field());
+ }
+
+ // Comparison
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(0, value.compare(value2));
+ value2.remove(intF);
+ CPPUNIT_ASSERT(value.compare(value2) < 0);
+ CPPUNIT_ASSERT(value2.compare(value) > 0);
+ value2 = value;
+ value2.setValue(intF, IntFieldValue(5));
+ CPPUNIT_ASSERT(value.compare(value2) < 0);
+ CPPUNIT_ASSERT(value2.compare(value) > 0);
+
+ // Output
+ CPPUNIT_ASSERT_EQUAL(
+ std::string("Document(doc::testdoc, DocumentType(test))"),
+ value.toString(false));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(
+" Document(doc::testdoc\n"
+" DocumentType(test, id -877171244)\n"
+" : DocumentType(document) {\n"
+" StructDataType(test.header, id 306916075) {\n"
+" Field(content, id 4, PrimitiveDataType(String, id 2), body)\n"
+" Field(int, id 1, NumericDataType(Int, id 0), header)\n"
+" Field(long, id 2, NumericDataType(Long, id 4), header)\n"
+" }\n"
+" }\n"
+" int: 1\n"
+" long: 2\n"
+" )"),
+ " " + value.toString(true, " "));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(
+ "<document documenttype=\"test\" documentid=\"doc::testdoc\">\n"
+ " <int>1</int>\n"
+ " <long>2</long>\n"
+ "</document>"),
+ value.toXml(" "));
+
+ // Failure situations.
+
+ // Fetch a field not existing in type
+ // (Would be nice if this failed, but whole idea to fetch by field
+ // objects is to improve performance.)
+ Field anotherIntF("int", 17, *DataType::INT, true);
+ CPPUNIT_ASSERT(!value.hasValue(anotherIntF));
+ CPPUNIT_ASSERT(value.getValue(anotherIntF).get() == 0);
+
+ // Refuse to accept non-document types
+ try{
+ StructDataType otherType("foo", 4);
+ Document value6(otherType, DocumentId("doc::"));
+ CPPUNIT_FAIL("Didn't complain about non-document type");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot generate a document with "
+ "non-document type", e.what());
+ }
+
+ // Refuse to set wrong types
+ try{
+ value2.setValue(intF, StringFieldValue("bar"));
+ CPPUNIT_FAIL("Failed to check type equality in setValue");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot assign value of type", e.what());
+ }
+}
+
+void verifyJavaDocument(Document& doc)
+{
+ IntFieldValue intVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("intfield"), intVal));
+ CPPUNIT_ASSERT_EQUAL(5, intVal.getAsInt());
+
+ FloatFieldValue floatVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("floatfield"), floatVal));
+ CPPUNIT_ASSERT(floatVal.getAsFloat() == (float) -9.23);
+
+ StringFieldValue stringVal("");
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("stringfield"), stringVal));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("This is a string."),
+ stringVal.getAsString());
+
+ LongFieldValue longVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("longfield"), longVal));
+ CPPUNIT_ASSERT_EQUAL((int64_t)398420092938472983LL, longVal.getAsLong());
+
+ DoubleFieldValue doubleVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("doublefield"), doubleVal));
+ CPPUNIT_ASSERT_EQUAL(doubleVal.getAsDouble(), 98374532.398820);
+
+ ByteFieldValue byteVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("bytefield"), byteVal));
+ CPPUNIT_ASSERT_EQUAL(-2, byteVal.getAsInt());
+
+ RawFieldValue rawVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("rawfield"), rawVal));
+ CPPUNIT_ASSERT(memcmp(rawVal.getAsRaw().first, "RAW DATA", 8) == 0);
+
+ Document embedDocVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("docfield"), embedDocVal));
+
+ ArrayFieldValue array(doc.getField("arrayoffloatfield").getDataType());
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("arrayoffloatfield"), array));
+ CPPUNIT_ASSERT_EQUAL((float)1.0, array[0].getAsFloat());
+ CPPUNIT_ASSERT_EQUAL((float)2.0, array[1].getAsFloat());
+
+ WeightedSetFieldValue wset(doc.getField("wsfield").getDataType());
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("wsfield"), wset));
+ CPPUNIT_ASSERT_EQUAL(50, wset.get(StringFieldValue("Weighted 0")));
+ CPPUNIT_ASSERT_EQUAL(199, wset.get(StringFieldValue("Weighted 1")));
+
+ MapFieldValue map(doc.getField("mapfield").getDataType());
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("mapfield"), map));
+ CPPUNIT_ASSERT(map.get(StringFieldValue("foo1")).get());
+ CPPUNIT_ASSERT(map.get(StringFieldValue("foo2")).get());
+ CPPUNIT_ASSERT_EQUAL(StringFieldValue("bar1"), dynamic_cast<StringFieldValue&>(*map.get(StringFieldValue("foo1"))));
+ CPPUNIT_ASSERT_EQUAL(StringFieldValue("bar2"), dynamic_cast<StringFieldValue&>(*map.get(StringFieldValue("foo2"))));
+}
+
+void DocumentTest::testReadSerializedFile()
+{
+ // Reads a file serialized from java
+ const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(file_name));
+
+ int fd = open("data/serializejava.dat", O_RDONLY);
+
+ size_t len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+
+ close(fd);
+
+ Document doc(repo, buf);
+ verifyJavaDocument(doc);
+
+ std::unique_ptr<ByteBuffer> buf2 = doc.serialize();
+ buf2->flip();
+
+ Document doc2(repo, *buf2);
+ verifyJavaDocument(doc2);
+
+ CPPUNIT_ASSERT_EQUAL(len, buf2->getPos());
+ CPPUNIT_ASSERT(memcmp(buf2->getBuffer(), buf.getBuffer(), buf2->getPos()) == 0);
+
+ doc2.setValue("stringfield", StringFieldValue("hei"));
+
+ std::unique_ptr<ByteBuffer> buf3 = doc2.serialize();
+ CPPUNIT_ASSERT(len != buf3->getPos());
+}
+
+void DocumentTest::testReadSerializedFileCompressed()
+{
+ // Reads a file serialized from java
+ const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(file_name));
+
+ int fd = open("data/serializejava-compressed.dat", O_RDONLY);
+
+ int len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+
+ close(fd);
+
+ Document doc(repo, buf);
+ verifyJavaDocument(doc);
+}
+
+namespace {
+ struct TestDoc {
+ std::string _dataFile;
+ /**
+ * We may add or remove types as we create new versions. If we do so,
+ * we can use the created version to know what types we no longer
+ * should check, or what fields these old documents does not contain.
+ */
+ uint32_t _createdVersion;
+
+ TestDoc(const std::string& dataFile, uint32_t version)
+ : _dataFile(dataFile),
+ _createdVersion(version)
+ {
+ }
+ };
+}
+
+/**
+ * Tests serialization of all versions.
+ *
+ * This test tests serialization and deserialization of documents of all
+ * supported types.
+ *
+ * Serialization is only supported in newest format. Deserialization should work
+ * for all formats supported, but only the part that makes sense in the new
+ * format. Thus, if new format deprecates a datatype, that datatype, when
+ * serializing old versions, must either just be dropped or converted.
+ *
+ * Thus, we create document type programmatically, because all old versions need
+ * to make sense with current config.
+ *
+ * When we create a document programmatically. This is serialized into current
+ * version files. When altering the format, after the alteration, copy the
+ * current version files to a specific version file and add those to list of
+ * files this test checks.
+ *
+ * When adding new fields to the documents, use the version tagged with each
+ * file to ignore these field for old types.
+ */
+void DocumentTest::testReadSerializedAllVersions()
+{
+ const int array_id = 1650586661;
+ const int wset_id = 1328286588;
+ // Create the datatype used for serialization test
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(1306012852, "serializetest",
+ Struct("serializetest.header")
+ .addField("floatfield", DataType::T_FLOAT)
+ .addField("stringfield", DataType::T_STRING)
+ .addField("longfield", DataType::T_LONG)
+ .addField("urifield", DataType::T_URI),
+ Struct("serializetest.body")
+ .addField("intfield", DataType::T_INT)
+ .addField("rawfield", DataType::T_RAW)
+ .addField("doublefield", DataType::T_DOUBLE)
+ .addField("bytefield", DataType::T_BYTE)
+ .addField("arrayoffloatfield",
+ Array(DataType::T_FLOAT).setId(array_id))
+ .addField("docfield", DataType::T_DOCUMENT)
+ .addField("wsfield",
+ Wset(DataType::T_STRING).setId(wset_id)));
+ builder.document(1447635645, "docindoc", Struct("docindoc.header"),
+ Struct("docindoc.body")
+ .addField("stringindocfield", DataType::T_STRING));
+ DocumentTypeRepo repo(builder.config());
+
+ const DocumentType* docType(repo.getDocumentType("serializetest"));
+ const DocumentType* docInDocType(repo.getDocumentType("docindoc"));
+ const DataType* arrayOfFloatDataType(repo.getDataType(*docType, array_id));
+ const DataType* weightedSetDataType(repo.getDataType(*docType, wset_id));
+
+ // Create a memory instance of document
+ {
+ Document doc(*docType,
+ DocumentId("doc:serializetest:http://test.doc.id/"));
+ doc.set("intfield", 5);
+ doc.set("floatfield", -9.23);
+ doc.set("stringfield", "This is a string.");
+ doc.set("longfield", static_cast<int64_t>(398420092938472983LL));
+ doc.set("doublefield", 98374532.398820);
+ doc.set("bytefield", -2);
+ doc.setValue("rawfield", RawFieldValue("RAW DATA", 8));
+ Document docInDoc(*docInDocType,
+ DocumentId("doc:serializetest:http://doc.in.doc/"));
+ docInDoc.set("stringindocfield", "Elvis is dead");
+ //docInDoc.setCompression(CompressionConfig(CompressionConfig::NONE, 0, 0));
+ doc.setValue("docfield", docInDoc);
+ ArrayFieldValue floatArray(*arrayOfFloatDataType);
+ floatArray.add(1.0);
+ floatArray.add(2.0);
+ doc.setValue("arrayoffloatfield", floatArray);
+ WeightedSetFieldValue weightedSet(*weightedSetDataType);
+ weightedSet.add(StringFieldValue("Weighted 0"), 50);
+ weightedSet.add(StringFieldValue("Weighted 1"), 199);
+ doc.setValue("wsfield", weightedSet);
+
+ // Write document to disk, (when you bump version and alter stuff,
+ // you can copy this current to new test for new version)
+ {
+ //doc.setCompression(CompressionConfig(CompressionConfig::NONE, 0, 0));
+ std::unique_ptr<ByteBuffer> buf = doc.serialize();
+ CPPUNIT_ASSERT_EQUAL(buf->getLength(), buf->getPos());
+ int fd = open("data/document-cpp-currentversion-uncompressed.dat",
+ O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ CPPUNIT_ASSERT(fd > 0);
+ size_t len = write(fd, buf->getBuffer(), buf->getPos());
+ CPPUNIT_ASSERT_EQUAL(buf->getPos(), len);
+ close(fd);
+ }
+ {
+ CompressionConfig oldCfg(doc.getType().getFieldsType().getCompressionConfig());
+ CompressionConfig newCfg(CompressionConfig::LZ4, 9, 95);
+ const_cast<StructDataType &>(doc.getType().getFieldsType()).setCompressionConfig(newCfg);
+ std::unique_ptr<ByteBuffer> buf = doc.serialize();
+ CPPUNIT_ASSERT(buf->getPos() <= buf->getLength());
+ int fd = open("data/document-cpp-currentversion-lz4-9.dat",
+ O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ CPPUNIT_ASSERT(fd > 0);
+ size_t len = write(fd, buf->getBuffer(), buf->getPos());
+ CPPUNIT_ASSERT_EQUAL(buf->getPos(), len);
+ close(fd);
+ const_cast<StructDataType &>(doc.getType().getFieldsType()).setCompressionConfig(oldCfg);
+ }
+ }
+
+ std::string jpath = "../test/serializeddocuments/";
+
+ std::vector<TestDoc> tests;
+ tests.push_back(TestDoc("data/document-cpp-v8-uncompressed.dat", 8));
+ tests.push_back(TestDoc("data/document-cpp-v7-uncompressed.dat", 7));
+ tests.push_back(TestDoc("data/serializev6.dat", 6));
+ tests.push_back(TestDoc(jpath + "document-java-v8-uncompressed.dat", 8));
+ for (uint32_t i=0; i<tests.size(); ++i) {
+ int version = tests[i]._createdVersion;
+ std::string name = tests[i]._dataFile;
+ std::cerr << name << std::endl;
+ if (!vespalib::fileExists(name)) {
+ CPPUNIT_FAIL("File " + name + " does not exist.");
+ }
+ int fd = open(tests[i]._dataFile.c_str(), O_RDONLY);
+ int len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+ close(fd);
+
+ Document doc(repo, buf);
+
+ IntFieldValue intVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("intfield"), intVal));
+ CPPUNIT_ASSERT_EQUAL(5, intVal.getAsInt());
+
+ FloatFieldValue floatVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("floatfield"), floatVal));
+ CPPUNIT_ASSERT(floatVal.getAsFloat() == (float) -9.23);
+
+ StringFieldValue stringVal("");
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("stringfield"), stringVal));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("This is a string."),
+ stringVal.getAsString());
+
+ LongFieldValue longVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("longfield"), longVal));
+ CPPUNIT_ASSERT_EQUAL(static_cast<int64_t>(398420092938472983LL),
+ longVal.getAsLong());
+
+ DoubleFieldValue doubleVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("doublefield"), doubleVal));
+ CPPUNIT_ASSERT_EQUAL(doubleVal.getAsDouble(), 98374532.398820);
+
+ ByteFieldValue byteVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("bytefield"), byteVal));
+ CPPUNIT_ASSERT_EQUAL(-2, byteVal.getAsInt());
+
+ RawFieldValue rawVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("rawfield"), rawVal));
+ CPPUNIT_ASSERT(memcmp(rawVal.getAsRaw().first, "RAW DATA", 8) == 0);
+
+ if (version > 6) {
+ Document docInDoc;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("docfield"), docInDoc));
+
+ CPPUNIT_ASSERT(docInDoc.getValue(
+ docInDoc.getField("stringindocfield"), stringVal));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("Elvis is dead"),
+ stringVal.getAsString());
+ }
+
+ ArrayFieldValue array(doc.getField("arrayoffloatfield").getDataType());
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("arrayoffloatfield"), array));
+ CPPUNIT_ASSERT_EQUAL((float)1.0, array[0].getAsFloat());
+ CPPUNIT_ASSERT_EQUAL((float)2.0, array[1].getAsFloat());
+
+ WeightedSetFieldValue wset(doc.getField("wsfield").getDataType());
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("wsfield"), wset));
+ CPPUNIT_ASSERT_EQUAL(50, wset.get(StringFieldValue("Weighted 0")));
+ CPPUNIT_ASSERT_EQUAL(199, wset.get(StringFieldValue("Weighted 1")));
+
+ // Check that serialization doesn't cause any problems.
+ std::unique_ptr<ByteBuffer> buf2 = doc.serialize();
+ buf2->flip();
+
+ Document doc2(repo, *buf2);
+ }
+}
+
+size_t getSerializedSize(const Document &doc) {
+ return doc.serialize()->getLength();
+}
+
+size_t getSerializedSizeHeader(const Document &doc) {
+ nbostream stream;
+ doc.serializeHeader(stream);
+ return stream.size();
+}
+
+size_t getSerializedSizeBody(const Document &doc) {
+ nbostream stream;
+ doc.serializeBody(stream);
+ return stream.size();
+}
+
+void DocumentTest::testGenerateSerializedFile()
+{
+ const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(file_name));
+ Document doc(*repo.getDocumentType("serializetest"),
+ DocumentId(DocIdString("serializetest",
+ "http://test.doc.id/")));
+
+ doc.set("intfield", 5);
+ doc.set("floatfield", -9.23);
+ doc.set("stringfield", "This is a string.");
+ doc.set("longfield", (int64_t) 398420092938472983ll);
+ doc.set("doublefield", 98374532.398820);
+ doc.set("urifield", "http://this.is.a.test/");
+ doc.set("bytefield", -2);
+ doc.set("rawfield", "RAW DATA");
+
+ const DocumentType *docindoc_type = repo.getDocumentType("docindoc");
+ CPPUNIT_ASSERT(docindoc_type);
+ Document embedDoc(*docindoc_type,
+ DocumentId(DocIdString("docindoc", "http://embedded")));
+
+ doc.setValue("docfield", embedDoc);
+
+ WeightedSetFieldValue wset(doc.getField("wsfield").getDataType());
+ wset.add(StringFieldValue("Weighted 0"), 50);
+ wset.add(StringFieldValue("Weighted 1"), 199);
+ doc.setValue("wsfield", wset);
+
+ ArrayFieldValue array(doc.getField("arrayoffloatfield").getDataType());
+ array.add(FloatFieldValue(1.0));
+ array.add(FloatFieldValue(2.0));
+ doc.setValue("arrayoffloatfield", array);
+
+ MapFieldValue map(doc.getField("mapfield").getDataType());
+ map.put(StringFieldValue("foo1"), StringFieldValue("bar1"));
+ map.put(StringFieldValue("foo2"), StringFieldValue("bar2"));
+ doc.setValue("mapfield", map);
+
+ std::unique_ptr<ByteBuffer> buf = doc.serialize();
+
+#define SERIALIZED_DIR "../../test/document/"
+ int fd = open(SERIALIZED_DIR "/serializecpp.dat",
+ O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ write(fd, buf->getBuffer(), buf->getPos());
+ close(fd);
+
+ ByteBuffer hBuf(getSerializedSizeHeader(doc));
+ doc.serializeHeader(hBuf);
+ fd = open(SERIALIZED_DIR "/serializecppsplit_header.dat",
+ O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ write(fd, hBuf.getBuffer(), hBuf.getPos());
+ close(fd);
+
+ ByteBuffer bBuf(getSerializedSizeBody(doc));
+ doc.serializeBody(bBuf);
+ fd = open(SERIALIZED_DIR "/serializecppsplit_body.dat",
+ O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ write(fd, bBuf.getBuffer(), bBuf.getPos());
+ close(fd);
+
+
+ CompressionConfig newCfg(CompressionConfig::LZ4, 9, 95);
+ const_cast<StructDataType &>(doc.getType().getFieldsType()).setCompressionConfig(newCfg);
+
+ ByteBuffer lz4buf(getSerializedSize(doc));
+
+ doc.serialize(lz4buf);
+ lz4buf.flip();
+
+ fd = open(SERIALIZED_DIR "/serializecpp-lz4-level9.dat",
+ O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ write(fd, lz4buf.getBufferAtPos(), lz4buf.getRemaining());
+ close(fd);
+}
+void DocumentTest::testGetURIFromSerialized()
+{
+ TestDocRepo test_repo;
+ Document doc(*test_repo.getDocumentType("testdoctype1"),
+ DocumentId("doc:ns:testdoc"));
+
+ {
+ std::unique_ptr<ByteBuffer> serialized = doc.serialize();
+ serialized->flip();
+
+ CPPUNIT_ASSERT_EQUAL(
+ vespalib::string(DocIdString("ns", "testdoc").toString()),
+ Document::getIdFromSerialized(*serialized).toString());
+
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("testdoctype1"),
+ Document::getDocTypeFromSerialized(
+ test_repo.getTypeRepo(),
+ *serialized)->getName());
+ }
+
+ {
+ std::unique_ptr<ByteBuffer> serialized = doc.serialize();
+ serialized->flip();
+
+ Document doc2(test_repo.getTypeRepo(), *serialized, false, NULL);
+ CPPUNIT_ASSERT_EQUAL(
+ vespalib::string(DocIdString("ns", "testdoc").toString()), doc2.getId().toString());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("testdoctype1"), doc2.getType().getName());
+ }
+};
+
+void DocumentTest::testBogusserialize()
+{
+ TestDocRepo test_repo;
+ try {
+ std::unique_ptr<ByteBuffer> buf(
+ new ByteBuffer("aoifjweprjwoejr203r+2+4r823++!",100));
+ Document doc(test_repo.getTypeRepo(), *buf);
+ CPPUNIT_ASSERT(false);
+ } catch (DeserializeException& e) {
+ CPPUNIT_ASSERT_CONTAIN("Unrecognized serialization version", e.what());
+ }
+
+ try {
+ std::unique_ptr<ByteBuffer> buf(new ByteBuffer("",0));
+ Document doc(test_repo.getTypeRepo(), *buf);
+ CPPUNIT_ASSERT(false);
+ } catch (DeserializeException& e) {
+ CPPUNIT_ASSERT_CONTAIN("Buffer out of bounds", e.what());
+ }
+}
+
+void DocumentTest::testCRC32()
+{
+ TestDocRepo test_repo;
+ Document doc(*test_repo.getDocumentType("testdoctype1"),
+ DocumentId(DocIdString("crawler", "http://www.ntnu.no/")));
+
+ doc.setValue(doc.getField("hstringval"),
+ StringFieldValue("bla bla bla bla bla"));
+
+ uint32_t crc = doc.calculateChecksum();
+ CPPUNIT_ASSERT_EQUAL(277496115u, crc);
+
+ std::unique_ptr<ByteBuffer> buf = doc.serialize();
+ buf->flip();
+
+ int pos = 20;
+
+ // Corrupt serialization.
+ buf->getBuffer()[pos] ^= 72;
+ // Create document. Byte corrupted above is in data area and
+ // shouldn't fail deserialization.
+ try {
+ Document doc2(test_repo.getTypeRepo(), *buf);
+ buf->setPos(0);
+ CPPUNIT_ASSERT(crc != doc2.calculateChecksum());
+ } catch (document::DeserializeException& e) {
+ CPPUNIT_ASSERT(false);
+ }
+ // Return original value and retry
+ buf->getBuffer()[pos] ^= 72;
+
+ /// \todo TODO (was warning): Cannot test for in memory representation altered, as there is no syntax for getting internal refs to data from document. Add test when this is added.
+}
+
+void
+DocumentTest::testHasChanged()
+{
+ TestDocRepo test_repo;
+ Document doc(*test_repo.getDocumentType("testdoctype1"),
+ DocumentId(DocIdString("crawler", "http://www.ntnu.no/")));
+ // Before deserialization we are changed.
+ CPPUNIT_ASSERT(doc.hasChanged());
+
+ doc.setValue(doc.getField("hstringval"),
+ StringFieldValue("bla bla bla bla bla"));
+ // Still changed after setting a value of course.
+ CPPUNIT_ASSERT(doc.hasChanged());
+
+ std::unique_ptr<ByteBuffer> buf = doc.serialize();
+ buf->flip();
+
+ // Setting a value in doc tags us changed.
+ {
+ buf->setPos(0);
+ Document doc2(test_repo.getTypeRepo(), *buf);
+ CPPUNIT_ASSERT(!doc2.hasChanged());
+
+ doc2.set("headerval", 13);
+ CPPUNIT_ASSERT(doc2.hasChanged());
+ }
+ // Overwriting a value in doc tags us changed.
+ {
+ buf->setPos(0);
+ Document doc2(test_repo.getTypeRepo(), *buf);
+
+ doc2.set("hstringval", "bla bla bla bla bla");
+ CPPUNIT_ASSERT(doc2.hasChanged());
+ }
+ // Clearing value tags us changed.
+ {
+ buf->setPos(0);
+ Document doc2(test_repo.getTypeRepo(), *buf);
+
+ doc2.clear();
+ CPPUNIT_ASSERT(doc2.hasChanged());
+ }
+ // Add more tests here when we allow non-const refs to internals
+}
+
+void
+DocumentTest::testSplitSerialization()
+{
+ TestDocMan testDocMan;
+ Document::UP doc = testDocMan.createDocument();
+ doc->set("headerval", 50);
+
+ ByteBuffer buf(getSerializedSizeHeader(*doc));
+ doc->serializeHeader(buf);
+ buf.flip();
+
+ ByteBuffer buf2(getSerializedSizeBody(*doc));
+ doc->serializeBody(buf2);
+ buf2.flip();
+
+ CPPUNIT_ASSERT_EQUAL(size_t(65), buf.getLength());
+ CPPUNIT_ASSERT_EQUAL(size_t(73), buf2.getLength());
+
+ Document headerDoc(testDocMan.getTypeRepo(), buf);
+ CPPUNIT_ASSERT(headerDoc.hasValue("headerval"));
+ CPPUNIT_ASSERT(!headerDoc.hasValue("content"));
+
+ buf.setPos(0);
+ Document fullDoc(testDocMan.getTypeRepo(), buf, buf2);
+ CPPUNIT_ASSERT(fullDoc.hasValue("headerval"));
+ CPPUNIT_ASSERT(fullDoc.hasValue("content"));
+
+ CPPUNIT_ASSERT_EQUAL(*doc, fullDoc);
+}
+
+void DocumentTest::testSliceSerialize()
+{
+ // Test that document doesn't need its own bytebuffer, such that we
+ // can serialize multiple documents after each other in the same
+ // bytebuffer.
+ TestDocMan testDocMan;
+ Document::UP doc = testDocMan.createDocument();
+ Document::UP doc2 = testDocMan.createDocument(
+ "Some other content", "doc:test:anotherdoc");
+
+ ArrayFieldValue val(doc2->getField("rawarray").getDataType());
+ val.add(RawFieldValue("hei", 3));
+ val.add(RawFieldValue("hallo", 5));
+ val.add(RawFieldValue("hei der", 7));
+ doc2->setValue(doc2->getField("rawarray"), val);
+
+ ByteBuffer buf(getSerializedSize(*doc) + getSerializedSize(*doc2));
+ doc->serialize(buf);
+ CPPUNIT_ASSERT_EQUAL(getSerializedSize(*doc), buf.getPos());
+ doc2->serialize(buf);
+ CPPUNIT_ASSERT_EQUAL(getSerializedSize(*doc) + getSerializedSize(*doc2),
+ buf.getPos());
+ buf.flip();
+
+ Document doc3(testDocMan.getTypeRepo(), buf);
+ CPPUNIT_ASSERT_EQUAL(getSerializedSize(*doc), buf.getPos());
+ Document doc4(testDocMan.getTypeRepo(), buf);
+ CPPUNIT_ASSERT_EQUAL(getSerializedSize(*doc) + getSerializedSize(*doc2),
+ buf.getPos());
+
+ CPPUNIT_ASSERT_EQUAL(*doc, doc3);
+ CPPUNIT_ASSERT_EQUAL(*doc2, doc4);
+}
+
+void DocumentTest::testCompression()
+{
+ TestDocMan testDocMan;
+ Document::UP doc = testDocMan.createDocument();
+
+ std::string bigString("compress me");
+ for (int i = 0; i < 8; ++i) { bigString += bigString; }
+
+ doc->setValue("hstringval", StringFieldValue(bigString));
+
+ std::unique_ptr<ByteBuffer> buf_uncompressed = doc->serialize();
+ buf_uncompressed->flip();
+
+ CompressionConfig oldCfg(doc->getType().getFieldsType().getCompressionConfig());
+
+ CompressionConfig newCfg(CompressionConfig::LZ4, 9, 95);
+ const_cast<StructDataType &>(doc->getType().getFieldsType()).setCompressionConfig(newCfg);
+ std::unique_ptr<ByteBuffer> buf_lz4 = doc->serialize();
+ buf_lz4->flip();
+
+ const_cast<StructDataType &>(doc->getType().getFieldsType()).setCompressionConfig(oldCfg);
+
+ CPPUNIT_ASSERT(buf_lz4->getRemaining() < buf_uncompressed->getRemaining());
+
+ Document doc_lz4(testDocMan.getTypeRepo(), *buf_lz4);
+
+ CPPUNIT_ASSERT_EQUAL(*doc, doc_lz4);
+}
+
+void DocumentTest::testCompressionConfigured()
+{
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(43, "serializetest",
+ Struct("serializetest.header").setId(44),
+ Struct("serializetest.body").setId(45)
+ .addField("stringfield", DataType::T_STRING));
+ DocumentTypeRepo repo(builder.config());
+ Document doc_uncompressed(
+ *repo.getDocumentType("serializetest"),
+ DocumentId("doc:test:test"));
+
+ std::string bigString("compress me");
+ for (int i = 0; i < 8; ++i) { bigString += bigString; }
+
+ doc_uncompressed.setValue("stringfield", StringFieldValue(bigString));
+ std::unique_ptr<ByteBuffer> buf_uncompressed = doc_uncompressed.serialize();
+ buf_uncompressed->flip();
+
+ size_t uncompressedSize = buf_uncompressed->getRemaining();
+
+ DocumenttypesConfigBuilderHelper builder2;
+ builder2.document(43, "serializetest",
+ Struct("serializetest.header").setId(44),
+ Struct("serializetest.body").setId(45)
+ .addField("stringfield", DataType::T_STRING)
+ .setCompression(DocumenttypesConfig::Documenttype::
+ Datatype::Sstruct::Compression::LZ4,
+ 9, 99, 0));
+ DocumentTypeRepo repo2(builder2.config());
+
+ Document doc(repo2, *buf_uncompressed);
+
+ std::unique_ptr<ByteBuffer> buf_compressed = doc.serialize();
+ buf_compressed->flip();
+ size_t compressedSize = buf_compressed->getRemaining();
+
+ CPPUNIT_ASSERT(compressedSize < uncompressedSize);
+
+ Document doc2(repo2, *buf_compressed);
+
+ std::unique_ptr<ByteBuffer> buf_compressed2 = doc2.serialize();
+ buf_compressed2->flip();
+
+ CPPUNIT_ASSERT_EQUAL(compressedSize, buf_compressed2->getRemaining());
+
+ Document doc3(repo2, *buf_compressed2);
+
+ CPPUNIT_ASSERT_EQUAL(doc2, doc_uncompressed);
+ CPPUNIT_ASSERT_EQUAL(doc2, doc3);
+}
+
+void
+DocumentTest::testUnknownEntries()
+{
+ // We should be able to deserialize a document with unknown values in it.
+ DocumentType type1("test", 0);
+ DocumentType type2("test", 0);
+ Field field1("int1", *DataType::INT, true);
+ Field field2("int2", *DataType::INT, true);
+ Field field3("int3", *DataType::INT, false);
+ Field field4("int4", *DataType::INT, false);
+
+ type1.addField(field1);
+ type1.addField(field2);
+ type1.addField(field3);
+ type1.addField(field4);
+
+ type2.addField(field3);
+ type2.addField(field4);
+
+ DocumentTypeRepo repo(type2);
+
+ Document doc1(type1, DocumentId("doc::testdoc"));
+ doc1.setValue(field1, IntFieldValue(1));
+ doc1.setValue(field2, IntFieldValue(2));
+ doc1.setValue(field3, IntFieldValue(3));
+ doc1.setValue(field4, IntFieldValue(4));
+
+ uint32_t headerLen = getSerializedSizeHeader(doc1);
+ document::ByteBuffer header(headerLen);
+ doc1.serializeHeader(header);
+ header.flip();
+
+ uint32_t bodyLen = getSerializedSizeBody(doc1);
+ document::ByteBuffer body(bodyLen);
+ doc1.serializeBody(body);
+ body.flip();
+
+ uint32_t totalLen = getSerializedSize(doc1);
+ document::ByteBuffer total(totalLen);
+ doc1.serialize(total);
+ total.flip();
+
+ Document doc2;
+ doc2.deserialize(repo, total);
+
+ Document doc3;
+ doc3.deserializeHeader(repo, header);
+ doc3.deserializeBody(repo, body);
+
+ CPPUNIT_ASSERT_EQUAL(std::string(
+ "<document documenttype=\"test\" documentid=\"doc::testdoc\">\n"
+ "<int3>3</int3>\n"
+ "<int4>4</int4>\n"
+ "</document>"), doc2.toXml());
+ CPPUNIT_ASSERT_EQUAL(std::string(
+ "<document documenttype=\"test\" documentid=\"doc::testdoc\">\n"
+ "<int3>3</int3>\n"
+ "<int4>4</int4>\n"
+ "</document>"), doc3.toXml());
+
+ CPPUNIT_ASSERT_EQUAL(3, doc2.getValue(field3)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(4, doc2.getValue(field4)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(3, doc3.getValue(field3)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(4, doc3.getValue(field4)->getAsInt());
+
+ // The fields are actually accessible as long as you ask with field of
+ // correct type.
+
+ CPPUNIT_ASSERT(doc2.hasValue(field1));
+ CPPUNIT_ASSERT(doc2.hasValue(field2));
+ CPPUNIT_ASSERT(doc3.hasValue(field1));
+ CPPUNIT_ASSERT(doc3.hasValue(field2));
+
+ CPPUNIT_ASSERT_EQUAL(1, doc2.getValue(field1)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(2, doc2.getValue(field2)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(1, doc3.getValue(field1)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(2, doc3.getValue(field2)->getAsInt());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(2), doc2.getSetFieldCount());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), doc3.getSetFieldCount());
+
+ // Copy paste of above test to read an old version document and
+ // deserialize it with some fields lacking to see that it doesn't
+ // report failure. (Had a bug on this earlier)
+ int fd = open("data/serializev6.dat", O_RDONLY);
+ int len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+ close(fd);
+
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(42, "docindoc", Struct("docindoc.header"),
+ Struct("docindoc.body")
+ .addField("stringindocfield", DataType::T_STRING));
+ builder.document(43, "serializetest",
+ Struct("serializetest.header")
+ .addField("floatfield", DataType::T_FLOAT),
+ Struct("serializetest.body")
+ .addField("rawfield", DataType::T_RAW));
+ DocumentTypeRepo repo2(builder.config());
+
+ Document doc(repo2, buf);
+ doc.toXml();
+}
+
+void DocumentTest::testAnnotationDeserialization()
+{
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(-1326249427, "dokk", Struct("dokk.header"),
+ Struct("dokk.body")
+ .addField("age", DataType::T_BYTE)
+ .addField("story", DataType::T_STRING)
+ .addField("date", DataType::T_INT)
+ .addField("friend", DataType::T_LONG))
+ .annotationType(609952424, "person", Struct("person")
+ .addField("firstname", DataType::T_STRING)
+ .addField("lastname", DataType::T_STRING)
+ .addField("birthyear", DataType::T_INT)
+ .setId(443162583))
+ .annotationType(-1695443536, "dummy", 0)
+ .annotationType(-427420193, "number", DataType::T_INT)
+ .annotationType(1616020615, "relative", Struct("relative")
+ .addField("title", DataType::T_STRING)
+ .addField("related", AnnotationRef(609952424))
+ .setId(-236946034))
+ .annotationType(-269517759, "banana", 0)
+ .annotationType(-513687143, "grape", 0)
+ .annotationType(1730712959, "apple", 0);
+ DocumentTypeRepo repo(builder.config());
+
+ int fd = open("data/serializejavawithannotations.dat", O_RDONLY);
+ int len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+ close(fd);
+
+ Document doc(repo, buf);
+ StringFieldValue strVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("story"), strVal));
+
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(strVal);
+
+ FixedTypeRepo fixedRepo(repo, doc.getType());
+ VespaDocumentDeserializer deserializer(fixedRepo, stream, 8);
+ StringFieldValue strVal2;
+ deserializer.read(strVal2);
+ CPPUNIT_ASSERT_EQUAL(strVal.toString(), strVal2.toString());
+ CPPUNIT_ASSERT_EQUAL(strVal.toString(true), strVal2.toString(true));
+
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("help me help me i'm stuck inside a computer!"),
+ strVal.getAsString());
+ StringFieldValue::SpanTrees trees = strVal.getSpanTrees();
+ const SpanTree *span_tree = StringFieldValue::findTree(trees, "fruits");
+ CPPUNIT_ASSERT(span_tree);
+ CPPUNIT_ASSERT_EQUAL(size_t(8), span_tree->numAnnotations());
+ span_tree = StringFieldValue::findTree(trees, "ballooo");
+ CPPUNIT_ASSERT(span_tree);
+ CPPUNIT_ASSERT_EQUAL(size_t(8), span_tree->numAnnotations());
+
+
+ ByteFieldValue byteVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("age"), byteVal));
+ CPPUNIT_ASSERT_EQUAL( 123, byteVal.getAsInt());
+
+ IntFieldValue intVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("date"), intVal));
+ CPPUNIT_ASSERT_EQUAL(13829297, intVal.getAsInt());
+
+ LongFieldValue longVal;
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("friend"), longVal));
+ CPPUNIT_ASSERT_EQUAL((int64_t)2384LL, longVal.getAsLong());
+}
+
+void
+DocumentTest::testGetSerializedSize()
+{
+ TestDocMan testDocMan;
+ Document::UP doc = testDocMan.createDocument();
+
+ CPPUNIT_ASSERT_EQUAL(getSerializedSize(*doc), doc->getSerializedSize());
+}
+
+void
+DocumentTest::testDeserializeMultiple()
+{
+ TestDocRepo testDocRepo;
+ const DocumentTypeRepo& repo(testDocRepo.getTypeRepo());
+ const DocumentType& docType(*repo.getDocumentType("testdoctype1"));
+ CPPUNIT_ASSERT(&docType != 0);
+
+ StructFieldValue sv1(docType.getField("mystruct").getDataType());
+
+ StructFieldValue sv2(docType.getField("mystruct").getDataType());
+
+ sv1.setValue(sv1.getField("key"), IntFieldValue(1234));
+ sv2.setValue(sv2.getField("value"), StringFieldValue("badger"));
+
+ StructFieldValue sv3(docType.getField("mystruct").getDataType());
+
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(sv1);
+ serializer.write(sv2);
+
+ VespaDocumentDeserializer deserializer(repo, stream, 8);
+ deserializer.readStructNoReset(sv3);
+ deserializer.readStructNoReset(sv3);
+
+ StructFieldValue correct(docType.getField("mystruct").getDataType());
+
+ correct.setValue(correct.getField("key"), IntFieldValue(1234));
+ correct.setValue(correct.getField("value"), StringFieldValue("badger"));
+ CPPUNIT_ASSERT_EQUAL(correct, sv3);
+}
+
+} // document
diff --git a/document/src/tests/documenttypetestcase.cpp b/document/src/tests/documenttypetestcase.cpp
new file mode 100644
index 00000000000..e946844e867
--- /dev/null
+++ b/document/src/tests/documenttypetestcase.cpp
@@ -0,0 +1,254 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/datatype/documenttype.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <sstream>
+#include <string>
+
+using document::config_builder::Struct;
+using document::config_builder::Wset;
+using document::config_builder::Array;
+using document::config_builder::Map;
+
+
+namespace document {
+
+struct DocumentTypeTest : public CppUnit::TestFixture {
+ void setUp();
+ void tearDown();
+
+ void testSetGet();
+ void testHeaderContent();
+ void testFieldSet();
+ void testInheritance();
+ void testInheritanceConfig();
+ void testMultipleInheritance();
+ void testOutputOperator();
+
+ CPPUNIT_TEST_SUITE( DocumentTypeTest);
+ CPPUNIT_TEST(testSetGet);
+ CPPUNIT_TEST(testFieldSet);
+ CPPUNIT_TEST(testInheritance);
+ CPPUNIT_TEST(testInheritanceConfig);
+ CPPUNIT_TEST(testMultipleInheritance);
+ CPPUNIT_TEST(testOutputOperator);
+ CPPUNIT_TEST(testHeaderContent);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DocumentTypeTest);
+
+void DocumentTypeTest::setUp()
+{
+}
+
+void DocumentTypeTest::tearDown()
+{
+}
+
+void DocumentTypeTest::testSetGet() {
+ DocumentType docType("doctypetestdoc", 0);
+
+ docType.addField(Field("stringattr", 3, *DataType::STRING, true));
+ docType.addField(Field("nalle", 0, *DataType::INT, false));
+
+ const Field& fetch1=docType.getField("stringattr");
+ const Field& fetch2=docType.getField("stringattr");
+
+ CPPUNIT_ASSERT(fetch1==fetch2);
+ CPPUNIT_ASSERT(fetch1.getName() == "stringattr");
+
+ const Field& fetch3 = docType.getField(3, Document::getNewestSerializationVersion());
+
+ CPPUNIT_ASSERT(fetch1==fetch3);
+
+ const Field& fetch4=docType.getField(0, Document::getNewestSerializationVersion());
+
+ CPPUNIT_ASSERT(fetch4!=fetch1);
+}
+
+void
+categorizeFields(const Field::Set& fields,
+ std::vector<const Field*>& headers,
+ std::vector<const Field*>& bodies)
+{
+ for (Field::Set::const_iterator it(fields.begin()), e(fields.end());
+ it != e; ++it)
+ {
+ if ((*it)->isHeaderField()) {
+ headers.push_back(*it);
+ } else {
+ bodies.push_back(*it);
+ }
+ }
+}
+
+void
+DocumentTypeTest::testInheritanceConfig()
+{
+ DocumentTypeRepo
+ repo(readDocumenttypesConfig("data/inheritancetest.cfg"));
+
+ {
+ const DocumentType* type(repo.getDocumentType("music"));
+ CPPUNIT_ASSERT(type != NULL);
+ }
+
+ {
+ const DocumentType* type(repo.getDocumentType("books"));
+ CPPUNIT_ASSERT(type != NULL);
+ }
+}
+
+void
+DocumentTypeTest::testHeaderContent()
+{
+ DocumentTypeRepo
+ repo(readDocumenttypesConfig("data/doctypesconfigtest.cfg"));
+
+ const DocumentType* type(repo.getDocumentType("derived"));
+
+ Field::Set fields = type->getFieldsType().getFieldSet();
+
+ std::vector<const Field*> headers;
+ std::vector<const Field*> bodies;
+ categorizeFields(fields, headers, bodies);
+
+ CPPUNIT_ASSERT(headers.size() == 4);
+ CPPUNIT_ASSERT(headers[0]->getName() == "field1");
+ CPPUNIT_ASSERT(headers[1]->getName() == "field2");
+ CPPUNIT_ASSERT(headers[2]->getName() == "field4");
+ CPPUNIT_ASSERT(headers[3]->getName() == "fieldarray1");
+
+ CPPUNIT_ASSERT(bodies.size() == 2);
+ CPPUNIT_ASSERT(bodies[0]->getName() == "field3");
+ CPPUNIT_ASSERT(bodies[1]->getName() == "field5");
+}
+
+void DocumentTypeTest::testMultipleInheritance()
+{
+ config_builder::DocumenttypesConfigBuilderHelper builder;
+ builder.document(42, "test1", Struct("test1.header"),
+ Struct("test1.body")
+ .addField("stringattr", DataType::T_STRING)
+ .addField("nalle", DataType::T_INT));
+ builder.document(43, "test2", Struct("test2.header"),
+ Struct("test2.body")
+ .addField("stringattr", DataType::T_STRING)
+ .addField("tmp", DataType::T_STRING)
+ .addField("tall", DataType::T_INT));
+ builder.document(44, "test3",
+ Struct("test3.header"), Struct("test3.body"))
+ .inherit(42).inherit(43);
+ DocumentTypeRepo repo(builder.config());
+ const DocumentType* docType3(repo.getDocumentType("test3"));
+
+ CPPUNIT_ASSERT(docType3->hasField("stringattr"));
+ CPPUNIT_ASSERT(docType3->hasField("nalle"));
+ CPPUNIT_ASSERT(docType3->hasField("tmp"));
+ CPPUNIT_ASSERT(docType3->hasField("tall"));
+
+ Document doc(*docType3, DocumentId(DocIdString("test", "test")));
+
+ IntFieldValue intVal(3);
+ doc.setValue(doc.getField("nalle"), intVal);
+
+ StringFieldValue stringVal("tmp");
+ doc.setValue(doc.getField("tmp"), stringVal);
+
+ CPPUNIT_ASSERT(doc.getValue(doc.getField("nalle"))->getAsInt()==3);
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("tmp"),
+ doc.getValue(doc.getField("tmp"))->getAsString());
+}
+
+void DocumentTypeTest::testFieldSet()
+{
+ DocumentType docType("test1");
+ docType.addField(Field("stringattr", 3, *DataType::STRING, false));
+ docType.addField(Field("nalle", 0, *DataType::INT, false));
+ DocumentType::FieldSet::Fields tmp;
+ tmp.insert("nalle");
+ tmp.insert("nulle");
+ try {
+ docType.addFieldSet("a", tmp);
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException & e) {
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("Fieldset 'a': No field with name 'nulle' in document type 'test1'."), e.getMessage());
+ }
+}
+
+void DocumentTypeTest::testInheritance()
+{
+ // Inheritance of conflicting but equal datatype ok
+ DocumentType docType("test1");
+ docType.addField(Field("stringattr", 3, *DataType::STRING, false));
+ docType.addField(Field("nalle", 0, *DataType::INT, false));
+
+ DocumentType docType2("test2");
+ docType2.addField(Field("stringattr", 3, *DataType::STRING, false));
+ docType2.addField(Field("tmp", 5, *DataType::STRING, false));
+ docType2.addField(Field("tall", 10, *DataType::INT, false));
+
+ docType.inherit(docType2);
+ CPPUNIT_ASSERT(docType.hasField("stringattr"));
+ CPPUNIT_ASSERT(docType.hasField("nalle"));
+ CPPUNIT_ASSERT(docType.hasField("tmp"));
+ CPPUNIT_ASSERT(docType.hasField("tall"));
+
+ DocumentType docType3("test3");
+ docType3.addField(Field("stringattr", 3, *DataType::RAW, false));
+ docType3.addField(Field("tall", 10, *DataType::INT, false));
+
+ try{
+ docType2.inherit(docType3);
+ //CPPUNIT_FAIL("Supposed to fail");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), std::string(e.what()));
+ }
+
+ try{
+ docType.inherit(docType3);
+ //CPPUNIT_FAIL("Supposed to fail");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), std::string(e.what()));
+ }
+
+ DocumentType docType4("test4");
+ docType4.inherit(docType3);
+
+ CPPUNIT_ASSERT(docType4.hasField("stringattr"));
+ CPPUNIT_ASSERT(docType4.hasField("tall"));
+
+ try{
+ docType3.inherit(docType4);
+ CPPUNIT_FAIL("Accepted cyclic inheritance");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot add cyclic dependencies", e.what());
+ }
+
+ DocumentType docType5("test5");
+ docType5.addField(Field("stringattr", 20, *DataType::RAW, false));
+
+ try{
+ docType4.inherit(docType5);
+ //CPPUNIT_FAIL("Supposed to fail");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), std::string(e.what()));
+ }
+}
+
+void DocumentTypeTest::testOutputOperator() {
+ DocumentType docType("test1");
+ std::ostringstream ost;
+ ost << docType;
+ std::string expected("DocumentType(test1)");
+ CPPUNIT_ASSERT_EQUAL(expected, ost.str());
+}
+
+} // document
diff --git a/document/src/tests/documentupdatetestcase.cpp b/document/src/tests/documentupdatetestcase.cpp
new file mode 100644
index 00000000000..be619b0f35e
--- /dev/null
+++ b/document/src/tests/documentupdatetestcase.cpp
@@ -0,0 +1,1030 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/update/documentupdate.h>
+#include <vespa/document/base/testdocman.h>
+
+#include <vespa/document/update/addvalueupdate.h>
+#include <vespa/document/update/arithmeticvalueupdate.h>
+#include <vespa/document/update/assignvalueupdate.h>
+#include <vespa/document/update/clearvalueupdate.h>
+#include <vespa/document/update/documentupdateflags.h>
+#include <vespa/document/update/fieldupdate.h>
+#include <vespa/document/update/mapvalueupdate.h>
+#include <vespa/document/update/removevalueupdate.h>
+#include <vespa/document/update/valueupdate.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+
+using namespace document::config_builder;
+
+namespace document {
+
+struct DocumentUpdateTest : public CppUnit::TestFixture {
+ void setUp() {}
+ void tearDown() {}
+
+ void testSimpleUsage();
+ void testUpdateApplySingleValue();
+ void testClearField();
+ void testUpdateArray();
+ void testUpdateWeightedSet();
+ void testIncrementNonExistingAutoCreateWSetField();
+ void testIncrementExistingWSetField();
+ void testIncrementWithZeroResultWeightIsRemoved();
+ void testReadSerializedFile();
+ void testGenerateSerializedFile();
+ void testSetBadFieldTypes();
+ void testUpdateApplyNoParams();
+ void testUpdateApplyNoArrayValues();
+ void testUpdateArrayEmptyParamValue();
+ void testUpdateWeightedSetEmptyParamValue();
+ void testUpdateArrayWrongSubtype();
+ void testUpdateWeightedSetWrongSubtype();
+ void testMapValueUpdate();
+ void testThatDocumentUpdateFlagsIsWorking();
+ void testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50();
+ void testThatCreateIfNonExistentFlagIsSerializedAndDeserialized();
+
+ CPPUNIT_TEST_SUITE(DocumentUpdateTest);
+ CPPUNIT_TEST(testSimpleUsage);
+ CPPUNIT_TEST(testUpdateApplySingleValue);
+ CPPUNIT_TEST(testClearField);
+ CPPUNIT_TEST(testUpdateArray);
+ CPPUNIT_TEST(testUpdateWeightedSet);
+ CPPUNIT_TEST(testIncrementNonExistingAutoCreateWSetField);
+ CPPUNIT_TEST(testIncrementExistingWSetField);
+ CPPUNIT_TEST(testIncrementWithZeroResultWeightIsRemoved);
+ CPPUNIT_TEST(testReadSerializedFile);
+ CPPUNIT_TEST(testGenerateSerializedFile);
+ CPPUNIT_TEST(testSetBadFieldTypes);
+ CPPUNIT_TEST(testUpdateApplyNoParams);
+ CPPUNIT_TEST(testUpdateApplyNoArrayValues);
+ CPPUNIT_TEST(testUpdateArrayEmptyParamValue);
+ CPPUNIT_TEST(testUpdateWeightedSetEmptyParamValue);
+ CPPUNIT_TEST(testUpdateArrayWrongSubtype);
+ CPPUNIT_TEST(testUpdateWeightedSetWrongSubtype);
+ CPPUNIT_TEST(testMapValueUpdate);
+ CPPUNIT_TEST(testThatDocumentUpdateFlagsIsWorking);
+ CPPUNIT_TEST(testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50);
+ CPPUNIT_TEST(testThatCreateIfNonExistentFlagIsSerializedAndDeserialized);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DocumentUpdateTest);
+
+namespace {
+
+ByteBuffer::UP serializeHEAD(const DocumentUpdate & update)
+{
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.writeHEAD(update);
+ ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
+ retVal->putBytes(stream.peek(), stream.size());
+ return retVal;
+}
+
+ByteBuffer::UP serialize42(const DocumentUpdate & update)
+{
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write42(update);
+ ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
+ retVal->putBytes(stream.peek(), stream.size());
+ return retVal;
+}
+
+ByteBuffer::UP serialize(const ValueUpdate & update)
+{
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(update);
+ ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
+ retVal->putBytes(stream.peek(), stream.size());
+ return retVal;
+}
+
+ByteBuffer::UP serialize(const FieldUpdate & update)
+{
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(update);
+ ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
+ retVal->putBytes(stream.peek(), stream.size());
+ return retVal;
+}
+
+template<typename UpdateType>
+void testValueUpdate(const UpdateType& update, const DataType &type) {
+ try{
+ DocumentTypeRepo repo;
+ ByteBuffer::UP buf = serialize(update);
+ buf->flip();
+ typename UpdateType::UP copy(dynamic_cast<UpdateType*>(
+ ValueUpdate::createInstance(repo, type, *buf,
+ Document::getNewestSerializationVersion())
+ .release()));
+ CPPUNIT_ASSERT_EQUAL(update, *copy);
+ } catch (std::exception& e) {
+ std::cerr << "Failed while processing update " << update << "\n";
+ throw;
+ }
+}
+
+
+} // namespace
+
+void
+DocumentUpdateTest::testSimpleUsage() {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(42, "test",
+ Struct("test.header")
+ .addField("bytef", DataType::T_BYTE)
+ .addField("intf", DataType::T_INT),
+ Struct("test.body")
+ .addField("intarr", Array(DataType::T_INT)));
+ DocumentTypeRepo repo(builder.config());
+ const DocumentType* docType(repo.getDocumentType("test"));
+ const DataType *arrayType = repo.getDataType(*docType, "Array<Int>");
+
+ // Test that primitive value updates can be serialized
+ testValueUpdate(ClearValueUpdate(), *DataType::INT);
+ testValueUpdate(AssignValueUpdate(IntFieldValue(1)), *DataType::INT);
+ testValueUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Div, 4.3),
+ *DataType::FLOAT);
+ testValueUpdate(AddValueUpdate(IntFieldValue(1), 4), *arrayType);
+ testValueUpdate(RemoveValueUpdate(IntFieldValue(1)), *arrayType);
+
+ FieldUpdate fieldUpdate(docType->getField("intf"));
+ fieldUpdate.addUpdate(AssignValueUpdate(IntFieldValue(1)));
+ ByteBuffer::UP fieldBuf = serialize(fieldUpdate);
+ fieldBuf->flip();
+ FieldUpdate fieldUpdateCopy(repo, *docType, *fieldBuf,
+ Document::getNewestSerializationVersion());
+ CPPUNIT_ASSERT_EQUAL(fieldUpdate, fieldUpdateCopy);
+
+ // Test that a document update can be serialized
+ DocumentUpdate docUpdate(*docType, DocumentId("doc::testdoc"));
+ docUpdate.addUpdate(fieldUpdateCopy);
+ ByteBuffer::UP docBuf = serialize42(docUpdate);
+ docBuf->flip();
+ DocumentUpdate::UP docUpdateCopy(DocumentUpdate::create42(repo, *docBuf));
+ CPPUNIT_ASSERT(!docUpdate.affectsDocumentBody());
+ CPPUNIT_ASSERT(!docUpdateCopy->affectsDocumentBody());
+
+ // Create a test document
+ Document doc(*docType, DocumentId("doc::testdoc"));
+ doc.set("bytef", 0);
+ doc.set("intf", 5);
+ ArrayFieldValue array(*arrayType);
+ array.add(IntFieldValue(3));
+ array.add(IntFieldValue(7));
+ doc.setValue("intarr", array);
+
+ // Verify that we can apply simple updates to it
+ {
+ Document updated(doc);
+ DocumentUpdate upd(*docType, DocumentId("doc::testdoc"));
+ upd.addUpdate(FieldUpdate(docType->getField("intf"))
+ .addUpdate(ClearValueUpdate()));
+ upd.applyTo(updated);
+ CPPUNIT_ASSERT(doc != updated);
+ CPPUNIT_ASSERT(! updated.getValue("intf"));
+ }
+ {
+ Document updated(doc);
+ DocumentUpdate upd(*docType, DocumentId("doc::testdoc"));
+ upd.addUpdate(FieldUpdate(docType->getField("intf"))
+ .addUpdate(AssignValueUpdate(IntFieldValue(15))));
+ upd.applyTo(updated);
+ CPPUNIT_ASSERT(doc != updated);
+ CPPUNIT_ASSERT_EQUAL(15, updated.getValue("intf")->getAsInt());
+ }
+ {
+ Document updated(doc);
+ DocumentUpdate upd(*docType, DocumentId("doc::testdoc"));
+ upd.addUpdate(FieldUpdate(docType->getField("intf"))
+ .addUpdate(ArithmeticValueUpdate(
+ ArithmeticValueUpdate::Add, 15)));
+ upd.applyTo(updated);
+ CPPUNIT_ASSERT(doc != updated);
+ CPPUNIT_ASSERT_EQUAL(20, updated.getValue("intf")->getAsInt());
+ }
+ {
+ Document updated(doc);
+ DocumentUpdate upd(*docType, DocumentId("doc::testdoc"));
+ upd.addUpdate(FieldUpdate(docType->getField("intarr"))
+ .addUpdate(AddValueUpdate(IntFieldValue(4))));
+ upd.applyTo(updated);
+ CPPUNIT_ASSERT(doc != updated);
+ std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>(
+ updated.getValue("intarr").release()));
+ CPPUNIT_ASSERT_EQUAL(size_t(3), val->size());
+ CPPUNIT_ASSERT_EQUAL(4, (*val)[2].getAsInt());
+ CPPUNIT_ASSERT(upd.affectsDocumentBody());
+ }
+ {
+ Document updated(doc);
+ DocumentUpdate upd(*docType, DocumentId("doc::testdoc"));
+ upd.addUpdate(FieldUpdate(docType->getField("intarr"))
+ .addUpdate(RemoveValueUpdate(IntFieldValue(3))));
+ upd.applyTo(updated);
+ CPPUNIT_ASSERT(doc != updated);
+ std::unique_ptr<ArrayFieldValue> val(dynamic_cast<ArrayFieldValue*>(
+ updated.getValue("intarr").release()));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), val->size());
+ CPPUNIT_ASSERT_EQUAL(7, (*val)[0].getAsInt());
+ }
+ {
+ Document updated(doc);
+ DocumentUpdate upd(*docType, DocumentId("doc::testdoc"));
+ upd.addUpdate(FieldUpdate(docType->getField("bytef"))
+ .addUpdate(ArithmeticValueUpdate(
+ ArithmeticValueUpdate::Add, 15)));
+ upd.applyTo(updated);
+ CPPUNIT_ASSERT(doc != updated);
+ CPPUNIT_ASSERT_EQUAL(15, (int) updated.getValue("bytef")->getAsByte());
+ }
+}
+
+void
+DocumentUpdateTest::testClearField()
+{
+ // Create a document.
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ doc->setValue(doc->getField("headerval"), IntFieldValue(4));
+ CPPUNIT_ASSERT_EQUAL(4, doc->getValue("headerval")->getAsInt());
+
+ // Apply an update.
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("headerval"))
+ .addUpdate(AssignValueUpdate()))
+ .applyTo(*doc);
+ CPPUNIT_ASSERT(!doc->getValue("headerval"));
+}
+
+void
+DocumentUpdateTest::testUpdateApplySingleValue()
+{
+ // Create a document.
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ doc->setValue(doc->getField("headerval"), IntFieldValue(4));
+ CPPUNIT_ASSERT_EQUAL(4, doc->getValue("headerval")->getAsInt());
+
+ // Apply an update.
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("headerval"))
+ .addUpdate(AssignValueUpdate(IntFieldValue(9))))
+ .applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(9, doc->getValue("headerval")->getAsInt());
+}
+
+void
+DocumentUpdateTest::testUpdateArray()
+{
+ // Create a document.
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL,
+ doc->getValue(doc->getField("tags")).get());
+
+ // Assign array field.
+ ArrayFieldValue myarray(doc->getType().getField("tags").getDataType());
+ myarray.add(StringFieldValue("foo"));
+ myarray.add(StringFieldValue("bar"));
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("tags"))
+ .addUpdate(AssignValueUpdate(myarray)))
+ .applyTo(*doc);
+ std::unique_ptr<ArrayFieldValue>
+ fval1(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, fval1->size());
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"),
+ std::string((*fval1)[0].getAsString()));
+ CPPUNIT_ASSERT_EQUAL(std::string("bar"),
+ std::string((*fval1)[1].getAsString()));
+
+ // Append array field
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("tags"))
+ .addUpdate(AddValueUpdate(StringFieldValue("another")))
+ .addUpdate(AddValueUpdate(StringFieldValue("tag"))))
+ .applyTo(*doc);
+ std::unique_ptr<ArrayFieldValue>
+ fval2(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, fval2->size());
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"),
+ std::string((*fval2)[0].getAsString()));
+ CPPUNIT_ASSERT_EQUAL(std::string("bar"),
+ std::string((*fval2)[1].getAsString()));
+ CPPUNIT_ASSERT_EQUAL(std::string("another"),
+ std::string((*fval2)[2].getAsString()));
+ CPPUNIT_ASSERT_EQUAL(std::string("tag"),
+ std::string((*fval2)[3].getAsString()));
+
+ // Append single value.
+ try {
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("tags"))
+ .addUpdate(AssignValueUpdate(
+ StringFieldValue("THROW MEH!"))))
+ .applyTo(*doc);
+ CPPUNIT_FAIL("Expected exception when assinging a string value to an "
+ "array field.");
+ }
+ catch (std::exception& e) {
+ ; // fprintf(stderr, "Got exception => OK: %s\n", e.what());
+ }
+
+ // Remove array field.
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("tags"))
+ .addUpdate(RemoveValueUpdate(StringFieldValue("foo")))
+ .addUpdate(RemoveValueUpdate(StringFieldValue("tag"))))
+ .applyTo(*doc);
+ std::unique_ptr<ArrayFieldValue>
+ fval3(doc->getAs<ArrayFieldValue>(doc->getField("tags")));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, fval3->size());
+ CPPUNIT_ASSERT_EQUAL(std::string("bar"),
+ std::string((*fval3)[0].getAsString()));
+ CPPUNIT_ASSERT_EQUAL(std::string("another"),
+ std::string((*fval3)[1].getAsString()));
+
+ // Remove array from array.
+ ArrayFieldValue myarray2(doc->getType().getField("tags").getDataType());
+ myarray2.add(StringFieldValue("foo"));
+ myarray2.add(StringFieldValue("bar"));
+ try {
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(doc->getField("tags"))
+ .addUpdate(RemoveValueUpdate(myarray2)))
+ .applyTo(*doc);
+ CPPUNIT_FAIL("Expected exception when removing an array from a "
+ "string array.");
+ }
+ catch (std::exception& e) {
+ ; // fprintf(stderr, "Got exception => OK: %s\n", e.what());
+ }
+}
+
+void
+DocumentUpdateTest::testUpdateWeightedSet()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field& field(doc->getType().getField("stringweightedset"));
+ CPPUNIT_ASSERT_EQUAL((FieldValue*) 0, doc->getValue(field).get());
+
+ // Assign weightedset field
+ WeightedSetFieldValue wset(field.getDataType());
+ wset.add(StringFieldValue("foo"), 3);
+ wset.add(StringFieldValue("bar"), 14);
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field)
+ .addUpdate(AssignValueUpdate(wset)))
+ .applyTo(*doc);
+ std::unique_ptr<WeightedSetFieldValue>
+ fval1(doc->getAs<WeightedSetFieldValue>(field));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, fval1->size());
+ CPPUNIT_ASSERT(fval1->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT(fval1->find(StringFieldValue("foo")) != fval1->end());
+ CPPUNIT_ASSERT_EQUAL(3, fval1->get(StringFieldValue("foo"), 0));
+ CPPUNIT_ASSERT(fval1->contains(StringFieldValue("bar")));
+ CPPUNIT_ASSERT(fval1->find(StringFieldValue("bar")) != fval1->end());
+ CPPUNIT_ASSERT_EQUAL(14, fval1->get(StringFieldValue("bar"), 0));
+
+ // Do a second assign
+ WeightedSetFieldValue wset2(field.getDataType());
+ wset2.add(StringFieldValue("foo"), 16);
+ wset2.add(StringFieldValue("bar"), 24);
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field)
+ .addUpdate(AssignValueUpdate(wset2)))
+ .applyTo(*doc);
+ std::unique_ptr<WeightedSetFieldValue>
+ fval2(doc->getAs<WeightedSetFieldValue>(field));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, fval2->size());
+ CPPUNIT_ASSERT(fval2->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT(fval2->find(StringFieldValue("foo")) != fval1->end());
+ CPPUNIT_ASSERT_EQUAL(16, fval2->get(StringFieldValue("foo"), 0));
+ CPPUNIT_ASSERT(fval2->contains(StringFieldValue("bar")));
+ CPPUNIT_ASSERT(fval2->find(StringFieldValue("bar")) != fval1->end());
+ CPPUNIT_ASSERT_EQUAL(24, fval2->get(StringFieldValue("bar"), 0));
+
+ // Append weighted field
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field)
+ .addUpdate(AddValueUpdate(StringFieldValue("foo"))
+ .setWeight(3))
+ .addUpdate(AddValueUpdate(StringFieldValue("too"))
+ .setWeight(14)))
+ .applyTo(*doc);
+ std::unique_ptr<WeightedSetFieldValue>
+ fval3(doc->getAs<WeightedSetFieldValue>(field));
+ CPPUNIT_ASSERT_EQUAL((size_t) 3, fval3->size());
+ CPPUNIT_ASSERT(fval3->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT_EQUAL(3, fval3->get(StringFieldValue("foo"), 0));
+ CPPUNIT_ASSERT(fval3->contains(StringFieldValue("bar")));
+ CPPUNIT_ASSERT_EQUAL(24, fval3->get(StringFieldValue("bar"), 0));
+ CPPUNIT_ASSERT(fval3->contains(StringFieldValue("too")));
+ CPPUNIT_ASSERT_EQUAL(14, fval3->get(StringFieldValue("too"), 0));
+
+ // Remove weighted field
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field)
+ .addUpdate(RemoveValueUpdate(StringFieldValue("foo")))
+ .addUpdate(RemoveValueUpdate(StringFieldValue("too"))))
+ .applyTo(*doc);
+ std::unique_ptr<WeightedSetFieldValue>
+ fval4(doc->getAs<WeightedSetFieldValue>(field));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, fval4->size());
+ CPPUNIT_ASSERT(!fval4->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT(fval4->contains(StringFieldValue("bar")));
+ CPPUNIT_ASSERT_EQUAL(24, fval4->get(StringFieldValue("bar"), 0));
+ CPPUNIT_ASSERT(!fval4->contains(StringFieldValue("too")));
+}
+
+namespace {
+
+struct WeightedSetAutoCreateFixture
+{
+ DocumentTypeRepo repo;
+ const DocumentType* docType;
+ Document doc;
+ const Field& field;
+ DocumentUpdate update;
+
+ WeightedSetAutoCreateFixture()
+ : repo(makeConfig()),
+ docType(repo.getDocumentType("test")),
+ doc(*docType, DocumentId("doc::testdoc")),
+ field(docType->getField("strwset")),
+ update(*docType, DocumentId("doc::testdoc"))
+ {
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(MapValueUpdate(StringFieldValue("foo"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1))));
+ }
+
+ void applyUpdateToDocument() {
+ update.applyTo(doc);
+ }
+
+ static DocumenttypesConfig makeConfig() {
+ DocumenttypesConfigBuilderHelper builder;
+ // T_TAG is an alias for a weighted set with create-if-non-existing
+ // and remove-if-zero attributes set. Attempting to explicitly create
+ // a field matching those characteristics will in fact fail with a
+ // redefinition error.
+ builder.document(42, "test",
+ Struct("test.header")
+ .addField("strwset", DataType::T_TAG),
+ Struct("test.body"));
+ return builder.config();
+ }
+};
+
+} // anon ns
+
+void
+DocumentUpdateTest::testIncrementNonExistingAutoCreateWSetField()
+{
+ WeightedSetAutoCreateFixture fixture;
+
+ fixture.applyUpdateToDocument();
+
+ std::unique_ptr<WeightedSetFieldValue> ws(
+ fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), ws->size());
+ CPPUNIT_ASSERT(ws->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT_EQUAL(1, ws->get(StringFieldValue("foo"), 0));
+}
+
+void
+DocumentUpdateTest::testIncrementExistingWSetField()
+{
+ WeightedSetAutoCreateFixture fixture;
+ {
+ WeightedSetFieldValue wset(fixture.field.getDataType());
+ wset.add(StringFieldValue("bar"), 14);
+ fixture.doc.setValue(fixture.field, wset);
+ }
+ fixture.applyUpdateToDocument();
+
+ std::unique_ptr<WeightedSetFieldValue> ws(
+ fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
+ CPPUNIT_ASSERT_EQUAL(size_t(2), ws->size());
+ CPPUNIT_ASSERT(ws->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT_EQUAL(1, ws->get(StringFieldValue("foo"), 0));
+}
+
+void
+DocumentUpdateTest::testIncrementWithZeroResultWeightIsRemoved()
+{
+ WeightedSetAutoCreateFixture fixture;
+ fixture.update.addUpdate(FieldUpdate(fixture.field)
+ .addUpdate(MapValueUpdate(StringFieldValue("baz"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 0))));
+
+ fixture.applyUpdateToDocument();
+
+ std::unique_ptr<WeightedSetFieldValue> ws(
+ fixture.doc.getAs<WeightedSetFieldValue>(fixture.field));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), ws->size());
+ CPPUNIT_ASSERT(ws->contains(StringFieldValue("foo")));
+ CPPUNIT_ASSERT(!ws->contains(StringFieldValue("baz")));
+}
+
+void DocumentUpdateTest::testReadSerializedFile()
+{
+ // Reads a file serialized from java
+ const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(file_name));
+
+ int fd = open("data/serializeupdatejava.dat", O_RDONLY);
+
+ int len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+
+ close(fd);
+
+ DocumentUpdate::UP updp(DocumentUpdate::create42(repo, buf));
+ DocumentUpdate& upd(*updp);
+
+ const DocumentType *type = repo.getDocumentType("serializetest");
+ CPPUNIT_ASSERT_EQUAL(DocumentId(DocIdString("update", "test")),
+ upd.getId());
+ CPPUNIT_ASSERT_EQUAL(*type, upd.getType());
+
+ // Verify assign value update.
+ FieldUpdate serField = upd[0];
+ CPPUNIT_ASSERT_EQUAL(serField.getField().getId(Document::getNewestSerializationVersion()),
+ type->getField("intfield").getId(Document::getNewestSerializationVersion()));
+
+ const ValueUpdate* serValue = &serField[0];
+ CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Assign);
+
+ const AssignValueUpdate* assign(
+ static_cast<const AssignValueUpdate*>(serValue));
+ CPPUNIT_ASSERT_EQUAL(IntFieldValue(4),
+ static_cast<const IntFieldValue&>(assign->getValue()));
+
+ // Verify clear field update.
+ serField = upd[1];
+ CPPUNIT_ASSERT_EQUAL(serField.getField().getId(Document::getNewestSerializationVersion()),
+ type->getField("floatfield").getId(Document::getNewestSerializationVersion()));
+
+ serValue = &serField[0];
+ CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Clear);
+ CPPUNIT_ASSERT(serValue->inherits(ClearValueUpdate::classId));
+
+ // Verify add value update.
+ serField = upd[2];
+ CPPUNIT_ASSERT_EQUAL(serField.getField().getId(Document::getNewestSerializationVersion()),
+ type->getField("arrayoffloatfield").getId(Document::getNewestSerializationVersion()));
+
+ serValue = &serField[0];
+ CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Add);
+
+ const AddValueUpdate* add = static_cast<const AddValueUpdate*>(serValue);
+ const FieldValue* value = &add->getValue();
+ CPPUNIT_ASSERT(value->inherits(FloatFieldValue::classId));
+ CPPUNIT_ASSERT_EQUAL(value->getAsFloat(), 5.00f);
+
+ serValue = &serField[1];
+ CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Add);
+
+ add = static_cast<const AddValueUpdate*>(serValue);
+ value = &add->getValue();
+ CPPUNIT_ASSERT(value->inherits(FloatFieldValue::classId));
+ CPPUNIT_ASSERT_EQUAL(value->getAsFloat(), 4.23f);
+
+ serValue = &serField[2];
+ CPPUNIT_ASSERT_EQUAL(serValue->getType(), ValueUpdate::Add);
+
+ add = static_cast<const AddValueUpdate*>(serValue);
+ value = &add->getValue();
+ CPPUNIT_ASSERT(value->inherits(FloatFieldValue::classId));
+ CPPUNIT_ASSERT_EQUAL(value->getAsFloat(), -1.00f);
+
+}
+
+void DocumentUpdateTest::testGenerateSerializedFile()
+{
+ // Tests nothing, only generates a file for java test
+ const char file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(file_name));
+
+ const DocumentType *type(repo.getDocumentType("serializetest"));
+ DocumentUpdate upd(*type, DocumentId(DocIdString("update", "test")));
+ upd.addUpdate(FieldUpdate(type->getField("intfield"))
+ .addUpdate(AssignValueUpdate(IntFieldValue(4))));
+ upd.addUpdate(FieldUpdate(type->getField("floatfield"))
+ .addUpdate(AssignValueUpdate(FloatFieldValue(1.00f))));
+ upd.addUpdate(FieldUpdate(type->getField("arrayoffloatfield"))
+ .addUpdate(AddValueUpdate(FloatFieldValue(5.00f)))
+ .addUpdate(AddValueUpdate(FloatFieldValue(4.23f)))
+ .addUpdate(AddValueUpdate(FloatFieldValue(-1.00f))));
+ upd.addUpdate(FieldUpdate(type->getField("intfield"))
+ .addUpdate(ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 3)));
+ upd.addUpdate(FieldUpdate(type->getField("wsfield"))
+ .addUpdate(MapValueUpdate(StringFieldValue("foo"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 2)))
+ .addUpdate(MapValueUpdate(StringFieldValue("foo"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Mul, 2))));
+ ByteBuffer::UP buf(serialize42(upd));
+
+ int fd = open("data/serializeupdatecpp.dat",
+ O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ write(fd, buf->getBuffer(), buf->getPos());
+ close(fd);
+}
+
+
+void DocumentUpdateTest::testSetBadFieldTypes()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL,
+ doc->getValue(doc->getField("headerval")).get());
+
+ // Assign a float value to an int field.
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ try {
+ update.addUpdate(FieldUpdate(doc->getField("headerval"))
+ .addUpdate(AssignValueUpdate(FloatFieldValue(4.00f))));
+ CPPUNIT_FAIL("Expected exception when adding a float to an int field.");
+ } catch (std::exception& e) {
+ ; // fprintf(stderr, "Got exception => OK: %s\n", e.what());
+ }
+
+ // Apply update
+ update.applyTo(*doc);
+
+ // Verify that the field is NOT set in the document.
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL,
+ doc->getValue(doc->getField("headerval")).get());
+}
+
+void
+DocumentUpdateTest::testUpdateApplyNoParams()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*)NULL,
+ doc->getValue(doc->getField("tags")).get());
+
+ // Assign array field with no parameters - illegal.
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ try {
+ update.addUpdate(FieldUpdate(doc->getField("tags"))
+ .addUpdate(AssignValueUpdate()));
+ CPPUNIT_FAIL("Expected exception when assign a NULL value.");
+ } catch (std::exception& e) {
+ ; // fprintf(stderr, "Got exception => OK: %s\n", e.what());
+ }
+
+ // Apply update
+ update.applyTo(*doc);
+
+ // Verify that the field was cleared in the document.
+ CPPUNIT_ASSERT(!doc->hasValue(doc->getField("tags")));
+}
+
+void
+DocumentUpdateTest::testUpdateApplyNoArrayValues()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field &field(doc->getType().getField("tags"));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0,
+ doc->getValue(field).get());
+
+ // Assign array field with no array values = empty array
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(AssignValueUpdate(
+ ArrayFieldValue(field.getDataType()))));
+
+ // Apply update
+ update.applyTo(*doc);
+
+ // Verify that the field was set in the document
+ std::unique_ptr<ArrayFieldValue> fval(doc->getAs<ArrayFieldValue>(field));
+ CPPUNIT_ASSERT(fval.get());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0, fval->size());
+}
+
+void
+DocumentUpdateTest::testUpdateArrayEmptyParamValue()
+{
+ // Create a test document.
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field &field(doc->getType().getField("tags"));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0,
+ doc->getValue(field).get());
+
+ // Assign array field with no array values = empty array.
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(AssignValueUpdate(
+ ArrayFieldValue(field.getDataType()))));
+ update.applyTo(*doc);
+
+ // Verify that the field was set in the document.
+ std::unique_ptr<ArrayFieldValue> fval1(doc->getAs<ArrayFieldValue>(field));
+ CPPUNIT_ASSERT(fval1.get());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0, fval1->size());
+
+ // Remove array field.
+ update.clear();
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(ClearValueUpdate()));
+ update.applyTo(*doc);
+
+ // Verify that the field was cleared in the document.
+ std::unique_ptr<ArrayFieldValue> fval2(doc->getAs<ArrayFieldValue>(field));
+ CPPUNIT_ASSERT(!fval2);
+}
+
+void
+DocumentUpdateTest::testUpdateWeightedSetEmptyParamValue()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field &field(doc->getType().getField("stringweightedset"));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0,
+ doc->getValue(field).get());
+
+ // Assign weighted set with no items = empty set.
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(AssignValueUpdate(
+ WeightedSetFieldValue(field.getDataType()))));
+ update.applyTo(*doc);
+
+ // Verify that the field was set in the document.
+ std::unique_ptr<WeightedSetFieldValue>
+ fval1(doc->getAs<WeightedSetFieldValue>(field));
+ CPPUNIT_ASSERT(fval1.get());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0, fval1->size());
+
+ // Remove weighted set field.
+ update.clear();
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(ClearValueUpdate()));
+ update.applyTo(*doc);
+
+ // Verify that the field was cleared in the document.
+ std::unique_ptr<WeightedSetFieldValue>
+ fval2(doc->getAs<WeightedSetFieldValue>(field));
+ CPPUNIT_ASSERT(!fval2);
+}
+
+void
+DocumentUpdateTest::testUpdateArrayWrongSubtype()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field &field(doc->getType().getField("tags"));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0,
+ doc->getValue(field).get());
+
+ // Assign int values to string array.
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ try {
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(AddValueUpdate(IntFieldValue(123)))
+ .addUpdate(AddValueUpdate(IntFieldValue(456))));
+ CPPUNIT_FAIL("Expected exception when adding wrong type.");
+ } catch (std::exception& e) {
+ ; // fprintf(stderr, "Got exception => OK: %s\n", e.what());
+ }
+
+ // Apply update
+ update.applyTo(*doc);
+
+ // Verify that the field was NOT set in the document
+ FieldValue::UP fval(doc->getValue(field));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, fval.get());
+}
+
+void
+DocumentUpdateTest::testUpdateWeightedSetWrongSubtype()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field &field(doc->getType().getField("stringweightedset"));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0,
+ doc->getValue(field).get());
+
+ // Assign int values to string array.
+ DocumentUpdate update(*doc->getDataType(), doc->getId());
+ try {
+ update.addUpdate(FieldUpdate(field)
+ .addUpdate(AddValueUpdate(IntFieldValue(123))
+ .setWeight(1000))
+ .addUpdate(AddValueUpdate(IntFieldValue(456))
+ .setWeight(2000)));
+ CPPUNIT_FAIL("Expected exception when adding wrong type.");
+ } catch (std::exception& e) {
+ ; // fprintf(stderr, "Got exception => OK: %s\n", e.what());
+ }
+
+ // Apply update
+ update.applyTo(*doc);
+
+ // Verify that the field was NOT set in the document
+ FieldValue::UP fval(doc->getValue(field));
+ CPPUNIT_ASSERT_EQUAL((document::FieldValue*) 0, fval.get());
+}
+
+void
+DocumentUpdateTest::testMapValueUpdate()
+{
+ // Create a test document
+ TestDocMan docMan;
+ Document::UP doc(docMan.createDocument());
+ const Field &field1 = doc->getField("stringweightedset");
+ const Field &field2 = doc->getField("stringweightedset2");
+ WeightedSetFieldValue wsval1(field1.getDataType());
+ WeightedSetFieldValue wsval2(field2.getDataType());
+ doc->setValue(field1, wsval1);
+ doc->setValue(field2, wsval2);
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field1)
+ .addUpdate(MapValueUpdate(
+ StringFieldValue("banana"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1.0)
+ )))
+ .applyTo(*doc);
+ std::unique_ptr<WeightedSetFieldValue> fv1 =
+ doc->getAs<WeightedSetFieldValue>(field1);
+ CPPUNIT_ASSERT(fv1->size() == 0);
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field2)
+ .addUpdate(MapValueUpdate(
+ StringFieldValue("banana"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Add, 1.0)
+ )))
+ .applyTo(*doc);
+ std::unique_ptr<WeightedSetFieldValue> fv2 =
+ doc->getAs<WeightedSetFieldValue>(field2);
+ CPPUNIT_ASSERT(fv2->size() == 1);
+
+ CPPUNIT_ASSERT(fv1->find(StringFieldValue("apple")) == fv1->end());
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field1)
+ .addUpdate(ClearValueUpdate()))
+ .applyTo(*doc);
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field1)
+ .addUpdate(AddValueUpdate(StringFieldValue("apple"))
+ .setWeight(1)))
+ .applyTo(*doc);
+
+ std::unique_ptr<WeightedSetFieldValue>
+ fval3(doc->getAs<WeightedSetFieldValue>(field1));
+ CPPUNIT_ASSERT(fval3->find(StringFieldValue("apple")) != fval3->end());
+ CPPUNIT_ASSERT_EQUAL(1, fval3->get(StringFieldValue("apple")));
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field2)
+ .addUpdate(AddValueUpdate(StringFieldValue("apple"))
+ .setWeight(1)))
+ .applyTo(*doc);
+
+ std::unique_ptr<WeightedSetFieldValue>
+ fval3b(doc->getAs<WeightedSetFieldValue>(field2));
+ CPPUNIT_ASSERT(fval3b->find(StringFieldValue("apple")) != fval3b->end());
+ CPPUNIT_ASSERT_EQUAL(1, fval3b->get(StringFieldValue("apple")));
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field1)
+ .addUpdate(MapValueUpdate(
+ StringFieldValue("apple"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Sub, 1.0)
+ )))
+ .applyTo(*doc);
+
+ std::unique_ptr<WeightedSetFieldValue> fv3 =
+ doc->getAs<WeightedSetFieldValue>(field1);
+ CPPUNIT_ASSERT(fv3->find(StringFieldValue("apple")) != fv3->end());
+ CPPUNIT_ASSERT_EQUAL(0, fv3->get(StringFieldValue("apple")));
+
+ DocumentUpdate(*doc->getDataType(), doc->getId())
+ .addUpdate(FieldUpdate(field2)
+ .addUpdate(MapValueUpdate(
+ StringFieldValue("apple"),
+ ArithmeticValueUpdate(ArithmeticValueUpdate::Sub, 1.0)
+ )))
+ .applyTo(*doc);
+
+ std::unique_ptr<WeightedSetFieldValue> fv4 =
+ doc->getAs<WeightedSetFieldValue>(field2);
+ CPPUNIT_ASSERT(fv4->find(StringFieldValue("apple")) == fv4->end());
+}
+
+
+void
+assertDocumentUpdateFlag(bool createIfNonExistent, int value)
+{
+ DocumentUpdateFlags f1;
+ f1.setCreateIfNonExistent(createIfNonExistent);
+ CPPUNIT_ASSERT_EQUAL(createIfNonExistent, f1.getCreateIfNonExistent());
+ int combined = f1.injectInto(value);
+ std::cout << "createIfNonExistent=" << createIfNonExistent << ", value=" << value << ", combined=" << combined << std::endl;
+
+ DocumentUpdateFlags f2 = DocumentUpdateFlags::extractFlags(combined);
+ int extractedValue = DocumentUpdateFlags::extractValue(combined);
+ CPPUNIT_ASSERT_EQUAL(createIfNonExistent, f2.getCreateIfNonExistent());
+ CPPUNIT_ASSERT_EQUAL(value, extractedValue);
+}
+
+void
+DocumentUpdateTest::testThatDocumentUpdateFlagsIsWorking()
+{
+ { // create-if-non-existent = true
+ assertDocumentUpdateFlag(true, 0);
+ assertDocumentUpdateFlag(true, 1);
+ assertDocumentUpdateFlag(true, 2);
+ assertDocumentUpdateFlag(true, 9999);
+ assertDocumentUpdateFlag(true, 0xFFFFFFE);
+ assertDocumentUpdateFlag(true, 0xFFFFFFF);
+ }
+ { // create-if-non-existent = false
+ assertDocumentUpdateFlag(false, 0);
+ assertDocumentUpdateFlag(false, 1);
+ assertDocumentUpdateFlag(false, 2);
+ assertDocumentUpdateFlag(false, 9999);
+ assertDocumentUpdateFlag(false, 0xFFFFFFE);
+ assertDocumentUpdateFlag(false, 0xFFFFFFF);
+ }
+}
+
+struct CreateIfNonExistentFixture
+{
+ TestDocMan docMan;
+ Document::UP document;
+ DocumentUpdate::UP update;
+ CreateIfNonExistentFixture()
+ : docMan(),
+ document(docMan.createDocument()),
+ update(new DocumentUpdate(*document->getDataType(),
+ document->getId()))
+ {
+ update->addUpdate(FieldUpdate(document->getField("headerval"))
+ .addUpdate(AssignValueUpdate(IntFieldValue(1))));
+ update->setCreateIfNonExistent(true);
+ }
+};
+
+void
+DocumentUpdateTest::testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50()
+{
+ CreateIfNonExistentFixture f;
+
+ ByteBuffer::UP buf(serializeHEAD(*f.update));
+ buf->flip();
+
+ DocumentUpdate::UP deserialized = DocumentUpdate::createHEAD(f.docMan.getTypeRepo(), *buf);
+ CPPUNIT_ASSERT_EQUAL(*f.update, *deserialized);
+ CPPUNIT_ASSERT(deserialized->getCreateIfNonExistent());
+}
+
+void
+DocumentUpdateTest::testThatCreateIfNonExistentFlagIsSerializedAndDeserialized()
+{
+ CreateIfNonExistentFixture f;
+
+ ByteBuffer::UP buf(serialize42(*f.update));
+ buf->flip();
+
+ DocumentUpdate::UP deserialized = DocumentUpdate::create42(f.docMan.getTypeRepo(), *buf);
+ CPPUNIT_ASSERT_EQUAL(*f.update, *deserialized);
+ CPPUNIT_ASSERT(deserialized->getCreateIfNonExistent());
+}
+
+} // namespace document
diff --git a/document/src/tests/documentupdatetestcase.h b/document/src/tests/documentupdatetestcase.h
new file mode 100644
index 00000000000..b8d8294ec0d
--- /dev/null
+++ b/document/src/tests/documentupdatetestcase.h
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class DocumentUpdate_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(DocumentUpdate_Test);
+ CPPUNIT_TEST(testUpdateApplySingleValue);
+ CPPUNIT_TEST(testUpdateArray);
+ CPPUNIT_TEST(testUpdateWeightedSet);
+ CPPUNIT_TEST(testReadSerializedFile);
+ CPPUNIT_TEST(testGenerateSerializedFile);
+ CPPUNIT_TEST(testSetBadFieldTypes);
+ CPPUNIT_TEST(testUpdateApplyNoParams);
+ CPPUNIT_TEST(testUpdateApplyNoArrayValues);
+ CPPUNIT_TEST(testUpdateArrayEmptyParamValue);
+ CPPUNIT_TEST(testUpdateWeightedSetEmptyParamValue);
+ CPPUNIT_TEST(testUpdateArrayWrongSubtype);
+ CPPUNIT_TEST(testUpdateWeightedSetWrongSubtype);
+ CPPUNIT_TEST(testMapValueUpdate);
+ CPPUNIT_TEST(testThatDocumentUpdateFlagsIsWorking);
+ CPPUNIT_TEST(testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50);
+ CPPUNIT_TEST(testThatCreateIfNonExistentFlagIsSerializedAndDeserialized);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp();
+ void tearDown();
+
+protected:
+ void testUpdateApplySingleValue();
+ void testUpdateArray();
+ void testUpdateWeightedSet();
+ void testReadSerializedFile();
+ void testGenerateSerializedFile();
+ void testSetBadFieldTypes();
+ void testUpdateApplyNoParams();
+ void testUpdateApplyNoArrayValues();
+ void testUpdateArrayEmptyParamValue();
+ void testUpdateWeightedSetEmptyParamValue();
+ void testUpdateArrayWrongSubtype();
+ void testUpdateWeightedSetWrongSubtype();
+ void testMapValueUpdate();
+ void testThatDocumentUpdateFlagsIsWorking();
+ void testThatCreateIfNonExistentFlagIsSerialized50AndDeserialized50();
+ void testThatCreateIfNonExistentFlagIsSerializedAndDeserialized();
+};
+
diff --git a/document/src/tests/fieldpathupdatetestcase.cpp b/document/src/tests/fieldpathupdatetestcase.cpp
new file mode 100644
index 00000000000..ec999d0dfe9
--- /dev/null
+++ b/document/src/tests/fieldpathupdatetestcase.cpp
@@ -0,0 +1,1310 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/base/testdocman.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/select/node.h>
+#include <vespa/vespalib/io/fileutil.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/document/update/fieldpathupdates.h>
+#include <vespa/document/update/documentupdate.h>
+
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/vespalib/objects/identifiable.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <fstream>
+#include <sstream>
+
+using vespalib::Identifiable;
+using namespace document::config_builder;
+
+namespace document {
+
+struct FieldPathUpdateTestCase : public CppUnit::TestFixture {
+ DocumentTypeRepo::SP _repo;
+ DocumentType _foobar_type;
+
+ void setUp();
+ void tearDown();
+
+ void testWhereClause();
+ void testNoIterateMapValues();
+ void testRemoveField();
+ void testApplyRemoveEntireListField();
+ void testApplyRemoveMultiList();
+ void testApplyRemoveMultiWset();
+ void testApplyAssignSingle();
+ void testApplyAssignMath();
+ void testApplyAssignMathDivZero();
+ void testApplyAssignMathByteToZero();
+ void testApplyAssignMathNotModifiedOnUnderflow();
+ void testApplyAssignMathNotModifiedOnOverflow();
+ void testApplyAssignFieldNotExistingInExpression();
+ void testApplyAssignFieldNotExistingInPath();
+ void testApplyAssignTargetNotExisting();
+ void testAssignSimpleMapValueWithVariable();
+ void testApplyAssignMathRemoveIfZero();
+ void testApplyAssignMultiList();
+ void testApplyAssignMultiWset();
+ void testAssignWsetRemoveIfZero();
+ void testApplyAddMultiList();
+ void testAddAndAssignList();
+ void testAssignMap();
+ void testAssignMapStruct();
+ void testAssignMapStructVariable();
+ void testAssignMapNoExist();
+ void testAssignMapNoExistNoCreate();
+ void testQuotedStringKey();
+ void testEqualityComparison();
+ void testAffectsDocumentBody();
+ void testIncompatibleDataTypeFails();
+ void testSerializeAssign();
+ void testSerializeAdd();
+ void testSerializeRemove();
+ void testSerializeAssignMath();
+ void testReadSerializedFile();
+ void testGenerateSerializedFile();
+
+ CPPUNIT_TEST_SUITE(FieldPathUpdateTestCase);
+ CPPUNIT_TEST(testWhereClause);
+ CPPUNIT_TEST(testNoIterateMapValues);
+ CPPUNIT_TEST(testRemoveField);
+ CPPUNIT_TEST(testApplyRemoveEntireListField);
+ CPPUNIT_TEST(testApplyRemoveMultiList);
+ CPPUNIT_TEST(testApplyRemoveMultiWset);
+ CPPUNIT_TEST(testApplyAssignSingle);
+ CPPUNIT_TEST(testApplyAssignMath);
+ CPPUNIT_TEST(testApplyAssignMathDivZero);
+ CPPUNIT_TEST(testApplyAssignMathByteToZero);
+ CPPUNIT_TEST(testApplyAssignMathNotModifiedOnUnderflow);
+ CPPUNIT_TEST(testApplyAssignMathNotModifiedOnOverflow);
+ CPPUNIT_TEST(testApplyAssignFieldNotExistingInExpression);
+ CPPUNIT_TEST(testApplyAssignFieldNotExistingInPath);
+ CPPUNIT_TEST(testApplyAssignTargetNotExisting);
+ CPPUNIT_TEST(testAssignSimpleMapValueWithVariable);
+ CPPUNIT_TEST(testApplyAssignMathRemoveIfZero);
+ CPPUNIT_TEST(testApplyAssignMultiList);
+ CPPUNIT_TEST(testApplyAssignMultiWset);
+ CPPUNIT_TEST(testAssignWsetRemoveIfZero);
+ CPPUNIT_TEST(testApplyAddMultiList);
+ CPPUNIT_TEST(testAddAndAssignList);
+ CPPUNIT_TEST(testAssignMap);
+ CPPUNIT_TEST(testAssignMapStruct);
+ CPPUNIT_TEST(testAssignMapStructVariable);
+ CPPUNIT_TEST(testAssignMapNoExist);
+ CPPUNIT_TEST(testAssignMapNoExistNoCreate);
+ CPPUNIT_TEST(testQuotedStringKey);
+ CPPUNIT_TEST(testEqualityComparison);
+ CPPUNIT_TEST(testAffectsDocumentBody);
+ CPPUNIT_TEST(testIncompatibleDataTypeFails);
+ CPPUNIT_TEST(testSerializeAssign);
+ CPPUNIT_TEST(testSerializeAdd);
+ CPPUNIT_TEST(testSerializeRemove);
+ CPPUNIT_TEST(testSerializeAssignMath);
+ CPPUNIT_TEST(testReadSerializedFile);
+ CPPUNIT_TEST(testGenerateSerializedFile);
+ CPPUNIT_TEST_SUITE_END();
+private:
+ DocumentUpdate::UP
+ createDocumentUpdateForSerialization(const DocumentTypeRepo& repo);
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(FieldPathUpdateTestCase);
+
+namespace {
+
+document::DocumenttypesConfig getRepoConfig() {
+ const int struct2_id = 64;
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(
+ 42, "test",
+ Struct("test.header")
+ .addField("primitive1", DataType::T_INT)
+ .addField("l1s1", Struct("struct3")
+ .addField("primitive1", DataType::T_INT)
+ .addField("ss", Struct("struct2")
+ .setId(struct2_id)
+ .addField("primitive1", DataType::T_INT)
+ .addField("primitive2", DataType::T_INT)
+ .addField("iarray", Array(DataType::T_INT))
+ .addField("sarray", Array(
+ Struct("struct1")
+ .addField("primitive1",
+ DataType::T_INT)
+ .addField("primitive2",
+ DataType::T_INT)))
+ .addField("smap", Map(DataType::T_STRING,
+ DataType::T_STRING)))
+ .addField("structmap",
+ Map(DataType::T_STRING, struct2_id))
+ .addField("wset", Wset(DataType::T_STRING))
+ .addField("structwset", Wset(struct2_id))),
+ Struct("test.body"));
+ return builder.config();
+}
+
+Document::UP
+createTestDocument(const DocumentTypeRepo &repo)
+{
+ const DocumentType* type(repo.getDocumentType("test"));
+ const DataType* struct3(repo.getDataType(*type, "struct3"));
+ const DataType* struct2(repo.getDataType(*type, "struct2"));
+ const DataType* iarr(repo.getDataType(*type, "Array<Int>"));
+ const DataType* sarr(repo.getDataType(*type, "Array<struct1>"));
+ const DataType* struct1(repo.getDataType(*type, "struct1"));
+ const DataType* smap(repo.getDataType(*type, "Map<String,String>"));
+ const DataType* structmap(repo.getDataType(*type, "Map<String,struct2>"));
+ const DataType* wset(repo.getDataType(*type, "WeightedSet<String>"));
+ const DataType* structwset(repo.getDataType(*type, "WeightedSet<struct2>"));
+ Document::UP
+ doc(new Document(*type, DocumentId("doc::testdoc")));
+ doc->setValue("primitive1", IntFieldValue(1));
+ StructFieldValue l1s1(*struct3);
+ l1s1.setValue("primitive1", IntFieldValue(2));
+
+ StructFieldValue l2s1(*struct2);
+ l2s1.setValue("primitive1", IntFieldValue(3));
+ l2s1.setValue("primitive2", IntFieldValue(4));
+ StructFieldValue l2s2(*struct2);
+ l2s2.setValue("primitive1", IntFieldValue(5));
+ l2s2.setValue("primitive2", IntFieldValue(6));
+ ArrayFieldValue iarr1(*iarr);
+ iarr1.add(IntFieldValue(11));
+ iarr1.add(IntFieldValue(12));
+ iarr1.add(IntFieldValue(13));
+ ArrayFieldValue sarr1(*sarr);
+ StructFieldValue l3s1(*struct1);
+ l3s1.setValue("primitive1", IntFieldValue(1));
+ l3s1.setValue("primitive2", IntFieldValue(2));
+ sarr1.add(l3s1);
+ sarr1.add(l3s1);
+ MapFieldValue smap1(*smap);
+ smap1.put(StringFieldValue("leonardo"), StringFieldValue("dicaprio"));
+ smap1.put(StringFieldValue("ellen"), StringFieldValue("page"));
+ smap1.put(StringFieldValue("joseph"), StringFieldValue("gordon-levitt"));
+ l2s1.setValue("smap", smap1);
+ l2s1.setValue("iarray", iarr1);
+ l2s1.setValue("sarray", sarr1);
+
+ l1s1.setValue("ss", l2s1);
+ MapFieldValue structmap1(*structmap);
+ structmap1.put(StringFieldValue("test"), l2s1);
+ l1s1.setValue("structmap", structmap1);
+
+ WeightedSetFieldValue wset1(*wset);
+ wset1.add("foo");
+ wset1.add("bar");
+ wset1.add("zoo");
+ l1s1.setValue("wset", wset1);
+
+ WeightedSetFieldValue wset2(*structwset);
+ wset2.add(l2s1);
+ wset2.add(l2s2);
+ l1s1.setValue("structwset", wset2);
+
+ doc->setValue("l1s1", l1s1);
+ return doc;
+}
+
+ByteBuffer::UP serializeHEAD(const DocumentUpdate & update)
+{
+ vespalib::nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.writeHEAD(update);
+ ByteBuffer::UP retVal(new ByteBuffer(stream.size()));
+ retVal->putBytes(stream.peek(), stream.size());
+ return retVal;
+}
+
+void testSerialize(const DocumentTypeRepo& repo, const DocumentUpdate& a) {
+ try{
+ bool affectsBody = a.affectsDocumentBody();
+ ByteBuffer::UP bb(serializeHEAD(a));
+ bb->flip();
+ DocumentUpdate::UP b(DocumentUpdate::createHEAD(repo, *bb));
+
+ CPPUNIT_ASSERT_EQUAL(affectsBody, b->affectsDocumentBody());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), bb->getRemaining());
+ CPPUNIT_ASSERT_EQUAL(a.getId().toString(), b->getId().toString());
+ CPPUNIT_ASSERT_EQUAL(a.getUpdates().size(), b->getUpdates().size());
+ for (size_t i(0); i < a.getUpdates().size(); i++) {
+ const FieldUpdate & ua = a.getUpdates()[i];
+ const FieldUpdate & ub = b->getUpdates()[i];
+
+ CPPUNIT_ASSERT_EQUAL(&ua.getField(), &ub.getField());
+ CPPUNIT_ASSERT_EQUAL(ua.getUpdates().size(),
+ ub.getUpdates().size());
+ for (size_t j(0); j < ua.getUpdates().size(); j++) {
+ CPPUNIT_ASSERT_EQUAL(ua.getUpdates()[j]->getType(),
+ ub.getUpdates()[j]->getType());
+ }
+ }
+ CPPUNIT_ASSERT_EQUAL(a.getFieldPathUpdates().size(), b->getFieldPathUpdates().size());
+ for (size_t i(0); i < a.getFieldPathUpdates().size(); i++) {
+ const FieldPathUpdate::CP& ua = a.getFieldPathUpdates()[i];
+ const FieldPathUpdate::CP& ub = b->getFieldPathUpdates()[i];
+
+ CPPUNIT_ASSERT_EQUAL(*ua, *ub);
+ }
+ CPPUNIT_ASSERT_EQUAL(a, *b);
+ } catch (std::exception& e) {
+ std::cerr << "Failed while testing document field path update:\n"
+ << a.toString(true) << "\n";
+ throw;
+ }
+}
+
+} // anon ns
+
+struct TestFieldPathUpdate : FieldPathUpdate
+{
+ struct TestIteratorHandler : FieldValue::IteratorHandler
+ {
+ TestIteratorHandler(std::string& str)
+ : _str(str) {}
+
+ ModificationStatus doModify(FieldValue& value)
+ {
+ std::ostringstream ss;
+ value.print(ss, false, "");
+ if (!_str.empty()) {
+ _str += ';';
+ }
+ _str += ss.str();
+ return NOT_MODIFIED;
+ }
+
+ bool onComplex(const Content&) { return false; }
+
+ std::string& _str;
+ };
+
+ mutable std::string _str;
+
+ TestFieldPathUpdate(const DocumentTypeRepo& repo,
+ const DataType *type,
+ const std::string& fieldPath,
+ const std::string& whereClause)
+ : FieldPathUpdate(repo, *type, fieldPath, whereClause)
+ {
+ }
+
+ TestFieldPathUpdate(const TestFieldPathUpdate& other)
+ : FieldPathUpdate(other)
+ {
+ }
+
+ std::unique_ptr<FieldValue::IteratorHandler> getIteratorHandler(Document&) const
+ {
+ return std::unique_ptr<FieldValue::IteratorHandler>(
+ new TestIteratorHandler(_str));
+ }
+
+ TestFieldPathUpdate* clone() const { return new TestFieldPathUpdate(*this); }
+
+ void print(std::ostream& out, bool, const std::string&) const
+ {
+ out << "TestFieldPathUpdate()";
+ }
+
+ void accept(UpdateVisitor & visitor) const override { (void) visitor; }
+ uint8_t getSerializedType() const override { assert(false); return 7; }
+};
+
+void
+FieldPathUpdateTestCase::setUp()
+{
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(42, "foobar",
+ Struct("foobar.header")
+ .addField("num", DataType::T_INT)
+ .addField("byteval", DataType::T_BYTE)
+ .addField("strfoo", DataType::T_STRING)
+ .addField("strarray", Array(DataType::T_STRING)),
+ Struct("foobar.body")
+ .addField("strwset", Wset(DataType::T_STRING))
+ .addField("structmap",
+ Map(DataType::T_STRING, Struct("mystruct")
+ .addField("title", DataType::T_STRING)
+ .addField("rating", DataType::T_INT)))
+ .addField("strmap",
+ Map(DataType::T_STRING, DataType::T_STRING)));
+ _repo.reset(new DocumentTypeRepo(builder.config()));
+
+ _foobar_type = *_repo->getDocumentType("foobar");
+}
+
+void
+FieldPathUpdateTestCase::tearDown()
+{
+}
+
+void
+FieldPathUpdateTestCase::testWhereClause()
+{
+ DocumentTypeRepo repo(getRepoConfig());
+ Document::UP doc(createTestDocument(repo));
+ std::string where = "test.l1s1.structmap.value.smap{$x} == \"dicaprio\"";
+ TestFieldPathUpdate update(repo, doc->getDataType(),
+ "l1s1.structmap.value.smap{$x}", where);
+ update.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(std::string("dicaprio"), update._str);
+}
+
+void
+FieldPathUpdateTestCase::testNoIterateMapValues()
+{
+ DocumentTypeRepo repo(getRepoConfig());
+ Document::UP doc(createTestDocument(repo));
+ TestFieldPathUpdate update(repo, doc->getDataType(),
+ "l1s1.structwset.primitive1", "true");
+ update.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(std::string("3;5"), update._str);
+}
+
+void
+FieldPathUpdateTestCase::testRemoveField()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:things:thangs")));
+ CPPUNIT_ASSERT(doc->hasValue("strfoo") == false);
+ doc->setValue("strfoo", StringFieldValue("cocacola"));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("cocacola"), doc->getValue("strfoo")->getAsString());
+ //doc->print(std::cerr, true, "");
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(FieldPathUpdate::CP(new RemoveFieldPathUpdate(*_repo, *doc->getDataType(), "strfoo")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT(doc->hasValue("strfoo") == false);
+}
+
+void
+FieldPathUpdateTestCase::testApplyRemoveMultiList()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:things:thangs")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+ {
+ ArrayFieldValue
+ strArray(doc->getType().getField("strarray").getDataType());
+ strArray.add(StringFieldValue("crouching tiger, hidden field"));
+ strArray.add(StringFieldValue("remove val 1"));
+ strArray.add(StringFieldValue("hello hello"));
+ doc->setValue("strarray", strArray);
+ }
+ CPPUNIT_ASSERT(doc->hasValue("strarray"));
+ //doc->print(std::cerr, true, "");
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new RemoveFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strarray[$x]", "foobar.strarray[$x] == \"remove val 1\"")));
+ docUp.applyTo(*doc);
+ {
+ std::unique_ptr<ArrayFieldValue> strArray =
+ doc->getAs<ArrayFieldValue>(doc->getField("strarray"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), strArray->size());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("crouching tiger, hidden field"), (*strArray)[0].getAsString());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("hello hello"), (*strArray)[1].getAsString());
+ }
+}
+
+void
+FieldPathUpdateTestCase::testApplyRemoveEntireListField()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:things:thangs")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+ {
+ ArrayFieldValue
+ strArray(doc->getType().getField("strarray").getDataType());
+ strArray.add(StringFieldValue("this list"));
+ strArray.add(StringFieldValue("should be"));
+ strArray.add(StringFieldValue("totally removed"));
+ doc->setValue("strarray", strArray);
+ }
+ //doc->print(std::cerr, true, "");
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new RemoveFieldPathUpdate(*_repo, *doc->getDataType(), "strarray", "")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT(!doc->hasValue("strarray"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyRemoveMultiWset()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:helan:halvan")));
+ CPPUNIT_ASSERT(doc->hasValue("strwset") == false);
+ {
+ WeightedSetFieldValue
+ strWset(doc->getType().getField("strwset").getDataType());
+ strWset.add(StringFieldValue("hello hello"), 10);
+ strWset.add(StringFieldValue("remove val 1"), 20);
+ doc->setValue("strwset", strWset);
+ }
+ CPPUNIT_ASSERT(doc->hasValue("strwset"));
+ //doc->print(std::cerr, true, "");
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new RemoveFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strwset{remove val 1}")));
+ docUp.applyTo(*doc);
+ {
+ std::unique_ptr<WeightedSetFieldValue> strWset =
+ doc->getAs<WeightedSetFieldValue>(doc->getField("strwset"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), strWset->size());
+ CPPUNIT_ASSERT_EQUAL(10, strWset->get(StringFieldValue("hello hello")));
+ }
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignSingle()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:drekka:karsk")));
+ CPPUNIT_ASSERT(doc->hasValue("strfoo") == false);
+ // Test assignment of non-existing
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strfoo", std::string(), StringFieldValue("himert"))));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT(doc->hasValue("strfoo"));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("himert"), doc->getValue("strfoo")->getAsString());
+ // Test overwriting existing
+ DocumentUpdate docUp2(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp2.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strfoo", std::string(), StringFieldValue("wunderbaum"))));
+ docUp2.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("wunderbaum"), doc->getValue("strfoo")->getAsString());
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMath()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ doc->setValue("num", IntFieldValue(34));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "num", "", "($value * 2) / $value")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(IntFieldValue(2)), *doc->getValue("num"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMathByteToZero()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ doc->setValue("byteval", ByteFieldValue(3));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "byteval", "", "$value - 3")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(ByteFieldValue(0)), *doc->getValue("byteval"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMathNotModifiedOnUnderflow()
+{
+ int low_value = -126;
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ doc->setValue("byteval", ByteFieldValue(low_value));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "byteval", "", "$value - 4")));
+ docUp.applyTo(*doc);
+ // Over/underflow will happen. You must have control of your data types.
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(ByteFieldValue((char)(low_value - 4))), *doc->getValue("byteval"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMathNotModifiedOnOverflow()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ doc->setValue("byteval", ByteFieldValue(127));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "byteval", "", "$value + 200")));
+ docUp.applyTo(*doc);
+ // Over/underflow will happen. You must have control of your data types.
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(ByteFieldValue(static_cast<char>(static_cast<int>(127+200)))), *doc->getValue("byteval"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMathDivZero()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ CPPUNIT_ASSERT(doc->hasValue("num") == false);
+ doc->setValue("num", IntFieldValue(10));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "num", "", "$value / ($value - 10)")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(IntFieldValue(10)), *doc->getValue("num"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignFieldNotExistingInExpression()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ CPPUNIT_ASSERT(doc->hasValue("num") == false);
+ doc->setValue("num", IntFieldValue(10));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "num", "", "foobar.num2 + $value")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(IntFieldValue(10)), *doc->getValue("num"));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignFieldNotExistingInPath()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ try {
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "nosuchnum", "", "foobar.num + $value")));
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException&) {
+ }
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignTargetNotExisting()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ CPPUNIT_ASSERT(doc->hasValue("num") == false);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "num", "", "$value + 5")));
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT_EQUAL(static_cast<const FieldValue&>(IntFieldValue(5)), *doc->getValue("num"));
+}
+
+void
+FieldPathUpdateTestCase::testAssignSimpleMapValueWithVariable()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bug:hunter")));
+
+ MapFieldValue mfv(doc->getType().getField("strmap").getDataType());
+ mfv.put(StringFieldValue("foo"), StringFieldValue("bar"));
+ mfv.put(StringFieldValue("baz"), StringFieldValue("bananas"));
+ doc->setValue("strmap", mfv);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ // Select on value, not key
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strmap{$x}", "foobar.strmap{$x} == \"bar\"", StringFieldValue("shinyvalue"))));
+ docUp.applyTo(*doc);
+
+ std::unique_ptr<MapFieldValue> valueNow(
+ doc->getAs<MapFieldValue>(doc->getField("strmap")));
+
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), valueNow->size());
+ CPPUNIT_ASSERT_EQUAL(
+ static_cast<const FieldValue&>(StringFieldValue("shinyvalue")),
+ *valueNow->get(StringFieldValue("foo")));
+ CPPUNIT_ASSERT_EQUAL(
+ static_cast<const FieldValue&>(StringFieldValue("bananas")),
+ *valueNow->get(StringFieldValue("baz")));
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMathRemoveIfZero()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ CPPUNIT_ASSERT(doc->hasValue("num") == false);
+ doc->setValue("num", IntFieldValue(34));
+ CPPUNIT_ASSERT(doc->hasValue("num") == true);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ FieldPathUpdate::CP up1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "num", "", "($value * 2) / $value - 2"));
+ static_cast<AssignFieldPathUpdate&>(*up1).setRemoveIfZero(true);
+ docUp.addFieldPathUpdate(up1);
+
+ docUp.applyTo(*doc);
+ CPPUNIT_ASSERT(doc->hasValue("num") == false);
+}
+
+void
+FieldPathUpdateTestCase::testApplyAssignMultiList()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:fest:skinnvest")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+
+ {
+ ArrayFieldValue
+ strArray(doc->getType().getField("strarray").getDataType());
+ strArray.add(StringFieldValue("hello hello"));
+ strArray.add(StringFieldValue("blah blargh"));
+ doc->setValue("strarray", strArray);
+ CPPUNIT_ASSERT(doc->hasValue("strarray"));
+ }
+
+ ArrayFieldValue
+ updateArray(doc->getType().getField("strarray").getDataType());
+ updateArray.add(StringFieldValue("assigned val 0"));
+ updateArray.add(StringFieldValue("assigned val 1"));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strarray", std::string(), updateArray)));
+ docUp.applyTo(*doc);
+ {
+ std::unique_ptr<ArrayFieldValue> strArray =
+ doc->getAs<ArrayFieldValue>(doc->getField("strarray"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), strArray->size());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("assigned val 0"), (*strArray)[0].getAsString());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("assigned val 1"), (*strArray)[1].getAsString());
+ }
+}
+
+
+void
+FieldPathUpdateTestCase::testApplyAssignMultiWset()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:fest:skinnvest")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+
+ {
+ WeightedSetFieldValue
+ strWset(doc->getType().getField("strwset").getDataType());
+ strWset.add(StringFieldValue("hello gentlemen"), 10);
+ strWset.add(StringFieldValue("what you say"), 20);
+ doc->setValue("strwset", strWset);
+ CPPUNIT_ASSERT(doc->hasValue("strwset"));
+ }
+
+ WeightedSetFieldValue
+ assignWset(doc->getType().getField("strwset").getDataType());
+ assignWset.add(StringFieldValue("assigned val 0"), 5);
+ assignWset.add(StringFieldValue("assigned val 1"), 10);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strwset", std::string(), assignWset)));
+ //doc->print(std::cerr, true, "");
+ docUp.applyTo(*doc);
+ //doc->print(std::cerr, true, "");
+ {
+ std::unique_ptr<WeightedSetFieldValue> strWset =
+ doc->getAs<WeightedSetFieldValue>(doc->getField("strwset"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), strWset->size());
+ CPPUNIT_ASSERT_EQUAL(5, strWset->get(StringFieldValue("assigned val 0")));
+ CPPUNIT_ASSERT_EQUAL(10, strWset->get(StringFieldValue("assigned val 1")));
+ }
+}
+
+void
+FieldPathUpdateTestCase::testAssignWsetRemoveIfZero()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:tronder:bataljon")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+
+ {
+ WeightedSetFieldValue
+ strWset(doc->getType().getField("strwset").getDataType());
+ strWset.add(StringFieldValue("you say goodbye"), 164);
+ strWset.add(StringFieldValue("but i say hello"), 243);
+ doc->setValue("strwset", strWset);
+ CPPUNIT_ASSERT(doc->hasValue("strwset"));
+ }
+
+ {
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ IntFieldValue zeroWeight(0);
+ FieldPathUpdate::CP assignUpdate(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strwset{you say goodbye}", std::string(), zeroWeight));
+ static_cast<AssignFieldPathUpdate&>(*assignUpdate).setRemoveIfZero(true);
+ docUp.addFieldPathUpdate(assignUpdate);
+ //doc->print(std::cerr, true, "");
+ docUp.applyTo(*doc);
+ //doc->print(std::cerr, true, "");
+ {
+ std::unique_ptr<WeightedSetFieldValue> strWset =
+ doc->getAs<WeightedSetFieldValue>(doc->getField("strwset"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), strWset->size());
+ CPPUNIT_ASSERT_EQUAL(243, strWset->get(StringFieldValue("but i say hello")));
+ }
+ }
+}
+
+void
+FieldPathUpdateTestCase::testApplyAddMultiList()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:george:costanza")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+
+ ArrayFieldValue adds(doc->getType().getField("strarray").getDataType());
+ adds.add(StringFieldValue("serenity now"));
+ adds.add(StringFieldValue("a festivus for the rest of us"));
+ adds.add(StringFieldValue("george is getting upset!"));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AddFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strarray", std::string(), adds)));
+ //doc->print(std::cerr, true, "");
+ docUp.applyTo(*doc);
+ //doc->print(std::cerr, true, "");
+ CPPUNIT_ASSERT(doc->hasValue("strarray"));
+}
+
+void
+FieldPathUpdateTestCase::testAddAndAssignList()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:fancy:pants")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+
+ {
+ ArrayFieldValue
+ strArray(doc->getType().getField("strarray").getDataType());
+ strArray.add(StringFieldValue("hello hello"));
+ strArray.add(StringFieldValue("blah blargh"));
+ doc->setValue("strarray", strArray);
+ CPPUNIT_ASSERT(doc->hasValue("strarray"));
+ }
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strarray[1]", std::string(), StringFieldValue("assigned val 1"))));
+
+ ArrayFieldValue adds(doc->getType().getField("strarray").getDataType());
+ adds.add(StringFieldValue("new value"));
+
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AddFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strarray", std::string(), adds)));
+ //doc->print(std::cerr, true, "");
+ docUp.applyTo(*doc);
+ //doc->print(std::cerr, true, "");
+ {
+ std::unique_ptr<ArrayFieldValue> strArray =
+ doc->getAs<ArrayFieldValue>(doc->getField("strarray"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), strArray->size());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("hello hello"), (*strArray)[0].getAsString());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("assigned val 1"), (*strArray)[1].getAsString());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("new value"), (*strArray)[2].getAsString());
+ }
+}
+
+namespace {
+struct Keys {
+ vespalib::string key1;
+ vespalib::string key2;
+ vespalib::string key3;
+ Keys() : key1("foo"), key2("bar"), key3("zoo") {}
+};
+
+struct Fixture {
+ Document::UP doc;
+ MapFieldValue mfv;
+ StructFieldValue fv1, fv2, fv3, fv4;
+
+ const MapDataType &getMapType(const DocumentType &doc_type) {
+ return static_cast<const MapDataType &>(
+ doc_type.getField("structmap").getDataType());
+ }
+
+ Fixture(const DocumentType &doc_type, const Keys &k)
+ : doc(new Document(doc_type, DocumentId("doc:planet:express"))),
+ mfv(getMapType(doc_type)),
+ fv1(getMapType(doc_type).getValueType()),
+ fv2(getMapType(doc_type).getValueType()),
+ fv3(getMapType(doc_type).getValueType()),
+ fv4(getMapType(doc_type).getValueType()) {
+
+ fv1.setValue("title", StringFieldValue("fry"));
+ fv1.setValue("rating", IntFieldValue(30));
+ mfv.put(StringFieldValue(k.key1), fv1);
+
+ fv2.setValue("title", StringFieldValue("farnsworth"));
+ fv2.setValue("rating", IntFieldValue(60));
+ mfv.put(StringFieldValue(k.key2), fv2);
+
+ fv3.setValue("title", StringFieldValue("zoidberg"));
+ fv3.setValue("rating", IntFieldValue(-20));
+ mfv.put(StringFieldValue(k.key3), fv3);
+
+ doc->setValue("structmap", mfv);
+
+ fv4.setValue("title", StringFieldValue("farnsworth"));
+ fv4.setValue("rating", IntFieldValue(48));
+ }
+};
+
+} // namespace
+
+void
+FieldPathUpdateTestCase::testAssignMap()
+{
+ Keys k;
+ Fixture f(_foobar_type, k);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *f.doc->getDataType(),
+ "structmap{" + k.key2 + "}", std::string(),
+ f.fv4)));
+ docUp.applyTo(*f.doc);
+
+ std::unique_ptr<MapFieldValue> valueNow =
+ f.doc->getAs<MapFieldValue>(f.doc->getField("structmap"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), valueNow->size());
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv1),
+ *valueNow->get(StringFieldValue(k.key1)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv4),
+ *valueNow->get(StringFieldValue(k.key2)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv3),
+ *valueNow->get(StringFieldValue(k.key3)));
+}
+
+void
+FieldPathUpdateTestCase::testAssignMapStruct()
+{
+ Keys k;
+ Fixture f(_foobar_type, k);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *f.doc->getDataType(),
+ "structmap{" + k.key2 + "}.rating", std::string(),
+ IntFieldValue(48))));
+ docUp.applyTo(*f.doc);
+
+ std::unique_ptr<MapFieldValue> valueNow =
+ f.doc->getAs<MapFieldValue>(f.doc->getField("structmap"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), valueNow->size());
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv1),
+ *valueNow->get(StringFieldValue(k.key1)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv4),
+ *valueNow->get(StringFieldValue(k.key2)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv3),
+ *valueNow->get(StringFieldValue(k.key3)));
+}
+
+void
+FieldPathUpdateTestCase::testAssignMapStructVariable()
+{
+ Keys k;
+ Fixture f(_foobar_type, k);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *f.doc->getDataType(),
+ "structmap{$x}.rating",
+ "foobar.structmap{$x}.title == \"farnsworth\"",
+ IntFieldValue(48))));
+ docUp.applyTo(*f.doc);
+
+ std::unique_ptr<MapFieldValue> valueNow =
+ f.doc->getAs<MapFieldValue>(f.doc->getField("structmap"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), valueNow->size());
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv1),
+ *valueNow->get(StringFieldValue(k.key1)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv4),
+ *valueNow->get(StringFieldValue(k.key2)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv3),
+ *valueNow->get(StringFieldValue(k.key3)));
+}
+
+void
+FieldPathUpdateTestCase::testAssignMapNoExist()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:planet:express")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ StructFieldValue fv1(dynamic_cast<const MapDataType&>(*mfv.getDataType())
+ .getValueType());
+ fv1.setValue("title", StringFieldValue("fry"));
+ fv1.setValue("rating", IntFieldValue(30));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{foo}", std::string(), fv1)));
+ //doc->print(std::cerr, true, "");
+ docUp.applyTo(*doc);
+ //doc->print(std::cerr, true, "");
+
+ std::unique_ptr<MapFieldValue> valueNow =
+ doc->getAs<MapFieldValue>(doc->getField("structmap"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), valueNow->size());
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(fv1), *valueNow->get(StringFieldValue("foo")));
+}
+
+void
+FieldPathUpdateTestCase::testAssignMapNoExistNoCreate()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:planet:express")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ StructFieldValue fv1(dynamic_cast<const MapDataType&>(*mfv.getDataType())
+ .getValueType());
+ fv1.setValue("title", StringFieldValue("fry"));
+ fv1.setValue("rating", IntFieldValue(30));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ FieldPathUpdate::CP assignUpdate(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{foo}", std::string(), fv1));
+ static_cast<AssignFieldPathUpdate&>(*assignUpdate).setCreateMissingPath(false);
+ docUp.addFieldPathUpdate(assignUpdate);
+
+ //doc->print(std::cerr, true, "");
+ docUp.applyTo(*doc);
+ //doc->print(std::cerr, true, "");
+
+ std::unique_ptr<MapFieldValue> valueNow =
+ doc->getAs<MapFieldValue>(doc->getField("structmap"));
+ CPPUNIT_ASSERT(valueNow.get() == 0);
+}
+
+void
+FieldPathUpdateTestCase::testQuotedStringKey()
+{
+ Keys k;
+ k.key2 = "here is a \"fancy\" 'map' :-} key :-{";
+ const char field_path[] =
+ "structmap{\"here is a \\\"fancy\\\" 'map' :-} key :-{\"}";
+ Fixture f(_foobar_type, k);
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *f.doc->getDataType(),
+ field_path, std::string(), f.fv4)));
+ docUp.applyTo(*f.doc);
+
+ std::unique_ptr<MapFieldValue> valueNow =
+ f.doc->getAs<MapFieldValue>(f.doc->getField("structmap"));
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), valueNow->size());
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv1),
+ *valueNow->get(StringFieldValue(k.key1)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv4),
+ *valueNow->get(StringFieldValue(k.key2)));
+ CPPUNIT_ASSERT_EQUAL(static_cast<FieldValue&>(f.fv3),
+ *valueNow->get(StringFieldValue(k.key3)));
+}
+
+void
+FieldPathUpdateTestCase::testEqualityComparison()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:foo:zoo")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ StructFieldValue fv4(dynamic_cast<const MapDataType&>(*mfv.getDataType())
+ .getValueType());
+ fv4.setValue("title", StringFieldValue("tasty cake"));
+ fv4.setValue("rating", IntFieldValue(95));
+
+ {
+ DocumentUpdate docUp1(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ DocumentUpdate docUp2(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ CPPUNIT_ASSERT(docUp1 == docUp2);
+
+ FieldPathUpdate::CP assignUp1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{here be dragons}", std::string(), fv4));
+ docUp1.addFieldPathUpdate(assignUp1);
+ CPPUNIT_ASSERT(docUp1 != docUp2);
+ docUp2.addFieldPathUpdate(assignUp1);
+ CPPUNIT_ASSERT(docUp1 == docUp2);
+ }
+ {
+ DocumentUpdate docUp1(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ DocumentUpdate docUp2(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ // where-clause diff
+ FieldPathUpdate::CP assignUp1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{here be dragons}", std::string(), fv4));
+ FieldPathUpdate::CP assignUp2(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{here be dragons}", "false", fv4));
+ docUp1.addFieldPathUpdate(assignUp1);
+ docUp2.addFieldPathUpdate(assignUp2);
+ CPPUNIT_ASSERT(docUp1 != docUp2);
+ }
+ {
+ DocumentUpdate docUp1(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ DocumentUpdate docUp2(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ // fieldpath diff
+ FieldPathUpdate::CP assignUp1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{here be dragons}", std::string(), fv4));
+ FieldPathUpdate::CP assignUp2(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{here be kittens}", std::string(), fv4));
+ docUp1.addFieldPathUpdate(assignUp1);
+ docUp2.addFieldPathUpdate(assignUp2);
+ CPPUNIT_ASSERT(docUp1 != docUp2);
+ }
+
+}
+
+void
+FieldPathUpdateTestCase::testAffectsDocumentBody()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:things:stuff")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ StructFieldValue fv4(dynamic_cast<const MapDataType&>(*mfv.getDataType())
+ .getValueType());
+ fv4.setValue("title", StringFieldValue("scruffy"));
+ fv4.setValue("rating", IntFieldValue(90));
+
+ // structmap is body field
+ {
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ CPPUNIT_ASSERT(!docUp.affectsDocumentBody());
+
+ FieldPathUpdate::CP update1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{janitor}", std::string(), fv4));
+ static_cast<AssignFieldPathUpdate&>(*update1).setCreateMissingPath(true);
+ docUp.addFieldPathUpdate(update1);
+ CPPUNIT_ASSERT(docUp.affectsDocumentBody());
+ }
+
+ // strfoo is header field
+ {
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ FieldPathUpdate::CP update1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strfoo", std::string(), StringFieldValue("helloworld")));
+ static_cast<AssignFieldPathUpdate&>(*update1).setCreateMissingPath(true);
+ docUp.addFieldPathUpdate(update1);
+ CPPUNIT_ASSERT(!docUp.affectsDocumentBody());
+ }
+
+}
+
+void
+FieldPathUpdateTestCase::testIncompatibleDataTypeFails()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:things:stuff")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+
+ try {
+ FieldPathUpdate::CP update1(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{foo}", std::string(),
+ StringFieldValue("bad things")));
+ CPPUNIT_ASSERT(false);
+ } catch (const vespalib::IllegalArgumentException& e) {
+ // OK
+ }
+}
+
+void
+FieldPathUpdateTestCase::testSerializeAssign()
+{
+ Document::UP doc(
+ new Document(_foobar_type,
+ DocumentId("doc:weloveto:serializestuff")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ StructFieldValue val(dynamic_cast<const MapDataType&>(*mfv.getDataType())
+ .getValueType());
+ val.setValue("title", StringFieldValue("cool frog"));
+ val.setValue("rating", IntFieldValue(100));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ CPPUNIT_ASSERT(!docUp.affectsDocumentBody());
+
+ FieldPathUpdate::CP update1(new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{ribbit}", "true", val));
+ static_cast<AssignFieldPathUpdate&>(*update1).setCreateMissingPath(true);
+ docUp.addFieldPathUpdate(update1);
+
+ testSerialize(*_repo, docUp);
+
+}
+
+void
+FieldPathUpdateTestCase::testSerializeAdd()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:george:costanza")));
+ CPPUNIT_ASSERT(doc->hasValue("strarray") == false);
+
+ ArrayFieldValue adds(doc->getType().getField("strarray").getDataType());
+ adds.add(StringFieldValue("serenity now"));
+ adds.add(StringFieldValue("a festivus for the rest of us"));
+ adds.add(StringFieldValue("george is getting upset!"));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ CPPUNIT_ASSERT(!docUp.affectsDocumentBody());
+
+ FieldPathUpdate::CP update1(new AddFieldPathUpdate(*_repo, *doc->getDataType(),
+ "strarray", std::string(), adds));
+ docUp.addFieldPathUpdate(update1);
+
+ testSerialize(*_repo, docUp);
+}
+
+void
+FieldPathUpdateTestCase::testSerializeRemove()
+{
+ Document::UP doc(
+ new Document(_foobar_type,
+ DocumentId("doc:weloveto:serializestuff")));
+ MapFieldValue mfv(doc->getType().getField("structmap").getDataType());
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ CPPUNIT_ASSERT(!docUp.affectsDocumentBody());
+
+ FieldPathUpdate::CP update1(new RemoveFieldPathUpdate(*_repo, *doc->getDataType(),
+ "structmap{ribbit}", std::string()));
+ docUp.addFieldPathUpdate(update1);
+
+ testSerialize(*_repo, docUp);
+}
+
+void
+FieldPathUpdateTestCase::testSerializeAssignMath()
+{
+ Document::UP doc(
+ new Document(_foobar_type, DocumentId("doc:bat:man")));
+ CPPUNIT_ASSERT(doc->hasValue("num") == false);
+ doc->setValue("num", IntFieldValue(34));
+
+ DocumentUpdate docUp(_foobar_type, DocumentId("doc:barbar:foofoo"));
+ docUp.addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AssignFieldPathUpdate(*_repo, *doc->getDataType(),
+ "num", "", "($value * 2) / $value")));
+ testSerialize(*_repo, docUp);
+}
+
+DocumentUpdate::UP
+FieldPathUpdateTestCase::createDocumentUpdateForSerialization(const DocumentTypeRepo& repo)
+{
+ const DocumentType *docType(repo.getDocumentType("serializetest"));
+ DocumentUpdate::UP docUp(new DocumentUpdate(
+ *docType, DocumentId("doc:serialization:xlanguage")));
+
+ FieldPathUpdate::CP
+ assign(new AssignFieldPathUpdate(repo, *docType, "intfield", "", "3"));
+ static_cast<AssignFieldPathUpdate&>(*assign).setRemoveIfZero(true);
+ static_cast<AssignFieldPathUpdate&>(*assign).setCreateMissingPath(false);
+ docUp->addFieldPathUpdate(assign);
+
+ ArrayFieldValue
+ fArray(docType->getField("arrayoffloatfield").getDataType());
+ fArray.add(FloatFieldValue(12.0));
+ fArray.add(FloatFieldValue(5.0));
+
+ docUp->addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new AddFieldPathUpdate(repo, *docType, "arrayoffloatfield", "", fArray)));
+
+ docUp->addFieldPathUpdate(
+ FieldPathUpdate::CP(
+ new RemoveFieldPathUpdate(repo, *docType, "intfield", "serializetest.intfield > 0")));
+
+ return docUp;
+}
+
+void
+FieldPathUpdateTestCase::testReadSerializedFile()
+{
+ // Reads a file serialized from java
+ const char cfg_file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(cfg_file_name));
+
+ int fd = open("data/serialize-fieldpathupdate-java.dat", O_RDONLY);
+
+ int len = lseek(fd,0,SEEK_END);
+ ByteBuffer buf(len);
+ lseek(fd,0,SEEK_SET);
+ read(fd, buf.getBuffer(), len);
+
+ close(fd);
+
+ DocumentUpdate::UP updp(DocumentUpdate::createHEAD(repo, buf));
+ DocumentUpdate& upd(*updp);
+
+ DocumentUpdate::UP compare(
+ createDocumentUpdateForSerialization(repo));
+ CPPUNIT_ASSERT_EQUAL(*compare, upd);
+}
+
+void
+FieldPathUpdateTestCase::testGenerateSerializedFile()
+{
+ const char cfg_file_name[] = "data/crossplatform-java-cpp-doctypes.cfg";
+ DocumentTypeRepo repo(readDocumenttypesConfig(cfg_file_name));
+ // Tests nothing, only generates a file for java test
+ DocumentUpdate::UP upd(
+ createDocumentUpdateForSerialization(repo));
+
+ ByteBuffer::UP buf(serializeHEAD(*upd));
+
+ int fd = open("data/serialize-fieldpathupdate-cpp.dat",
+ O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ write(fd, buf->getBuffer(), buf->getPos());
+ close(fd);
+}
+
+} // ns document
diff --git a/document/src/tests/fieldsettest.cpp b/document/src/tests/fieldsettest.cpp
new file mode 100644
index 00000000000..41a530c4b17
--- /dev/null
+++ b/document/src/tests/fieldsettest.cpp
@@ -0,0 +1,348 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/base/testdocman.h>
+#include <vespa/document/fieldset/fieldsetrepo.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/vespalib/io/fileutil.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/datatype/annotationreferencedatatype.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <fstream>
+
+using vespalib::nbostream;
+
+using namespace document::config_builder;
+
+namespace document {
+
+struct FieldSetTest : public CppUnit::TestFixture {
+ void testParsing();
+ void testContains();
+ void testCopyDocumentFields();
+ void testDocumentSubsetCopy();
+ void testStripFields();
+ void testSerialize();
+
+ CPPUNIT_TEST_SUITE(FieldSetTest);
+ CPPUNIT_TEST(testParsing);
+ CPPUNIT_TEST(testSerialize);
+ CPPUNIT_TEST(testContains);
+ CPPUNIT_TEST(testCopyDocumentFields);
+ CPPUNIT_TEST(testDocumentSubsetCopy);
+ CPPUNIT_TEST(testStripFields);
+ CPPUNIT_TEST_SUITE_END();
+
+ std::string stringifyFields(const Document& doc) const;
+ std::string doCopyFields(const Document& src,
+ const DocumentTypeRepo& docRepo,
+ const std::string& fieldSetStr,
+ Document* dest = 0) const;
+ std::string doCopyDocument(const Document& src,
+ const DocumentTypeRepo& docRepo,
+ const std::string& fieldSetStr);
+ std::string doStripFields(const Document& doc,
+ const DocumentTypeRepo& docRepo,
+ const std::string& fieldSetStr);
+ Document::UP createTestDocument(const TestDocMan& testDocMan) const;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(FieldSetTest);
+
+void FieldSetTest::testParsing()
+{
+ TestDocMan testDocMan;
+ const DocumentTypeRepo& docRepo = testDocMan.getTypeRepo();
+
+ FieldSetRepo repo;
+
+ dynamic_cast<AllFields&>(*repo.parse(docRepo, "[all]"));
+ dynamic_cast<NoFields&>(*repo.parse(docRepo, "[none]"));
+ dynamic_cast<DocIdOnly&>(*repo.parse(docRepo, "[id]"));
+ dynamic_cast<HeaderFields&>(*repo.parse(docRepo, "[header]"));
+ dynamic_cast<BodyFields&>(*repo.parse(docRepo, "[body]"));
+
+ FieldSet::UP set = repo.parse(docRepo, "testdoctype1:headerval,content");
+ FieldCollection& coll = dynamic_cast<FieldCollection&>(*set);
+
+ std::ostringstream ost;
+ for (Field::Set::const_iterator iter = coll.getFields().begin();
+ iter != coll.getFields().end();
+ ++iter) {
+ ost << (*iter)->getName() << " ";
+ }
+
+ CPPUNIT_ASSERT_EQUAL(std::string("content headerval "), ost.str());
+}
+
+namespace {
+
+bool checkContains(FieldSetRepo& r, const DocumentTypeRepo& repo,
+ const std::string& str1, const std::string str2) {
+ FieldSet::UP set1 = r.parse(repo, str1);
+ FieldSet::UP set2 = r.parse(repo, str2);
+
+ return set1->contains(*set2);
+}
+
+bool checkError(FieldSetRepo& r, const DocumentTypeRepo& repo,
+ const std::string& str) {
+ try {
+ r.parse(repo, str);
+ return false;
+ } catch (...) {
+ return true;
+ }
+}
+
+}
+
+void FieldSetTest::testContains()
+{
+ TestDocMan testDocMan;
+ const DocumentTypeRepo& repo = testDocMan.getTypeRepo();
+ const DocumentType& type = *repo.getDocumentType("testdoctype1");
+
+ const Field& headerField = type.getField("headerval");
+ const Field& bodyField = type.getField("content");
+
+ NoFields none;
+ AllFields all;
+ DocIdOnly id;
+ HeaderFields h;
+ BodyFields b;
+
+ CPPUNIT_ASSERT_EQUAL(false, headerField.contains(
+ type.getField("headerlongval")));
+ CPPUNIT_ASSERT_EQUAL(true, headerField.contains(headerField));
+ CPPUNIT_ASSERT_EQUAL(true, headerField.contains(id));
+ CPPUNIT_ASSERT_EQUAL(false, headerField.contains(all));
+ CPPUNIT_ASSERT_EQUAL(true, headerField.contains(none));
+ CPPUNIT_ASSERT_EQUAL(false, none.contains(headerField));
+ CPPUNIT_ASSERT_EQUAL(true, all.contains(headerField));
+ CPPUNIT_ASSERT_EQUAL(true, all.contains(none));
+ CPPUNIT_ASSERT_EQUAL(false, none.contains(all));
+ CPPUNIT_ASSERT_EQUAL(true, all.contains(id));
+ CPPUNIT_ASSERT_EQUAL(false, none.contains(id));
+ CPPUNIT_ASSERT_EQUAL(true, id.contains(none));
+
+ CPPUNIT_ASSERT_EQUAL(true, h.contains(headerField));
+ CPPUNIT_ASSERT_EQUAL(false, h.contains(bodyField));
+
+ CPPUNIT_ASSERT_EQUAL(false, b.contains(headerField));
+ CPPUNIT_ASSERT_EQUAL(true, b.contains(bodyField));
+
+ FieldSetRepo r;
+ CPPUNIT_ASSERT_EQUAL(true, checkContains(r, repo, "[body]",
+ "testdoctype1:content"));
+ CPPUNIT_ASSERT_EQUAL(false, checkContains(r, repo, "[header]",
+ "testdoctype1:content"));
+ CPPUNIT_ASSERT_EQUAL(true, checkContains(r, repo,
+ "testdoctype1:content,headerval",
+ "testdoctype1:content"));
+ CPPUNIT_ASSERT_EQUAL(false, checkContains(r, repo,
+ "testdoctype1:content",
+ "testdoctype1:content,headerval"));
+ CPPUNIT_ASSERT_EQUAL(true, checkContains(r, repo,
+ "testdoctype1:headerval,content",
+ "testdoctype1:content,headerval"));
+
+ CPPUNIT_ASSERT(checkError(r, repo, "nodoctype"));
+ CPPUNIT_ASSERT(checkError(r, repo, "unknowndoctype:foo"));
+ CPPUNIT_ASSERT(checkError(r, repo, "testdoctype1:unknownfield"));
+ CPPUNIT_ASSERT(checkError(r, repo, "[badid]"));
+}
+
+std::string
+FieldSetTest::stringifyFields(const Document& doc) const
+{
+ std::vector<std::string> output;
+ const StructFieldValue& fields(doc.getFields());
+ for (StructFieldValue::const_iterator
+ it(fields.begin()), e(fields.end());
+ it != e; ++it)
+ {
+ std::ostringstream ss;
+ const Field& f(it.field());
+ ss << f.getName() << ": ";
+ FieldValue::UP val(fields.getValue(f));
+ if (val.get()) {
+ ss << val->toString();
+ } else {
+ ss << "(null)";
+ }
+ output.push_back(ss.str());
+ }
+ std::ostringstream ret;
+ std::sort(output.begin(), output.end());
+ std::copy(output.begin(), output.end(),
+ std::ostream_iterator<std::string>(ret, "\n"));
+ return ret.str();
+}
+
+std::string
+FieldSetTest::doCopyFields(const Document& src,
+ const DocumentTypeRepo& docRepo,
+ const std::string& fieldSetStr,
+ Document* dest) const
+{
+ Document destDoc(src.getType(), DocumentId("doc:test:fieldsdest"));
+ if (!dest) {
+ dest = &destDoc;
+ }
+ FieldSetRepo repo;
+ FieldSet::UP fset = repo.parse(docRepo, fieldSetStr);
+ FieldSet::copyFields(*dest, src, *fset);
+ return stringifyFields(*dest);
+}
+
+std::string
+FieldSetTest::doStripFields(const Document& doc,
+ const DocumentTypeRepo& docRepo,
+ const std::string& fieldSetStr)
+{
+ Document::UP copy(doc.clone());
+ FieldSetRepo repo;
+ FieldSet::UP fset = repo.parse(docRepo, fieldSetStr);
+ FieldSet::stripFields(*copy, *fset);
+ return stringifyFields(*copy);
+}
+
+Document::UP
+FieldSetTest::createTestDocument(const TestDocMan& testDocMan) const
+{
+ Document::UP doc(testDocMan.createDocument("megafoo megabar",
+ "doc:test:fieldssrc",
+ "testdoctype1"));
+ doc->setValue(doc->getField("headerval"), IntFieldValue(5678));
+ doc->setValue(doc->getField("hstringval"),
+ StringFieldValue("hello fantastic world"));
+ return doc;
+}
+
+void
+FieldSetTest::testCopyDocumentFields()
+{
+ TestDocMan testDocMan;
+ const DocumentTypeRepo& repo = testDocMan.getTypeRepo();
+ Document::UP src(createTestDocument(testDocMan));
+
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"),
+ doCopyFields(*src, repo, "[body]"));
+ CPPUNIT_ASSERT_EQUAL(std::string(""),
+ doCopyFields(*src, repo, "[none]"));
+ CPPUNIT_ASSERT_EQUAL(std::string("headerval: 5678\n"
+ "hstringval: hello fantastic world\n"),
+ doCopyFields(*src, repo, "[header]"));
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"
+ "headerval: 5678\n"
+ "hstringval: hello fantastic world\n"),
+ doCopyFields(*src, repo, "[all]"));
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"
+ "hstringval: hello fantastic world\n"),
+ doCopyFields(*src, repo, "testdoctype1:hstringval,content"));
+ // Test that we overwrite already set fields in destination document
+ {
+ Document dest(src->getType(), DocumentId("doc:foo:bar"));
+ dest.setValue(dest.getField("content"), StringFieldValue("overwriteme"));
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"),
+ doCopyFields(*src, repo, "[body]", &dest));
+ }
+}
+
+std::string
+FieldSetTest::doCopyDocument(const Document& src,
+ const DocumentTypeRepo& docRepo,
+ const std::string& fieldSetStr)
+{
+ FieldSetRepo repo;
+ FieldSet::UP fset = repo.parse(docRepo, fieldSetStr);
+ Document::UP doc(FieldSet::createDocumentSubsetCopy(src, *fset));
+ return stringifyFields(*doc);
+}
+
+
+void
+FieldSetTest::testDocumentSubsetCopy()
+{
+ TestDocMan testDocMan;
+ const DocumentTypeRepo& repo = testDocMan.getTypeRepo();
+ Document::UP src(createTestDocument(testDocMan));
+
+ {
+ Document::UP doc(FieldSet::createDocumentSubsetCopy(*src, AllFields()));
+ // Test that document id and type are copied correctly.
+ CPPUNIT_ASSERT(doc.get());
+ CPPUNIT_ASSERT_EQUAL(src->getId(), doc->getId());
+ CPPUNIT_ASSERT_EQUAL(src->getType(), doc->getType());
+ CPPUNIT_ASSERT_EQUAL(doCopyFields(*src, repo, "[all]"),
+ stringifyFields(*doc));
+ }
+
+ const char* fieldSets[] = {
+ "[all]",
+ "[none]",
+ "[header]",
+ "[body]",
+ "testdoctype1:hstringval,content"
+ };
+ for (size_t i = 0; i < sizeof(fieldSets) / sizeof(fieldSets[0]); ++i) {
+ CPPUNIT_ASSERT_EQUAL(doCopyFields(*src, repo, fieldSets[i]),
+ doCopyDocument(*src, repo, fieldSets[i]));
+ }
+}
+
+void
+FieldSetTest::testSerialize()
+{
+ TestDocMan testDocMan;
+ const DocumentTypeRepo& docRepo = testDocMan.getTypeRepo();
+
+ const char* fieldSets[] = {
+ "[all]",
+ "[none]",
+ "[header]",
+ "[docid]",
+ "[body]",
+ "testdoctype1:content",
+ "testdoctype1:content,hstringval"
+ };
+
+ FieldSetRepo repo;
+ for (size_t i = 0; i < sizeof(fieldSets) / sizeof(fieldSets[0]); ++i) {
+ FieldSet::UP fs = repo.parse(docRepo, fieldSets[i]);
+ CPPUNIT_ASSERT_EQUAL(vespalib::string(fieldSets[i]), repo.serialize(*fs));
+ }
+}
+
+void
+FieldSetTest::testStripFields()
+{
+ TestDocMan testDocMan;
+ const DocumentTypeRepo& repo = testDocMan.getTypeRepo();
+ Document::UP src(createTestDocument(testDocMan));
+
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"),
+ doStripFields(*src, repo, "[body]"));
+ CPPUNIT_ASSERT_EQUAL(std::string(""),
+ doStripFields(*src, repo, "[none]"));
+ CPPUNIT_ASSERT_EQUAL(std::string(""),
+ doStripFields(*src, repo, "[id]"));
+ CPPUNIT_ASSERT_EQUAL(std::string("headerval: 5678\n"
+ "hstringval: hello fantastic world\n"),
+ doStripFields(*src, repo, "[header]"));
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"
+ "headerval: 5678\n"
+ "hstringval: hello fantastic world\n"),
+ doStripFields(*src, repo, "[all]"));
+ CPPUNIT_ASSERT_EQUAL(std::string("content: megafoo megabar\n"
+ "hstringval: hello fantastic world\n"),
+ doStripFields(*src, repo, "testdoctype1:hstringval,content"));
+}
+
+} // document
diff --git a/document/src/tests/fieldtestcase.h b/document/src/tests/fieldtestcase.h
new file mode 100644
index 00000000000..0001b8fce1f
--- /dev/null
+++ b/document/src/tests/fieldtestcase.h
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class Field_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( Field_Test);
+ CPPUNIT_TEST(testserialize);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp();
+
+protected:
+ void testserialize();
+};
+
+
diff --git a/document/src/tests/fieldvalue/.gitignore b/document/src/tests/fieldvalue/.gitignore
new file mode 100644
index 00000000000..6b57b8c8d17
--- /dev/null
+++ b/document/src/tests/fieldvalue/.gitignore
@@ -0,0 +1,7 @@
+*.So
+*_test
+.depend
+Makefile
+document_document_test_app
+document_fieldvalue_test_app
+document_predicatefieldvalue_test_app
diff --git a/document/src/tests/fieldvalue/CMakeLists.txt b/document/src/tests/fieldvalue/CMakeLists.txt
new file mode 100644
index 00000000000..7b5fc637e4f
--- /dev/null
+++ b/document/src/tests/fieldvalue/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_document_test_app
+ SOURCES
+ document_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_document_test_app COMMAND document_document_test_app)
+vespa_add_executable(document_fieldvalue_test_app
+ SOURCES
+ fieldvalue_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_fieldvalue_test_app COMMAND document_fieldvalue_test_app)
+vespa_add_executable(document_predicatefieldvalue_test_app
+ SOURCES
+ predicatefieldvalue_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_predicatefieldvalue_test_app COMMAND document_predicatefieldvalue_test_app)
diff --git a/document/src/tests/fieldvalue/document_test.cpp b/document/src/tests/fieldvalue/document_test.cpp
new file mode 100644
index 00000000000..51216c6d77a
--- /dev/null
+++ b/document/src/tests/fieldvalue/document_test.cpp
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for document.
+
+#include <vespa/log/log.h>
+LOG_SETUP("document_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/base/documentid.h>
+#include <vespa/document/base/testdocrepo.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace document;
+
+namespace {
+
+TEST("require that document with id schema 'id' checks type") {
+ TestDocRepo repo;
+ const DataType *type = repo.getDocumentType("testdoctype1");
+ ASSERT_TRUE(type);
+
+ Document(*type, DocumentId("id:ns:testdoctype1::")); // Should not throw
+
+ EXPECT_EXCEPTION(Document(*type, DocumentId("id:ns:type::")),
+ vespalib::IllegalArgumentException,
+ "testdoctype1 that don't match the id (type type)");
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/fieldvalue/fieldvalue_test.cpp b/document/src/tests/fieldvalue/fieldvalue_test.cpp
new file mode 100644
index 00000000000..7fecb42417e
--- /dev/null
+++ b/document/src/tests/fieldvalue/fieldvalue_test.cpp
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for fieldvalue.
+
+#include <vespa/log/log.h>
+LOG_SETUP("fieldvalue_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/longfieldvalue.h>
+#include <vespa/document/fieldvalue/intfieldvalue.h>
+
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace document;
+
+namespace {
+
+TEST("require that StringFieldValue can be assigned primitives") {
+ StringFieldValue val;
+ val = "foo";
+ EXPECT_EQUAL("foo", val.getValue());
+ val = 1;
+ EXPECT_EQUAL("1", val.getValue());
+ val = static_cast<int64_t>(2);
+ EXPECT_EQUAL("2", val.getValue());
+ val = 3.0f;
+ EXPECT_EQUAL("3", val.getValue());
+ val = 4.0;
+ EXPECT_EQUAL("4", val.getValue());
+}
+
+TEST("require that FieldValues does not change their storage size.") {
+ EXPECT_EQUAL(8u, sizeof(FieldValue));
+ EXPECT_EQUAL(16u, sizeof(IntFieldValue));
+ EXPECT_EQUAL(24u, sizeof(LongFieldValue));
+ EXPECT_EQUAL(104u, sizeof(StringFieldValue));
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/fieldvalue/predicatefieldvalue_test.cpp b/document/src/tests/fieldvalue/predicatefieldvalue_test.cpp
new file mode 100644
index 00000000000..94b0f406696
--- /dev/null
+++ b/document/src/tests/fieldvalue/predicatefieldvalue_test.cpp
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for predicatefieldvalue.
+
+#include <vespa/log/log.h>
+LOG_SETUP("predicatefieldvalue_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/predicate/predicate.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/predicatefieldvalue.h>
+#include <vespa/document/predicate/predicate_slime_builder.h>
+#include <sstream>
+#include <string>
+
+using std::ostringstream;
+using std::string;
+using vespalib::Slime;
+using namespace document;
+
+namespace {
+
+void verifyEqual(const FieldValue & a, const FieldValue & b) {
+ ostringstream o1;
+ a.print(o1, false, "");
+ ostringstream o2;
+ b.print(o2, false, "");
+ ASSERT_EQUAL(o1.str(), o2.str());
+}
+
+TEST("require that PredicateFieldValue can be cloned, assigned, and operator=") {
+ PredicateSlimeBuilder builder;
+ builder.neg().feature("foo").value("bar").value("baz");
+ PredicateFieldValue val(builder.build());
+
+ FieldValue::UP val2(val.clone());
+ verifyEqual(val, *val2);
+
+ PredicateFieldValue assigned;
+ assigned.assign(val);
+ verifyEqual(val, assigned);
+
+ PredicateFieldValue operatorAssigned;
+ operatorAssigned = val;
+ verifyEqual(val, operatorAssigned);
+}
+
+TEST("require that PredicateFieldValue can be created from datatype") {
+ FieldValue::UP val = DataType::PREDICATE->createFieldValue();
+ ASSERT_TRUE(dynamic_cast<PredicateFieldValue *>(val.get()));
+}
+
+TEST("require that PredicateFieldValue can be cloned") {
+ PredicateSlimeBuilder builder;
+ builder.neg().feature("foo").value("bar").value("baz");
+ PredicateFieldValue val(builder.build());
+ FieldValue::UP val2(val.clone());
+ ostringstream o1;
+ val.print(o1, false, "");
+ ostringstream o2;
+ val2->print(o2, false, "");
+ ASSERT_EQUAL(o1.str(), o2.str());
+}
+
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/forcelinktest.cpp b/document/src/tests/forcelinktest.cpp
new file mode 100644
index 00000000000..c62f327585f
--- /dev/null
+++ b/document/src/tests/forcelinktest.cpp
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/base/forcelink.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace document {
+
+struct ForceLinkTest : public CppUnit::TestFixture {
+ void testUsage();
+
+ CPPUNIT_TEST_SUITE(ForceLinkTest);
+ CPPUNIT_TEST(testUsage);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ForceLinkTest);
+
+void ForceLinkTest::testUsage()
+{
+ ForceLink link;
+}
+
+} // document
diff --git a/document/src/tests/gid_filter_test.cpp b/document/src/tests/gid_filter_test.cpp
new file mode 100644
index 00000000000..9caf55f6a0b
--- /dev/null
+++ b/document/src/tests/gid_filter_test.cpp
@@ -0,0 +1,317 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2016 Yahoo! Technologies Norway AS
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <vespa/document/select/gid_filter.h>
+#include <vespa/document/select/parser.h>
+#include <vespa/document/base/documentid.h>
+#include <vespa/document/base/testdocrepo.h>
+
+namespace document {
+namespace select {
+
+class GidFilterTest : public CppUnit::TestFixture {
+ struct Fixture {
+ TestDocRepo _repo;
+ BucketIdFactory _id_factory;
+ std::unique_ptr<Node> _root;
+
+ Fixture(vespalib::stringref selection)
+ : _repo(),
+ _id_factory(),
+ _root(Parser(_repo.getTypeRepo(), _id_factory).parse(selection))
+ {
+ }
+
+ Fixture(Fixture&&) = default;
+ Fixture& operator=(Fixture&&) = default;
+
+ static Fixture for_selection(vespalib::stringref s) {
+ return Fixture(s);
+ }
+ };
+
+ static GlobalId id_to_gid(vespalib::stringref id_string) {
+ return DocumentId(id_string).getGlobalId();
+ }
+
+ bool might_match(vespalib::stringref selection,
+ vespalib::stringref id_string) const
+ {
+ Fixture f(selection);
+ auto filter = GidFilter::for_selection_root_node(*f._root);
+ return filter.gid_might_match_selection(id_to_gid(id_string));
+ }
+
+ CPPUNIT_TEST_SUITE(GidFilterTest);
+ CPPUNIT_TEST(same_user_for_selection_and_gid_returns_match);
+ CPPUNIT_TEST(differing_user_for_selection_and_gid_returns_mismatch);
+ CPPUNIT_TEST(user_location_constraint_is_order_invariant);
+ CPPUNIT_TEST(non_location_selection_always_matches);
+ CPPUNIT_TEST(location_selection_does_not_match_non_location_id);
+ CPPUNIT_TEST(simple_conjunctive_location_expressions_are_filtered);
+ CPPUNIT_TEST(complex_conjunctive_location_expressions_are_filtered);
+ CPPUNIT_TEST(simple_disjunctive_location_expressions_are_not_filtered);
+ CPPUNIT_TEST(complex_disjunctive_location_expressions_are_not_filtered);
+ CPPUNIT_TEST(non_location_id_comparisons_are_not_filtered);
+ CPPUNIT_TEST(unsupported_location_comparison_operands_not_filtered);
+ CPPUNIT_TEST(default_constructed_filter_always_matches);
+ CPPUNIT_TEST(most_significant_32_bits_are_ignored);
+ CPPUNIT_TEST(gid_filters_may_be_copy_constructed);
+ CPPUNIT_TEST(gid_filters_may_be_copy_assigned);
+ CPPUNIT_TEST(same_group_for_selection_and_gid_returns_match);
+ CPPUNIT_TEST(differing_group_for_selection_and_gid_returns_mismatch);
+ CPPUNIT_TEST(composite_user_comparison_sub_expressions_not_supported);
+ CPPUNIT_TEST(composite_group_comparison_sub_expressions_not_supported);
+ CPPUNIT_TEST_SUITE_END();
+public:
+ void same_user_for_selection_and_gid_returns_match();
+ void differing_user_for_selection_and_gid_returns_mismatch();
+ void user_location_constraint_is_order_invariant();
+ void non_location_selection_always_matches();
+ void location_selection_does_not_match_non_location_id();
+ void simple_conjunctive_location_expressions_are_filtered();
+ void complex_conjunctive_location_expressions_are_filtered();
+ void simple_disjunctive_location_expressions_are_not_filtered();
+ void complex_disjunctive_location_expressions_are_not_filtered();
+ void non_location_id_comparisons_are_not_filtered();
+ void unsupported_location_comparison_operands_not_filtered();
+ void default_constructed_filter_always_matches();
+ void most_significant_32_bits_are_ignored();
+ void gid_filters_may_be_copy_constructed();
+ void gid_filters_may_be_copy_assigned();
+ void same_group_for_selection_and_gid_returns_match();
+ void differing_group_for_selection_and_gid_returns_mismatch();
+ void composite_user_comparison_sub_expressions_not_supported();
+ void composite_group_comparison_sub_expressions_not_supported();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GidFilterTest);
+
+void
+GidFilterTest::same_user_for_selection_and_gid_returns_match()
+{
+ CPPUNIT_ASSERT(might_match("id.user == 12345",
+ "id::testdoctype1:n=12345:foo"));
+ // User locations are defined over [0, 2**63-1]
+ CPPUNIT_ASSERT(might_match("id.user == 0",
+ "id::testdoctype1:n=0:foo"));
+ CPPUNIT_ASSERT(might_match("id.user == 9223372036854775807" ,
+ "id::testdoctype1:n=9223372036854775807:foo"));
+}
+
+void
+GidFilterTest::differing_user_for_selection_and_gid_returns_mismatch()
+{
+ CPPUNIT_ASSERT(!might_match("id.user == 1", "id::testdoctype1:n=2000:foo"));
+ // Similar, but non-identical, bit patterns
+ CPPUNIT_ASSERT(!might_match("id.user == 12345",
+ "id::testdoctype1:n=12346:foo"));
+ CPPUNIT_ASSERT(!might_match("id.user == 12345",
+ "id::testdoctype1:n=12344:foo"));
+}
+
+void
+GidFilterTest::user_location_constraint_is_order_invariant()
+{
+ CPPUNIT_ASSERT(might_match("12345 == id.user",
+ "id::testdoctype1:n=12345:foo"));
+ CPPUNIT_ASSERT(!might_match("12345 == id.user",
+ "id::testdoctype1:n=12346:foo"));
+}
+
+void
+GidFilterTest::non_location_selection_always_matches()
+{
+ CPPUNIT_ASSERT(might_match("testdoctype1.headerval == 67890",
+ "id::testdoctype1:n=12345:foo"));
+}
+
+void
+GidFilterTest::location_selection_does_not_match_non_location_id()
+{
+ // Test name is a half-truth; the MD5-derived ID _will_ give a false
+ // positive every 2**32 or so document ID when the stars and their bit
+ // patterns align :)
+ CPPUNIT_ASSERT(!might_match("id.user == 987654321",
+ "id::testdoctype1::foo"));
+
+ CPPUNIT_ASSERT(!might_match("id.group == 'snusmumrikk'",
+ "id::testdoctype1::foo"));
+}
+
+void
+GidFilterTest::simple_conjunctive_location_expressions_are_filtered()
+{
+ // A conjunctive expression in this context is one where there exist a
+ // location predicate and the result of the entire expression can only
+ // be true iff the location predicate matches.
+ CPPUNIT_ASSERT(might_match("id.user == 12345 and true",
+ "id::testdoctype1:n=12345:bar"));
+ CPPUNIT_ASSERT(might_match("true and id.user == 12345",
+ "id::testdoctype1:n=12345:bar"));
+
+ CPPUNIT_ASSERT(!might_match("id.user == 123456 and true",
+ "id::testdoctype1:n=12345:bar"));
+ CPPUNIT_ASSERT(!might_match("true and id.user == 123456",
+ "id::testdoctype1:n=12345:bar"));
+}
+
+void
+GidFilterTest::complex_conjunctive_location_expressions_are_filtered()
+{
+ CPPUNIT_ASSERT(might_match("(((testdoctype1.headerval < 5) and (1 != 2)) "
+ "and id.user == 12345)",
+ "id::testdoctype1:n=12345:bar"));
+ CPPUNIT_ASSERT(!might_match("(((1 != 2) and (id.user==12345)) and "
+ "(2 != 3)) and (testdoctype1.headerval < 5)",
+ "id::testdoctype1:n=23456:bar"));
+ // In this case the expression contains a disjunction but the outcome
+ // of evaluating it still strongly depends on the location predicate.
+ CPPUNIT_ASSERT(might_match("((id.user == 12345 and true) and "
+ "(true or false))",
+ "id::testdoctype1:n=12345:bar"));
+ CPPUNIT_ASSERT(!might_match("((id.user == 12345 and true) and "
+ "(true or false))",
+ "id::testdoctype1:n=23456:bar"));
+}
+
+void
+GidFilterTest::simple_disjunctive_location_expressions_are_not_filtered()
+{
+ // Documents mismatch location but match selection as a whole.
+ CPPUNIT_ASSERT(might_match("id.user == 12345 or true",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("true or id.user == 12345",
+ "id::testdoctype1:n=12345678:bar"));
+}
+
+void
+GidFilterTest::complex_disjunctive_location_expressions_are_not_filtered()
+{
+ CPPUNIT_ASSERT(might_match("((id.user == 12345) and true) or false",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("((id.user == 12345) or false) and true",
+ "id::testdoctype1:n=12345678:bar"));
+}
+
+void
+GidFilterTest::non_location_id_comparisons_are_not_filtered()
+{
+ // Note: these selections are syntactically valid but semantically
+ // invalid (comparing strings to integers), but are used to catch any
+ // logic holes where an id node is indiscriminately treated as something
+ // from which we should derive a GID-related integer.
+ CPPUNIT_ASSERT(might_match("id.namespace == 123456",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("id.type == 1234",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("id.scheme == 555",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("id.specific == 7654",
+ "id::testdoctype1:n=12345678:bar"));
+}
+
+void
+GidFilterTest::unsupported_location_comparison_operands_not_filtered()
+{
+ CPPUNIT_ASSERT(might_match("id.user == 'rick & morty'",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("id.group == 56789",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("id.user == testdoctype1.headervalue",
+ "id::testdoctype1:n=12345678:bar"));
+ CPPUNIT_ASSERT(might_match("id.group == testdoctype1.headervalue",
+ "id::testdoctype1:g=helloworld:bar"));
+}
+
+void
+GidFilterTest::default_constructed_filter_always_matches()
+{
+ GidFilter filter;
+ CPPUNIT_ASSERT(filter.gid_might_match_selection(
+ DocumentId("id::testdoctype1:n=12345678:bar").getGlobalId()));
+ CPPUNIT_ASSERT(filter.gid_might_match_selection(
+ DocumentId("id::testdoctype1::foo").getGlobalId()));
+}
+
+void
+GidFilterTest::most_significant_32_bits_are_ignored()
+{
+ // The fact that the 32 MSB are effectively ignored is an artifact of
+ // how the GID location extraction is historically performed and is not
+ // necessarily the optimum (in particular, an XOR combination of the upper
+ // and lower 32 bits would likely be much better), but it's what the
+ // behavior currently is and should thus be tested.
+
+ // The following locations have the same 32 LSB:
+ CPPUNIT_ASSERT(might_match("id.user == 12345678901",
+ "id::testdoctype1:n=29525548085:bar"));
+}
+
+void
+GidFilterTest::gid_filters_may_be_copy_constructed()
+{
+ Fixture f("id.user == 1337");
+ GidFilter filter = GidFilter::for_selection_root_node(*f._root);
+
+ GidFilter copy_constructed(filter);
+ CPPUNIT_ASSERT(copy_constructed.gid_might_match_selection(
+ id_to_gid("id::testdoctype1:n=1337:zoid")));
+ CPPUNIT_ASSERT(!copy_constructed.gid_might_match_selection(
+ id_to_gid("id::testdoctype1:n=555:zoid")));
+
+}
+
+void
+GidFilterTest::gid_filters_may_be_copy_assigned()
+{
+ Fixture f("id.user == 1337");
+ GidFilter filter = GidFilter::for_selection_root_node(*f._root);
+
+ GidFilter copy_assigned;
+ copy_assigned = filter;
+
+ CPPUNIT_ASSERT(copy_assigned.gid_might_match_selection(
+ id_to_gid("id::testdoctype1:n=1337:zoid")));
+ CPPUNIT_ASSERT(!copy_assigned.gid_might_match_selection(
+ id_to_gid("id::testdoctype1:n=555:zoid")));
+}
+
+void
+GidFilterTest::same_group_for_selection_and_gid_returns_match()
+{
+ CPPUNIT_ASSERT(might_match("id.group == 'bjarne'",
+ "id::testdoctype1:g=bjarne:foo"));
+ CPPUNIT_ASSERT(might_match("id.group == 'andrei'",
+ "id::testdoctype1:g=andrei:bar"));
+}
+
+void
+GidFilterTest::differing_group_for_selection_and_gid_returns_mismatch()
+{
+ CPPUNIT_ASSERT(!might_match("id.group == 'cult of bjarne'",
+ "id::testdoctype1:g=stl:foo"));
+ CPPUNIT_ASSERT(!might_match("id.group == 'sutters mill'",
+ "id::testdoctype1:g=andrei:bar"));
+}
+
+void
+GidFilterTest::composite_user_comparison_sub_expressions_not_supported()
+{
+ // Technically this is a mismatch, but we currently only want to support
+ // the simple, obvious cases since this is not an expected use case.
+ CPPUNIT_ASSERT(might_match("id.user == (1 + 2)",
+ "id::testdoctype1:n=20:foo"));
+}
+
+void
+GidFilterTest::composite_group_comparison_sub_expressions_not_supported()
+{
+ CPPUNIT_ASSERT(might_match("id.group == 'foo'+'bar'",
+ "id::testdoctype1:g=sputnik_hits:foo"));
+}
+
+} // select
+} // document
diff --git a/document/src/tests/globalidtest.cpp b/document/src/tests/globalidtest.cpp
new file mode 100644
index 00000000000..d0d066e5510
--- /dev/null
+++ b/document/src/tests/globalidtest.cpp
@@ -0,0 +1,268 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <vespa/document/bucket/bucketidfactory.h>
+#include <vespa/document/base/documentid.h>
+#include <vespa/document/base/globalid.h>
+#include <vespa/vespalib/util/random.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+
+namespace document {
+
+struct GlobalIdTest : public CppUnit::TestFixture {
+ void testNormalUsage();
+ void testBucketIdConversion();
+ void testGidRangeConversion();
+ void testBucketOrderCmp();
+
+ void verifyGlobalIdRange(const std::vector<DocumentId>& ids,
+ uint32_t countBits);
+
+ CPPUNIT_TEST_SUITE(GlobalIdTest);
+ CPPUNIT_TEST(testNormalUsage);
+ CPPUNIT_TEST(testBucketIdConversion);
+ CPPUNIT_TEST(testGidRangeConversion);
+ CPPUNIT_TEST(testBucketOrderCmp);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GlobalIdTest);
+
+void
+GlobalIdTest::testNormalUsage()
+{
+ const char* emptystring = "\0\0\0\0\0\0\0\0\0\0\0\0";
+ const char* teststring = "1234567890ABCDEF";
+ CPPUNIT_ASSERT(strlen(teststring) > GlobalId::LENGTH);
+
+ { // Test empty global id
+ GlobalId id;
+ for (uint32_t i=0; i<GlobalId::LENGTH; ++i) {
+ CPPUNIT_ASSERT_EQUAL((unsigned char) '\0', id.get()[i]);
+ }
+ GlobalId id2(emptystring);
+ CPPUNIT_ASSERT_EQUAL(id, id2);
+ CPPUNIT_ASSERT(!(id != id2));
+ CPPUNIT_ASSERT(!(id < id2) && !(id2 < id));
+ CPPUNIT_ASSERT_EQUAL(std::string(emptystring, GlobalId::LENGTH),
+ std::string((const char*) id.get(),
+ GlobalId::LENGTH));
+ }
+ { // Test non-empty global id
+ GlobalId empty;
+ GlobalId initialempty;
+ initialempty.set(teststring);
+ GlobalId id(teststring);
+ CPPUNIT_ASSERT_EQUAL(id, initialempty);
+ CPPUNIT_ASSERT(!(id != initialempty));
+ CPPUNIT_ASSERT(!(id < initialempty) && !(initialempty < id));
+
+ CPPUNIT_ASSERT(id != empty);
+ CPPUNIT_ASSERT(!(id == empty));
+ CPPUNIT_ASSERT(!(id < empty) && (empty < id));
+
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(teststring, GlobalId::LENGTH),
+ std::string((const char*) id.get(), GlobalId::LENGTH));
+ CPPUNIT_ASSERT_EQUAL(std::string(teststring, GlobalId::LENGTH),
+ std::string((const char*) initialempty.get(),
+ GlobalId::LENGTH));
+ }
+ { // Test printing and parsing
+ GlobalId id1("LIN!#LNKASD#!MYL#&NK");
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("gid(0x4c494e21234c4e4b41534423)"),
+ id1.toString());
+ GlobalId id2 = GlobalId::parse(id1.toString());
+ CPPUNIT_ASSERT_EQUAL(id1, id2);
+ // Verify string representation too, to verify that operator== works
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("gid(0x4c494e21234c4e4b41534423)"),
+ id2.toString());
+ }
+}
+
+namespace {
+ void verifyDocumentId(const std::string& s) {
+ document::DocumentId did(s);
+ BucketIdFactory factory;
+ BucketId bid = factory.getBucketId(did);
+ GlobalId gid = did.getGlobalId();
+ BucketId generated = gid.convertToBucketId();
+ //std::cerr << bid << ", " << generated << "\n";
+ if (bid != generated) {
+ std::ostringstream ost;
+ ost << "Document id " << s << " with gid " << gid
+ << " belongs to bucket " << bid
+ << ", but globalid convert function generated bucketid "
+ << generated;
+ CPPUNIT_FAIL(ost.str());
+ }
+ }
+}
+
+void
+GlobalIdTest::testBucketIdConversion()
+{
+ verifyDocumentId("userdoc:ns:1:abc");
+ verifyDocumentId("userdoc:ns:1000:abc");
+ verifyDocumentId("userdoc:hsgf:18446744073700000000:dfdfsdfg");
+ verifyDocumentId("groupdoc:ns:somegroup:hmm");
+ verifyDocumentId("doc::test");
+ verifyDocumentId("doc:myns:http://foo.bar");
+ verifyDocumentId("doc:jsrthsdf:a234aleingzldkifvasdfgadf");
+}
+
+void
+GlobalIdTest::verifyGlobalIdRange(const std::vector<DocumentId>& ids,
+ uint32_t countBits)
+{
+ BucketIdFactory factory;
+ for (uint32_t i=0, n=ids.size(); i<n; ++i) {
+ // Create the bucket this document would be in with given
+ // countbits
+ BucketId bucket(factory.getBucketId(ids[i]));
+ bucket.setUsedBits(countBits);
+ bucket = bucket.stripUnused();
+ // Get the min and max GIDs for this bucket
+ GlobalId first = GlobalId::calculateFirstInBucket(bucket);
+ GlobalId last = GlobalId::calculateLastInBucket(bucket);
+ // For each document in set, verify that they are within
+ // limits if they are contained in bucket.
+ for (uint32_t j=0; j<n; ++j) {
+ BucketId bid(factory.getBucketId(ids[j]));
+ GlobalId gid(ids[j].getGlobalId());
+ uint64_t gidKey = gid.convertToBucketId().toKey();
+ if (bucket.contains(bid)) {
+ if ((gidKey < last.convertToBucketId().toKey()) || (gidKey > last.convertToBucketId().toKey())) {
+ std::ostringstream msg;
+ msg << gid << " should be in the range " << first
+ << " - " << last;
+ msg << ", as bucket " << gid.convertToBucketId()
+ << " should be in the range "
+ << first.convertToBucketId() << " - "
+ << last.convertToBucketId() << "\n";
+ msg << ", reverted " << std::hex
+ << gid.convertToBucketId().toKey()
+ << " should be in the range "
+ << first.convertToBucketId().toKey() << " - "
+ << last.convertToBucketId().toKey() << "\n";
+ CPPUNIT_ASSERT_MESSAGE(msg.str(),
+ gid.convertToBucketId().toKey() >=
+ first.convertToBucketId().toKey());
+ CPPUNIT_ASSERT_MESSAGE(msg.str(),
+ gid.convertToBucketId().toKey() <=
+ last.convertToBucketId().toKey());
+ }
+ } else {
+ if ((gidKey >= first.convertToBucketId().toKey()) && (gidKey <= last.convertToBucketId().toKey())) {
+ std::ostringstream msg;
+ msg << gid << " should not be in the range " << first
+ << " - " << last;
+ CPPUNIT_ASSERT_MESSAGE(msg.str(),
+ (gid.convertToBucketId().toKey() <
+ first.convertToBucketId().toKey())
+ ||
+ (gid.convertToBucketId().toKey() >
+ last.convertToBucketId().toKey()));
+ }
+ }
+ }
+ }
+}
+
+void
+GlobalIdTest::testGidRangeConversion()
+{
+ // Generate a lot of random document ids used for test
+ std::vector<DocumentId> docIds;
+ vespalib::RandomGen randomizer(0xdeadbabe);
+ for (uint32_t j=0; j<100; ++j) {
+ vespalib::asciistream name_space;
+ vespalib::asciistream ost;
+ for (uint32_t i=0, n=randomizer.nextUint32(1, 5); i<n; ++i) {
+ name_space << (char) ('a' + randomizer.nextUint32(0, 25));
+ }
+ uint32_t scheme = randomizer.nextUint32(0, 2);
+ switch (scheme) {
+ case 0: ost << "doc:" << name_space.str() << ":";
+ break;
+ case 1: ost << "userdoc:" << name_space.str() << ":";
+ ost << randomizer.nextUint32() << ":";
+ break;
+ case 2: ost << "groupdoc:" << name_space.str() << ":";
+ for (uint32_t i=0, n=randomizer.nextUint32(1, 10); i<n; ++i)
+ {
+ ost << (char) ('a' + randomizer.nextUint32(0, 25));
+ }
+ ost << ":";
+ break;
+ default: CPPUNIT_ASSERT(false);
+ }
+ ost << "http://";
+ for (uint32_t i=0, n=randomizer.nextUint32(1, 20); i<n; ++i) {
+ ost << (char) ('a' + randomizer.nextUint32(0, 25));
+ }
+ docIds.push_back(DocumentId(ost.str()));
+ }
+ //std::cerr << "\nDoing " << ((58 - 16) * docIds.size() * docIds.size())
+ // << " tests for whether global id calculation is correct.\n";
+ for (uint32_t i=1; i<=58; ++i) {
+ //std::cerr << "Verifying with " << i << " countbits\n";
+ verifyGlobalIdRange(docIds, i);
+ }
+}
+
+void
+GlobalIdTest::testBucketOrderCmp()
+{
+ typedef GlobalId::BucketOrderCmp C;
+ CPPUNIT_ASSERT(C::compareRaw(0, 0) == 0);
+ CPPUNIT_ASSERT(C::compareRaw(0, 1) == -1);
+ CPPUNIT_ASSERT(C::compareRaw(1, 0) == 1);
+ CPPUNIT_ASSERT(C::compareRaw(255, 255) == 0);
+ CPPUNIT_ASSERT(C::compareRaw(0, 255) == -255);
+ CPPUNIT_ASSERT(C::compareRaw(255, 0) == 255);
+ CPPUNIT_ASSERT(C::compareRaw(254, 254) == 0);
+ CPPUNIT_ASSERT(C::compareRaw(254, 255) == -1);
+ CPPUNIT_ASSERT(C::compareRaw(255, 254) == 1);
+ {
+ // Test raw comparator object.
+ GlobalId foo = GlobalId::parse("gid(0x000001103330333077700000)");
+ GlobalId bar = GlobalId::parse("gid(0x000000100030003000700000)");
+ GlobalId baz = GlobalId::parse("gid(0x000000103330333000700000)");
+
+ GlobalId::BucketOrderCmp cmp;
+ CPPUNIT_ASSERT(!cmp(foo, foo));
+ CPPUNIT_ASSERT(!cmp(bar, bar));
+ CPPUNIT_ASSERT(!cmp(baz, baz));
+ CPPUNIT_ASSERT(!cmp(foo, bar));
+ CPPUNIT_ASSERT( cmp(bar, foo));
+ CPPUNIT_ASSERT(!cmp(foo, baz));
+ CPPUNIT_ASSERT( cmp(baz, foo));
+ CPPUNIT_ASSERT(!cmp(baz, bar));
+ CPPUNIT_ASSERT( cmp(bar, baz));
+ }
+ {
+ // Test sorting by bucket.
+ GlobalId foo = GlobalId::parse("gid(0x000001103330333077700000)");
+ GlobalId bar = GlobalId::parse("gid(0x000000100030003000700000)");
+ GlobalId baz = GlobalId::parse("gid(0x000000103330333000700000)");
+
+ typedef std::map<GlobalId, uint32_t, GlobalId::BucketOrderCmp> GidMap;
+ GidMap gidMap;
+ gidMap[foo] = 666;
+ gidMap[bar] = 777;
+ gidMap[baz] = 888;
+
+ GidMap::iterator it = gidMap.begin();
+ CPPUNIT_ASSERT(it->first == bar);
+ CPPUNIT_ASSERT(it->second == 777);
+ ++it;
+ CPPUNIT_ASSERT(it->first == baz);
+ CPPUNIT_ASSERT(it->second == 888);
+ ++it;
+ CPPUNIT_ASSERT(it->first == foo);
+ CPPUNIT_ASSERT(it->second == 666);
+ }
+}
+
+} // document
diff --git a/document/src/tests/heapdebugger.h b/document/src/tests/heapdebugger.h
new file mode 100644
index 00000000000..deb862fbec3
--- /dev/null
+++ b/document/src/tests/heapdebugger.h
@@ -0,0 +1,68 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @file heapdebugger.h
+ *
+ * @author Ove Martin Malm
+ * @date Creation date: 2000-08-09
+ * @version $Id$
+ * @file mcheckhooks.h
+ *
+ * Copyright (c) : 1997-2000 Fast Search & Transfer ASA
+ * ALL RIGHTS RESERVED
+ */
+
+#pragma once
+#include <cstddef>
+
+
+/**
+ * This function is used for counting memory usage. Must be called before any block is allocated
+ * (pt. linux only)
+ * Parameter controls operation
+ */
+extern void enableHeapUsageMonitor(int param = 0);
+
+
+/**
+ * This function return the current memory used.
+ * @return Net number of bytes allocated on the heap
+ */
+extern size_t getHeapUsage(void);
+
+/**
+ * This enables heap debugging. Must be called before any block is allocated
+ * Parameter controls operation
+ * (pt. linux only)
+ */
+extern void enableHeapCorruptCheck(int param = 0);
+
+#define HEAPCHECKMODE_REMOVE -1 // Deinstall
+#define HEAPCHECKMODE_NORMAL 0 // Normal
+#define HEAPCHECKMODE_EXTENSIVE 1 // All allocated blocks checked on all ops..
+#define HEAPCHECKMODE_DISABLED 2 // No checking (but extra bytes allocated)
+
+
+
+/**
+ * Run a heap check now. Will lock the heap and run through a full check. Will crash if it fails...
+ */
+extern void checkHeapNow(void);
+
+
+/**
+ * And this enables linux mcheck function with an approprate callback function. (absolutely linux only)
+ * see man mcheck
+ */
+extern void enableMCheck(void);
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/document/src/tests/heapdebuggerlinux.cpp b/document/src/tests/heapdebuggerlinux.cpp
new file mode 100644
index 00000000000..5afe583c5e1
--- /dev/null
+++ b/document/src/tests/heapdebuggerlinux.cpp
@@ -0,0 +1,708 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ *
+ * @author Ove Martin Malm
+ * @date Creation date: 2000-21-08
+ * @version $Id$
+ *
+ * Copyright (c) : 1997-2000 Fast Search & Transfer ASA
+ * ALL RIGHTS RESERVED
+ */
+
+
+// These are necessary to make it compile....
+#define _MALLOC_INTERNAL
+#define MALLOC_HOOKS
+#include <vespa/fastos/fastos.h>
+#include <stdio.h>
+#define _LIBC
+#include <malloc.h>
+#include <mcheck.h>
+#include <stdint.h>
+#include <libintl.h>
+
+#include <iostream>
+#include <pthread.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "heapdebugger.h"
+
+// Predeclaration
+static void HeapCorrupt(const char * caller, const struct elemheader *hdr, enum mcheck_status err);
+
+// -------------------------------
+// Part #1 Memory usage
+
+
+#ifndef INTERNAL_SIZE_T
+#define INTERNAL_SIZE_T size_t
+#endif
+
+#define SIZE_SZ (sizeof(INTERNAL_SIZE_T))
+
+/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
+
+#define PREV_INUSE 0x1
+
+/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
+
+#define IS_MMAPPED 0x2
+
+/* Bits to mask off when extracting size */
+
+#define SIZE_BITS (PREV_INUSE|IS_MMAPPED)
+
+struct malloc_chunk
+{
+ INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
+ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
+ struct malloc_chunk* fd; /* double links -- used only if free. */
+ struct malloc_chunk* bk;
+};
+
+typedef struct malloc_chunk* mchunkptr;
+
+/* conversion from malloc headers to user pointers, and back */
+
+#define chunk2mem(p) ((Void_t*)((char*)(p) + 2*SIZE_SZ))
+#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))
+
+/* Get size, ignoring use bits */
+
+#define chunkSize(p) ((p)->size & ~(SIZE_BITS))
+
+
+static void (*old_free_hook_memusage)(void* buf, const void * caller );
+static void* (*old_malloc_hook_memusage)(size_t bytes, const void * caller);
+static void* (*old_realloc_hook_memusage)(void *buf, size_t bytes, const void *caller);
+static void* (*old_memalign_hook_memusage)(size_t alignment, size_t bytes,const void *caller);
+static size_t MemoryUsage = 0;
+static pthread_mutex_t memUsageMutex = PTHREAD_MUTEX_INITIALIZER;
+static bool MemUsageHookInstalled = false;
+
+
+static void me_use_free_hook(void *ptr, const void *caller)
+{
+ mchunkptr p;
+
+ pthread_mutex_lock(&memUsageMutex);
+
+ if (ptr) {
+ p = mem2chunk(ptr);
+ MemoryUsage -= chunkSize(p);
+ }
+
+ __free_hook = old_free_hook_memusage;
+
+ if (old_free_hook_memusage != NULL) {
+ (*old_free_hook_memusage) (ptr, caller);
+ } else {
+ free (ptr);
+ }
+
+ __free_hook = me_use_free_hook;
+
+ pthread_mutex_unlock(&memUsageMutex);
+}
+
+static void* me_use_malloc_hook(size_t size, const void *caller)
+{
+ void *anew;
+ mchunkptr p;
+
+ pthread_mutex_lock(&memUsageMutex);
+
+ __malloc_hook = old_malloc_hook_memusage;
+
+ if (old_malloc_hook_memusage != NULL) {
+ anew = (*old_malloc_hook_memusage) (size, caller);
+ } else {
+ anew = malloc (size);
+ }
+
+ if (anew) {
+ p = mem2chunk(anew);
+ MemoryUsage += chunkSize(p);
+ }
+
+ __malloc_hook = me_use_malloc_hook;
+ pthread_mutex_unlock(&memUsageMutex);
+
+ return(anew);
+}
+
+static void* me_use_realloc_hook(void *ptr, size_t size, const void* caller)
+{
+ void *anew;
+ mchunkptr p;
+ size_t oldsz = 0;
+ size_t newsz = 0;
+
+ pthread_mutex_lock(&memUsageMutex);
+
+ if (ptr) {
+ p = mem2chunk(ptr);
+ oldsz = chunkSize(p);
+ }
+
+ __free_hook = old_free_hook_memusage;
+ __malloc_hook = old_malloc_hook_memusage;
+ __realloc_hook = old_realloc_hook_memusage;
+
+ if (old_realloc_hook_memusage != NULL) {
+ anew = (*old_realloc_hook_memusage) (ptr, size, caller);
+ } else {
+ anew = realloc (ptr, size);
+ }
+
+ if (anew) {
+ p = mem2chunk(anew);
+ newsz = chunkSize(p);
+
+ MemoryUsage += (newsz - oldsz);
+ }
+
+ __free_hook = me_use_free_hook;
+ __malloc_hook = me_use_malloc_hook;
+ __realloc_hook = me_use_realloc_hook;
+
+ pthread_mutex_unlock(&memUsageMutex);
+
+ return anew;
+}
+
+static void* me_use_memalign_hook(size_t alignment, size_t bytes,const void *caller)
+{
+
+ void *aligned;
+
+ std::cout << "memalign" << std::endl;
+ __memalign_hook = old_memalign_hook_memusage;
+
+ if (old_memalign_hook_memusage != NULL) {
+ aligned = (*old_memalign_hook_memusage) (alignment, bytes,caller);
+ } else {
+ aligned = memalign (alignment, bytes);
+ }
+
+ __memalign_hook = me_use_memalign_hook;
+
+ return aligned;
+}
+
+
+void enableHeapUsageMonitor(int param)
+{
+ (void)param;
+ if (!MemUsageHookInstalled) {
+ old_free_hook_memusage = __free_hook;
+ __free_hook = me_use_free_hook;
+ old_malloc_hook_memusage = __malloc_hook;
+ __malloc_hook = me_use_malloc_hook;
+ old_realloc_hook_memusage = __realloc_hook;
+ __realloc_hook = me_use_realloc_hook;
+ MemUsageHookInstalled = true;
+ std::cout << "Memory usage will be counted" << std::endl;
+ }
+}
+
+size_t getHeapUsage(void)
+{
+ return(MemoryUsage);
+}
+
+
+// + Get status message....
+
+
+
+
+// -------------------
+// Part 2: Heap corrupt
+
+static void (*old_free_hook_memcorrupt)(void*, const void *);
+static void* (*old_malloc_hook_memcorrupt)(size_t, const void *);
+static void* (*old_realloc_hook_memcorrupt)(void*, size_t, const void *);
+static void* (*old_memalign_hook_memcorrupt)(size_t alignment, size_t bytes,const void *caller);
+
+static pthread_mutex_t HeapCorruptCheckMutex = PTHREAD_MUTEX_INITIALIZER;
+
+static int HeapCorruptCheckMode = HEAPCHECKMODE_REMOVE;
+static bool PrintUsage = false;
+
+
+/* Arbitrary magical numbers. */
+#define MAGICWORD 0xfedabeeb
+#define MAGICFREE 0xf1eef1ee
+#define MAGICBYTE ((char) 0x01) // "End byte"
+#define MALLOCFLOOD ((char) 0x02) // "Fresh (unused) memory"
+#define FREEFLOOD ((char) 0x03) // "Dead" memory
+
+struct elemheader {
+ __malloc_size_t size; /* Exact size requested by user. */
+ unsigned long int magic; /* Magic number to check header integrity. */
+ struct elemheader *prev;
+ struct elemheader *next;
+};
+
+/* This is the beginning of the list of all memory blocks allocated.
+ It is only constructed if the pedantic testing is requested. */
+
+static struct elemheader *root;
+
+static enum mcheck_status me_corrupt_mprobe (const struct elemheader * block)
+{
+ enum mcheck_status status;
+ switch (block->magic ^ ((uintptr_t) block->prev + (uintptr_t) block->next)) {
+ case MAGICFREE:
+ status = MCHECK_FREE;
+ break;
+ case MAGICWORD:
+ {
+ const char *elem = (const char *)block;
+ status = (elem[sizeof(struct elemheader)+block->size] != MAGICBYTE) ? MCHECK_TAIL : MCHECK_OK;
+ }
+ break;
+
+ default:
+ status = MCHECK_HEAD;
+ break;
+ }
+ return status;
+}
+
+
+
+static void unlink_blk (struct elemheader *ptr)
+{
+ if (ptr->next != NULL) {
+ ptr->next->prev = ptr->prev;
+ ptr->next->magic = MAGICWORD ^ ((uintptr_t) ptr->next->prev + (uintptr_t) ptr->next->next);
+ }
+
+ if (ptr->prev != NULL) {
+ ptr->prev->next = ptr->next;
+ ptr->prev->magic = MAGICWORD ^ ((uintptr_t) ptr->prev->prev + (uintptr_t) ptr->prev->next);
+ } else {
+ root = ptr->next;
+ }
+}
+
+static void link_blk (struct elemheader *ptr)
+{
+ ptr->prev = NULL;
+ ptr->next = root;
+ root = ptr;
+ ptr->magic = MAGICWORD ^ (uintptr_t) ptr->next;
+
+ /* And the next block. */
+ if (ptr->next != NULL) {
+ ptr->next->prev = ptr;
+ ptr->next->magic = MAGICWORD ^ ((uintptr_t) ptr + (uintptr_t) ptr->next->next);
+ }
+}
+
+static void heapwalker(void)
+{
+ const struct elemheader *runp = root;
+
+ while (runp) {
+ enum mcheck_status mstatus = me_corrupt_mprobe(runp);
+ if (mstatus != MCHECK_OK) {
+ HeapCorrupt("heapwalker",runp,mstatus);
+ }
+ runp = runp->next;
+ }
+}
+
+
+static void me_corrupt_free_hook (__ptr_t ptr, const __ptr_t caller)
+{
+ enum mcheck_status mstatus;
+
+ pthread_mutex_lock(&HeapCorruptCheckMutex);
+
+ if (HeapCorruptCheckMode == HEAPCHECKMODE_EXTENSIVE) {
+ heapwalker();
+ }
+
+ if (PrintUsage) {
+ if (ptr) {
+ struct elemheader *hdr = (struct elemheader *)ptr - 1;
+ std::cout << "pid :" << getpid() << " free: Attempting to delete area with size " << hdr->size << " bytes" << std::endl;
+ } else {
+ std::cout << "pid :" << getpid() << " free: Attempting to delete NULL pointer " << std::endl;
+ }
+ }
+
+
+ if (ptr) {
+ struct elemheader *hdr = (struct elemheader *)ptr - 1;
+
+ mstatus = (HeapCorruptCheckMode == HEAPCHECKMODE_DISABLED) ? MCHECK_OK : me_corrupt_mprobe(hdr);
+ if (mstatus != MCHECK_OK) {
+ HeapCorrupt("free", hdr ,mstatus);
+ }
+
+ if (mstatus == MCHECK_OK) { // ForGet element if not detected or reallocated..
+ hdr->magic = MAGICFREE;
+ unlink_blk (hdr);
+ hdr->prev = hdr->next = NULL;
+
+ char * userdata = (char *)hdr+sizeof(struct elemheader);
+ if (hdr->size > 0) {
+ memset (userdata, FREEFLOOD, hdr->size);
+ }
+
+ ptr = (__ptr_t) hdr;
+ } else {
+ ptr = NULL; // ForGet element if not detected or reallocated..
+ }
+ }
+
+
+ __free_hook = old_free_hook_memcorrupt;
+
+ if (old_free_hook_memcorrupt != NULL) {
+ (*old_free_hook_memcorrupt) (ptr, caller);
+ } else {
+ free (ptr);
+ }
+
+ __free_hook = me_corrupt_free_hook;
+
+ pthread_mutex_unlock(&HeapCorruptCheckMutex);
+}
+
+static __ptr_t me_corrupt_malloc_hook (__malloc_size_t size, const __ptr_t caller)
+{
+ struct elemheader *hdr;
+ enum mcheck_status mstatus;
+
+ pthread_mutex_lock(&HeapCorruptCheckMutex);
+
+
+ if (HeapCorruptCheckMode == HEAPCHECKMODE_EXTENSIVE) {
+ heapwalker();
+ }
+
+ if (PrintUsage) {
+ std::cout << "pid :" << getpid() << " malloc: Attempting to allocate " << size << " bytes" << std::endl;
+ }
+
+
+ __malloc_hook = old_malloc_hook_memcorrupt;
+ if (old_malloc_hook_memcorrupt != NULL) {
+ hdr = (struct elemheader *) (*old_malloc_hook_memcorrupt) (sizeof (struct elemheader) + size + 1,caller);
+ } else {
+ hdr = (struct elemheader *) malloc (sizeof (struct elemheader) + size + 1);
+ }
+
+ __malloc_hook = me_corrupt_malloc_hook;
+
+ if (hdr) {
+ hdr->size = size;
+ link_blk (hdr);
+
+ char * userdata = (char *)hdr+sizeof(struct elemheader);
+ userdata[size] = MAGICBYTE;
+ if (size > 0) {
+ memset (userdata, MALLOCFLOOD, size);
+ }
+
+ // Self check!
+ mstatus = (HeapCorruptCheckMode == HEAPCHECKMODE_DISABLED) ? MCHECK_OK : me_corrupt_mprobe(hdr);
+ if (mstatus != MCHECK_OK) {
+ HeapCorrupt("malloc", hdr ,mstatus);
+ }
+ }
+
+ pthread_mutex_unlock(&HeapCorruptCheckMutex);
+
+ return hdr ? (__ptr_t) (hdr + 1) : NULL;
+}
+
+static __ptr_t me_corrupt_realloc_hook (__ptr_t ptr, __malloc_size_t size, const __ptr_t caller)
+{
+ struct elemheader *hdr;
+ __malloc_size_t osize;
+ enum mcheck_status mstatus;
+
+ pthread_mutex_lock(&HeapCorruptCheckMutex);
+
+ if (HeapCorruptCheckMode == HEAPCHECKMODE_EXTENSIVE) {
+ heapwalker();
+ }
+
+ if (PrintUsage) {
+ std::cout << "pid :" << getpid() << " realloc: Attempting to allocate area with size " << size << " bytes.";
+ if (ptr) {
+ hdr = (struct elemheader *)ptr - 1;
+ std::cout << " Old buffer was " << hdr->size << " bytes" << std::endl;
+ } else {
+ std::cout << " No old buffer." << std::endl;
+ }
+ }
+
+ if (ptr) {
+ hdr = ((struct elemheader *) ptr) - 1;
+ osize = hdr->size;
+
+ mstatus = (HeapCorruptCheckMode == HEAPCHECKMODE_DISABLED) ? MCHECK_OK : me_corrupt_mprobe(hdr);
+ if (mstatus != MCHECK_OK) {
+ HeapCorrupt("realloc/1", hdr ,mstatus);
+ }
+
+ unlink_blk (hdr);
+ if (size < osize) {
+ memset ((char *) ptr + size, FREEFLOOD, osize - size);
+ }
+ } else {
+ osize = 0;
+ hdr = NULL;
+ }
+
+ __free_hook = old_free_hook_memcorrupt;
+ __malloc_hook = old_malloc_hook_memcorrupt;
+ __realloc_hook = old_realloc_hook_memcorrupt;
+
+ if (old_realloc_hook_memcorrupt != NULL) {
+ hdr = (struct elemheader *) (*old_realloc_hook_memcorrupt) ((__ptr_t) hdr, sizeof (struct elemheader) + size + 1, caller);
+ } else {
+ hdr = (struct elemheader *) realloc ((__ptr_t) hdr,sizeof (struct elemheader) + size + 1);
+ }
+
+ __free_hook = me_corrupt_free_hook;
+ __malloc_hook = me_corrupt_malloc_hook;
+ __realloc_hook = me_corrupt_realloc_hook;
+
+ if (hdr) {
+ hdr->size = size;
+ link_blk (hdr);
+
+ char * userdata = (char *)hdr+sizeof(struct elemheader);
+ userdata[size] = MAGICBYTE;
+
+ if (size > osize) {
+ memset (&userdata[osize], MALLOCFLOOD, size-osize);
+ }
+
+ mstatus = (HeapCorruptCheckMode == HEAPCHECKMODE_DISABLED) ? MCHECK_OK : me_corrupt_mprobe(hdr);
+ if (mstatus != MCHECK_OK) {
+ HeapCorrupt("realloc/2", hdr ,mstatus);
+ }
+ }
+
+ pthread_mutex_unlock(&HeapCorruptCheckMutex);
+
+ return hdr ? (__ptr_t) (hdr + 1) : NULL;
+}
+
+
+static void* me_corrupt_memalign_hook(size_t alignment, size_t bytes,const void *caller)
+{
+ void *aligned;
+
+ std::cout << "memalign #2 " << std::endl;
+ __memalign_hook = old_memalign_hook_memcorrupt;
+
+ if (old_memalign_hook_memcorrupt != NULL) {
+ aligned = (*old_memalign_hook_memcorrupt) (alignment, bytes,caller);
+ } else {
+ aligned = memalign (alignment, bytes);
+ }
+
+ __memalign_hook = me_corrupt_memalign_hook;
+
+ return aligned;
+}
+
+
+
+void enableHeapCorruptCheck(int param)
+{
+
+ pthread_mutex_lock(&HeapCorruptCheckMutex);
+
+
+ // This is install..
+ if (HeapCorruptCheckMode == HEAPCHECKMODE_REMOVE && param != HEAPCHECKMODE_REMOVE) {
+ old_free_hook_memcorrupt = __free_hook;
+ __free_hook = me_corrupt_free_hook;
+ old_malloc_hook_memcorrupt = __malloc_hook;
+ __malloc_hook=me_corrupt_malloc_hook;
+ old_realloc_hook_memcorrupt = __realloc_hook;
+ __realloc_hook = me_corrupt_realloc_hook;
+ old_memalign_hook_memcorrupt = __memalign_hook;
+ __memalign_hook = me_corrupt_memalign_hook;
+ std::cout << "Heap corruption detector installed" << std::endl;
+ }
+
+ // This is deinstall..
+ if (HeapCorruptCheckMode != HEAPCHECKMODE_REMOVE && param == HEAPCHECKMODE_REMOVE) {
+ __free_hook = old_free_hook_memcorrupt;
+ __malloc_hook = old_malloc_hook_memcorrupt;
+ __realloc_hook = old_realloc_hook_memcorrupt;
+ __memalign_hook = old_memalign_hook_memcorrupt;
+ std::cout << "Heap corruption detector removed" << std::endl;
+ }
+
+ HeapCorruptCheckMode = param;
+ pthread_mutex_unlock(&HeapCorruptCheckMutex);
+}
+
+
+
+
+void checkHeapNow(void)
+{
+ pthread_mutex_lock(&HeapCorruptCheckMutex);
+ heapwalker();
+ pthread_mutex_unlock(&HeapCorruptCheckMutex);
+}
+
+
+//---------------------------------
+// Part 3: glibc's mcheck librrary
+
+static void mcheckabortfunc(enum mcheck_status err)
+{
+ HeapCorrupt("mcheckabortfunc",NULL,err);
+}
+
+
+void enableMCheck(void)
+{
+ mcheck(&mcheckabortfunc);
+}
+
+
+//---------------------------------
+// Part 4: error report & exit
+
+static void HeapDump(const char *info,const char *p,int cnt)
+{
+ char Legend[150];
+ snprintf(Legend,sizeof(Legend),"Legend: Magic (tail) [%02x]. allocated, but unused: [%02x]. Reclaimed to free pool: [%02x]",
+ (unsigned char)MAGICBYTE,
+ (unsigned char)MALLOCFLOOD,
+ (unsigned char)FREEFLOOD);
+
+ std::cout << Legend << std::endl;
+ std::cout << "Buffer dump: " << info << std::endl;
+
+ for (int i = 0;i < cnt;i++) {
+ char c = p[i];
+ if (isgraph(c) || c == ' ') {
+ std::cout << c;
+ } else {
+ char Tmp[10];
+ snprintf(Tmp,sizeof(Tmp),"[%02x]",(unsigned char)c);
+ std::cout << Tmp;
+ }
+ }
+ std::cout << std::endl;
+}
+
+
+static void HeapCorrupt(const char * caller, const struct elemheader *hdr, enum mcheck_status err)
+{
+
+ bool AbortNow = true;
+
+#define DUMPSIZE 100
+
+ if (!caller) {
+ caller = "?";
+ }
+ switch (err) {
+ case MCHECK_DISABLED:
+ std::cout << caller
+ << ": Heap check was not called before the first allocation. No consistency checking can be done."
+ << std::endl;
+ break;
+
+ case MCHECK_OK:
+ std::cout << caller
+ << ": No inconsistency detected."
+ << std::endl;
+ AbortNow = false;
+ break;
+
+ case MCHECK_HEAD:
+ std::cout << caller
+ << ": The data immediately before the block was modified. This commonly happens when an array index or pointer is decremented too far."
+ << std::endl
+ << " This block is ignored (and may appear as memory leakage)."
+ << std::endl;
+
+ if (hdr) {
+ HeapDump("Immediately before ",((const char *)hdr) - (DUMPSIZE/2) ,DUMPSIZE/2);
+ HeapDump("The element in question ",(const char *)hdr,DUMPSIZE/2);
+ }
+ AbortNow = false;
+ break;
+
+ case MCHECK_TAIL:
+ std::cout << caller
+ << ": The data immediately after the block was modified. This commonly happens when an array index or pointer is incremented too far."
+ << std::endl;
+
+ if (hdr) {
+ HeapDump("Tail of element in question: ", ((const char *)hdr) + sizeof(struct elemheader) + hdr->size + 1 - (DUMPSIZE/2),(DUMPSIZE/2));
+ HeapDump("Immediately after element: ",((const char *)hdr) + sizeof(struct elemheader) + hdr->size + 1 + 1, (DUMPSIZE/2));
+ }
+
+ break;
+
+ case MCHECK_FREE:
+ std::cout << caller
+ << ": The block was already freed."
+ << std::endl;
+ break;
+
+
+ default:
+ std::cout << caller
+ << ": Unexpected event " << err
+ << std::endl;
+ break;
+ }
+
+
+ if (hdr) {
+ std::cout << caller << ": The element size was " << hdr->size << " bytes." << std::endl;
+ if (err == MCHECK_HEAD) {
+ std::cout << caller << ": But size may be incorrect since other parts of the header is destroyed/undecodable." << std::endl;
+ }
+ }
+
+ if (AbortNow) {
+ std::cout << caller << ": Application will crash now." << std::flush;
+
+ // No core dump? Check ulimit -c
+
+ kill(getpid(), SIGSEGV);
+ }
+
+}
+
+// Enable this one if you want the heapdebugger to be installed "before anything else"
+// Uses same method as -lmcheck
+#if 0
+
+/* This one is called from malloc init.. */
+
+static void MallocInitHook(void)
+{
+ enableHeapUsageMonitor(0);
+ enableHeapCorruptCheck(0);
+}
+
+void (*__malloc_initialize_hook) (void) = MallocInitHook;
+
+
+#endif
diff --git a/document/src/tests/heapdebuggerother.cpp b/document/src/tests/heapdebuggerother.cpp
new file mode 100644
index 00000000000..641951f1883
--- /dev/null
+++ b/document/src/tests/heapdebuggerother.cpp
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ *
+ * @author Ove Martin Malm
+ * @date Creation date: 2000-21-08
+ * @version $Id$
+ *
+ * Copyright (c) : 1997-2000 Fast Search & Transfer ASA
+ * ALL RIGHTS RESERVED
+ */
+
+#include <vespa/fastos/fastos.h>
+#include <stdlib.h>
+#include "heapdebugger.h"
+
+// Not implemented
+void enableHeapUsageMonitor(int param)
+{
+ (void)param;
+
+}
+
+// Not implemented
+extern size_t getHeapUsage(void)
+{
+ return 0;
+}
+
+// Not implemented
+void enableHeapCorruptCheck(int param)
+{
+ (void)param;
+
+}
+
+// Not implemented
+void enableMCheck(void)
+{
+}
+
+// Not implemented
+void checkHeapNow(void)
+{
+}
diff --git a/document/src/tests/orderingselectortest.cpp b/document/src/tests/orderingselectortest.cpp
new file mode 100644
index 00000000000..b9cf7dc358a
--- /dev/null
+++ b/document/src/tests/orderingselectortest.cpp
@@ -0,0 +1,98 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/select/orderingselector.h>
+#include <vespa/document/bucket/bucketidfactory.h>
+#include <vespa/document/base/testdocrepo.h>
+
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/document/select/parser.h>
+#include <memory>
+
+using document::select::Node;
+using document::select::Parser;
+
+namespace document {
+
+struct OrderingSelectorTest : public CppUnit::TestFixture {
+ void testSimple();
+
+ CPPUNIT_TEST_SUITE(OrderingSelectorTest);
+ CPPUNIT_TEST(testSimple);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(OrderingSelectorTest);
+
+#define ASSERT_MATCH(expression, ordering, correct) \
+{ \
+ BucketIdFactory idfactory; \
+ TestDocRepo repo; \
+ OrderingSelector selector; \
+ Parser parser(repo.getTypeRepo(), idfactory); \
+ std::unique_ptr<Node> node(parser.parse(expression)); \
+ CPPUNIT_ASSERT(node.get() != 0); \
+ OrderingSpecification::UP spec = selector.select(*node, ordering); \
+ if (spec.get() == NULL && correct.get() == NULL) { \
+ return;\
+ }\
+ if (spec.get() == NULL && correct.get() != NULL) { \
+ CPPUNIT_ASSERT_MSG(std::string("Was NULL, expected ") + correct->toString(), false); \
+ } \
+ if (correct.get() == NULL && spec.get() != NULL) { \
+ CPPUNIT_ASSERT_MSG(std::string("Expected NULL, was ") + spec->toString(), false); \
+ } \
+ CPPUNIT_ASSERT_EQUAL(*correct, *spec); \
+}
+
+void OrderingSelectorTest::testSimple()
+{
+ ASSERT_MATCH("id.order(10,10) < 100", OrderingSpecification::DESCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::DESCENDING, (long)99, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) <= 100", OrderingSpecification::DESCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::DESCENDING, (long)100, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) > 100", OrderingSpecification::DESCENDING, OrderingSpecification::UP());
+
+ ASSERT_MATCH("id.order(10,10) > 100", OrderingSpecification::ASCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::ASCENDING, (long)101, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.user==1234 AND id.order(10,10) > 100", OrderingSpecification::ASCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::ASCENDING, (long)101, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) >= 100", OrderingSpecification::ASCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::ASCENDING, (long)100, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) == 100", OrderingSpecification::ASCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::ASCENDING, (long)100, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) = 100", OrderingSpecification::DESCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::DESCENDING, (long)100, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) > 30 AND id.order(10,10) < 100", OrderingSpecification::ASCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::ASCENDING, (long)31, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) > 30 AND id.order(10,10) < 100", OrderingSpecification::DESCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::DESCENDING, (long)99, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) > 30 OR id.order(10,10) > 70", OrderingSpecification::ASCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::ASCENDING, (long)31, (short)10, (short)10)));
+
+ ASSERT_MATCH("id.order(10,10) < 30 OR id.order(10,10) < 70", OrderingSpecification::DESCENDING,
+ OrderingSpecification::UP(
+ new OrderingSpecification(OrderingSpecification::DESCENDING, (long)69, (short)10, (short)10)));
+}
+
+
+}
diff --git a/document/src/tests/positiontypetest.cpp b/document/src/tests/positiontypetest.cpp
new file mode 100644
index 00000000000..33398e9fa00
--- /dev/null
+++ b/document/src/tests/positiontypetest.cpp
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/datatype/positiondatatype.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace document {
+
+class PositionTypeTest : public CppUnit::TestFixture {
+public:
+ void requireThatNameIsCorrect();
+ void requireThatExpectedFieldsAreThere();
+ void requireThatZCurveFieldMatchesJava();
+
+ CPPUNIT_TEST_SUITE(PositionTypeTest);
+ CPPUNIT_TEST(requireThatNameIsCorrect);
+ CPPUNIT_TEST(requireThatExpectedFieldsAreThere);
+ CPPUNIT_TEST(requireThatZCurveFieldMatchesJava);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PositionTypeTest);
+
+void
+PositionTypeTest::requireThatNameIsCorrect()
+{
+ const StructDataType &type = PositionDataType::getInstance();
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("position"), type.getName());
+}
+
+void
+PositionTypeTest::requireThatExpectedFieldsAreThere()
+{
+ const StructDataType &type = PositionDataType::getInstance();
+ Field field = type.getField("x");
+ CPPUNIT_ASSERT_EQUAL(*DataType::INT, field.getDataType());
+
+ field = type.getField("y");
+ CPPUNIT_ASSERT_EQUAL(*DataType::INT, field.getDataType());
+}
+
+void
+PositionTypeTest::requireThatZCurveFieldMatchesJava()
+{
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("foo_zcurve"),
+ PositionDataType::getZCurveFieldName("foo"));
+ CPPUNIT_ASSERT( ! PositionDataType::isZCurveFieldName("foo"));
+ CPPUNIT_ASSERT( ! PositionDataType::isZCurveFieldName("_zcurve"));
+ CPPUNIT_ASSERT( PositionDataType::isZCurveFieldName("x_zcurve"));
+ CPPUNIT_ASSERT( ! PositionDataType::isZCurveFieldName("x_zcurvex"));
+ CPPUNIT_ASSERT_EQUAL(vespalib::stringref("x"), PositionDataType::cutZCurveFieldName("x_zcurve"));
+}
+
+} // document
diff --git a/document/src/tests/predicate/.gitignore b/document/src/tests/predicate/.gitignore
new file mode 100644
index 00000000000..219b478bd6d
--- /dev/null
+++ b/document/src/tests/predicate/.gitignore
@@ -0,0 +1,7 @@
+*.So
+*_test
+.depend
+Makefile
+document_predicate_builder_test_app
+document_predicate_printer_test_app
+document_predicate_test_app
diff --git a/document/src/tests/predicate/CMakeLists.txt b/document/src/tests/predicate/CMakeLists.txt
new file mode 100644
index 00000000000..445da128498
--- /dev/null
+++ b/document/src/tests/predicate/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_predicate_test_app
+ SOURCES
+ predicate_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_predicate_test_app COMMAND document_predicate_test_app)
+vespa_add_executable(document_predicate_builder_test_app
+ SOURCES
+ predicate_builder_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_predicate_builder_test_app COMMAND document_predicate_builder_test_app)
+vespa_add_executable(document_predicate_printer_test_app
+ SOURCES
+ predicate_printer_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_predicate_printer_test_app COMMAND document_predicate_printer_test_app)
diff --git a/document/src/tests/predicate/predicate_builder_test.cpp b/document/src/tests/predicate/predicate_builder_test.cpp
new file mode 100644
index 00000000000..1e32aa2cc51
--- /dev/null
+++ b/document/src/tests/predicate/predicate_builder_test.cpp
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for predicate_builder.
+
+#include <vespa/log/log.h>
+LOG_SETUP("predicate_builder_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/predicate/predicate.h>
+#include <vespa/document/predicate/predicate_builder.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using std::string;
+using vespalib::Slime;
+using vespalib::slime::Cursor;
+using namespace document;
+
+namespace {
+
+TEST("require that a predicate tree can be built from a slime object") {
+ const string feature_name = "feature name";
+ Slime input;
+ Cursor &obj = input.setObject();
+ obj.setLong(Predicate::NODE_TYPE, Predicate::TYPE_DISJUNCTION);
+ Cursor &children = obj.setArray(Predicate::CHILDREN);
+ {
+ Cursor &child = children.addObject();
+ child.setLong(Predicate::NODE_TYPE, Predicate::TYPE_FEATURE_SET);
+ child.setString(Predicate::KEY, feature_name);
+ Cursor &arr = child.setArray(Predicate::SET);
+ arr.addString("foo");
+ arr.addString("bar");
+ }
+ {
+ Cursor &child = children.addObject();
+ child.setLong(Predicate::NODE_TYPE, Predicate::TYPE_CONJUNCTION);
+ Cursor &and_children = child.setArray(Predicate::CHILDREN);
+ {
+ Cursor &and_child = and_children.addObject();
+ and_child.setLong(Predicate::NODE_TYPE,
+ Predicate::TYPE_FEATURE_RANGE);
+ and_child.setString(Predicate::KEY, feature_name);
+ and_child.setLong(Predicate::RANGE_MIN, 42);
+ }
+ {
+ Cursor &and_child = and_children.addObject();
+ and_child.setLong(Predicate::NODE_TYPE, Predicate::TYPE_NEGATION);
+ Cursor &not_child =
+ and_child.setArray(Predicate::CHILDREN).addObject();
+ {
+ not_child.setLong(Predicate::NODE_TYPE,
+ Predicate::TYPE_FEATURE_SET);
+ not_child.setString(Predicate::KEY, feature_name);
+ Cursor &arr = not_child.setArray(Predicate::SET);
+ arr.addString("baz");
+ arr.addString("qux");
+ }
+ }
+ }
+
+ PredicateNode::UP node = PredicateBuilder().build(input.get());
+ Disjunction *disjunction = dynamic_cast<Disjunction *>(node.get());
+ ASSERT_TRUE(disjunction);
+ ASSERT_EQUAL(2u, disjunction->getSize());
+ FeatureSet *feature_set = dynamic_cast<FeatureSet *>((*disjunction)[0]);
+ ASSERT_TRUE(feature_set);
+ Conjunction *conjunction = dynamic_cast<Conjunction *>((*disjunction)[1]);
+ ASSERT_TRUE(conjunction);
+ ASSERT_EQUAL(2u, conjunction->getSize());
+ FeatureRange *feature_range =
+ dynamic_cast<FeatureRange *>((*conjunction)[0]);
+ ASSERT_TRUE(feature_range);
+ Negation *negation = dynamic_cast<Negation *>((*conjunction)[1]);
+ ASSERT_TRUE(negation);
+ feature_set = dynamic_cast<FeatureSet *>(&negation->getChild());
+ ASSERT_TRUE(feature_set);
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/predicate/predicate_printer_test.cpp b/document/src/tests/predicate/predicate_printer_test.cpp
new file mode 100644
index 00000000000..8525fb8e2db
--- /dev/null
+++ b/document/src/tests/predicate/predicate_printer_test.cpp
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for predicate_printer.
+
+#include <vespa/log/log.h>
+LOG_SETUP("predicate_printer_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/predicate/predicate.h>
+#include <vespa/document/predicate/predicate_printer.h>
+#include <vespa/document/predicate/predicate_slime_builder.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using vespalib::Slime;
+using vespalib::slime::Cursor;
+using namespace document;
+
+namespace {
+
+typedef std::unique_ptr<Slime> SlimeUP;
+using namespace document::predicate_slime_builder;
+
+TEST("require that PredicatePrinter prints FeatureSets") {
+ PredicateSlimeBuilder builder;
+ builder.feature("foo").value("bar").value("baz");
+ ASSERT_EQUAL("'foo' in ['bar','baz']",
+ PredicatePrinter::print(*builder.build()));
+
+ builder.feature("foo").value("bar");
+ ASSERT_EQUAL("'foo' in ['bar']",
+ PredicatePrinter::print(*builder.build()));
+}
+
+TEST("require that PredicatePrinter escapes non-ascii characters") {
+ PredicateSlimeBuilder builder;
+ builder.feature("\n\t\001'").value("\xc3\xb8");
+ ASSERT_EQUAL("'\\n\\t\\x01\\x27' in ['\\xc3\\xb8']",
+ PredicatePrinter::print(*builder.build()));
+}
+
+TEST("require that PredicatePrinter prints FeatureRanges") {
+ PredicateSlimeBuilder builder;
+ builder.feature("foo").range(-10, 42);
+ ASSERT_EQUAL("'foo' in [-10..42]",
+ PredicatePrinter::print(*builder.build()));
+}
+
+TEST("require that PredicatePrinter prints open ended FeatureRanges") {
+ PredicateSlimeBuilder builder;
+ builder.feature("foo").greaterEqual(-10);
+ ASSERT_EQUAL("'foo' in [-10..]",
+ PredicatePrinter::print(*builder.build()));
+
+ builder.feature("foo").lessEqual(42);
+ ASSERT_EQUAL("'foo' in [..42]", PredicatePrinter::print(*builder.build()));
+}
+
+TEST("require that PredicatePrinter prints NOT_IN FeatureSets") {
+ PredicateSlimeBuilder builder;
+ builder.neg().feature("foo").value("bar").value("baz");
+ ASSERT_EQUAL("'foo' not in ['bar','baz']",
+ PredicatePrinter::print(*builder.build()));
+}
+
+TEST("require that PredicatePrinter can negate FeatureRanges") {
+ auto slime = neg(featureRange("foo", -10, 42));
+ ASSERT_EQUAL("'foo' not in [-10..42]", PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter can negate open ended FeatureRanges") {
+ auto slime = neg(greaterEqual("foo", 42));
+ ASSERT_EQUAL("'foo' not in [42..]", PredicatePrinter::print(*slime));
+
+ slime = neg(lessEqual("foo", 42));
+ ASSERT_EQUAL("'foo' not in [..42]", PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter can negate double open ended ranges") {
+ auto slime = neg(emptyRange("foo"));
+ ASSERT_EQUAL("'foo' not in [..]", PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter prints AND expressions") {
+ auto slime = andNode({featureSet("foo", {"bar", "baz"}),
+ featureSet("foo", {"bar", "baz"})});
+ ASSERT_EQUAL("('foo' in ['bar','baz'] and 'foo' in ['bar','baz'])",
+ PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter prints OR expressions") {
+ auto slime = orNode({featureSet("foo", {"bar", "baz"}),
+ featureSet("foo", {"bar", "baz"})});
+ ASSERT_EQUAL("('foo' in ['bar','baz'] or 'foo' in ['bar','baz'])",
+ PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter can negate OR expressions") {
+ auto slime = neg(orNode({featureSet("foo", {"bar", "baz"}),
+ featureSet("foo", {"bar", "baz"})}));
+ ASSERT_EQUAL("not ('foo' in ['bar','baz'] or 'foo' in ['bar','baz'])",
+ PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter can negate AND expressions") {
+ auto slime = neg(andNode({featureSet("foo", {"bar", "baz"}),
+ featureSet("foo", {"bar", "baz"})}));
+ ASSERT_EQUAL("not ('foo' in ['bar','baz'] and 'foo' in ['bar','baz'])",
+ PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter prints True") {
+ auto slime = truePredicate();
+ ASSERT_EQUAL("true", PredicatePrinter::print(*slime));
+}
+
+TEST("require that PredicatePrinter prints False") {
+ auto slime = falsePredicate();
+ ASSERT_EQUAL("false", PredicatePrinter::print(*slime));
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/predicate/predicate_test.cpp b/document/src/tests/predicate/predicate_test.cpp
new file mode 100644
index 00000000000..3223b628c6a
--- /dev/null
+++ b/document/src/tests/predicate/predicate_test.cpp
@@ -0,0 +1,248 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for predicate.
+
+#include <vespa/log/log.h>
+LOG_SETUP("predicate_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/predicate/predicate.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+#include <vespa/document/predicate/predicate_slime_builder.h>
+#include <string>
+
+using std::string;
+using std::vector;
+using vespalib::Slime;
+using vespalib::slime::Cursor;
+using namespace document;
+
+namespace {
+
+typedef std::unique_ptr<Slime> SlimeUP;
+
+TEST("require that predicate feature set slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ builder.feature("foo").value("bar").value("baz");
+ SlimeUP s1 = builder.build();
+ builder.feature("foo").value("baz").value("bar");
+ ASSERT_EQUAL(0, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("bar").value("baz").value("bar");
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("qux").value("baz").value("bar");
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").value("baz");
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("foo").value("baz").value("qux").value("quux");
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").value("baz").value("qux");
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("foo").value("baz").value("aaa");
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+}
+
+TEST("require that predicate feature range slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ builder.feature("foo").range(0, 10);
+ SlimeUP s1 = builder.build();
+ builder.feature("foo").range(0, 10);
+ ASSERT_EQUAL(0, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").range(-1, 10);
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("foo").range(1, 10);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").range(0, 9);
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("foo").range(0, 11);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").greaterEqual(0);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("foo").lessEqual(10);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+}
+
+TEST("require that predicate open feature range slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ builder.feature("foo").greaterEqual(10);
+ SlimeUP s1 = builder.build();
+ builder.feature("foo").greaterEqual(10);
+ ASSERT_EQUAL(0, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").greaterEqual(9);
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+ builder.feature("foo").greaterEqual(11);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").lessEqual(10);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+}
+
+TEST("require that predicate 'not' slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ builder.neg().feature("foo").range(0, 10);
+ SlimeUP s1 = builder.build();
+ builder.neg().feature("foo").range(0, 10);
+ ASSERT_EQUAL(0, Predicate::compare(*s1, *builder.build()));
+
+ builder.neg().feature("foo").range(0, 11);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+
+ builder.feature("foo").range(0, 10);
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+}
+
+TEST("require that predicate 'and' slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ SlimeUP s1 = builder.feature("foo").value("bar").value("baz").build();
+ SlimeUP s2 = builder.feature("foo").value("bar").value("qux").build();
+ SlimeUP and_node = builder.and_node(std::move(s1), std::move(s2)).build();
+
+ s1 = builder.feature("foo").value("bar").value("baz").build();
+ s2 = builder.feature("foo").value("bar").value("qux").build();
+ builder.and_node(std::move(s1), std::move(s2));
+ ASSERT_EQUAL(0, Predicate::compare(*and_node, *builder.build()));
+
+ s1 = builder.feature("foo").value("bar").value("baz").build();
+ s2 = builder.feature("foo").value("bar").value("qux").build();
+ builder.and_node(std::move(s2), std::move(s1));
+ ASSERT_EQUAL(-1, Predicate::compare(*and_node, *builder.build()));
+}
+
+TEST("require that predicate 'or' slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ SlimeUP s1 = builder.feature("foo").value("bar").value("baz").build();
+ SlimeUP s2 = builder.feature("foo").value("bar").value("qux").build();
+ SlimeUP or_node = builder.or_node(std::move(s1), std::move(s2)).build();
+
+ s1 = builder.feature("foo").value("bar").value("baz").build();
+ s2 = builder.feature("foo").value("bar").value("qux").build();
+ builder.or_node(std::move(s1), std::move(s2));
+ ASSERT_EQUAL(0, Predicate::compare(*or_node, *builder.build()));
+
+ s1 = builder.feature("foo").value("bar").value("baz").build();
+ s2 = builder.feature("foo").value("bar").value("qux").build();
+ builder.or_node(std::move(s2), std::move(s1));
+ ASSERT_EQUAL(-1, Predicate::compare(*or_node, *builder.build()));
+}
+
+TEST("require that predicate 'true' slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ builder.true_predicate();
+ SlimeUP s1 = builder.build();
+ builder.true_predicate();
+ ASSERT_EQUAL(0, Predicate::compare(*s1, *builder.build()));
+
+ builder.false_predicate();
+ ASSERT_EQUAL(-1, Predicate::compare(*s1, *builder.build()));
+}
+
+TEST("require that predicate 'false' slimes can be compared") {
+ PredicateSlimeBuilder builder;
+ builder.false_predicate();
+ SlimeUP s1 = builder.build();
+ builder.false_predicate();
+ ASSERT_EQUAL(0, Predicate::compare(*s1, *builder.build()));
+
+ builder.true_predicate();
+ ASSERT_EQUAL(1, Predicate::compare(*s1, *builder.build()));
+}
+
+TEST("require that feature set can be created") {
+ const string feature_name = "feature name";
+ Slime input;
+ Cursor &obj = input.setObject();
+ obj.setString(Predicate::KEY, feature_name);
+ Cursor &arr = obj.setArray(Predicate::SET);
+ arr.addString("foo");
+ arr.addString("bar");
+ FeatureSet set(input.get());
+ EXPECT_EQUAL(feature_name, set.getKey());
+ ASSERT_EQUAL(2u, set.getSize());
+ EXPECT_EQUAL("foo", set[0]);
+ EXPECT_EQUAL("bar", set[1]);
+}
+
+TEST("require that feature range can be created") {
+ const string feature_name = "feature name";
+ const long min = 0;
+ const long max = 42;
+ Slime input;
+ Cursor &obj = input.setObject();
+ obj.setString(Predicate::KEY, feature_name);
+ obj.setLong(Predicate::RANGE_MIN, min);
+ obj.setLong(Predicate::RANGE_MAX, max);
+ FeatureRange set(input.get());
+ EXPECT_EQUAL(feature_name, set.getKey());
+ EXPECT_TRUE(set.hasMin());
+ EXPECT_TRUE(set.hasMax());
+ EXPECT_EQUAL(min, set.getMin());
+ EXPECT_EQUAL(max, set.getMax());
+}
+
+TEST("require that feature range can be open") {
+ const string feature_name = "feature name";
+ Slime input;
+ Cursor &obj = input.setObject();
+ obj.setString(Predicate::KEY, feature_name);
+ FeatureRange set(input.get());
+ EXPECT_EQUAL(feature_name, set.getKey());
+ EXPECT_FALSE(set.hasMin());
+ EXPECT_FALSE(set.hasMax());
+ EXPECT_EQUAL(LLONG_MIN, set.getMin());
+ EXPECT_EQUAL(LLONG_MAX, set.getMax());
+}
+
+PredicateNode::UP getPredicateNode() {
+ const string feature_name = "feature name";
+ Slime input;
+ Cursor &obj = input.setObject();
+ obj.setString(Predicate::KEY, feature_name);
+ Cursor &arr = obj.setArray(Predicate::SET);
+ arr.addString("foo");
+ arr.addString("bar");
+
+ PredicateNode::UP node(new FeatureSet(input.get()));
+ return node;
+}
+
+TEST("require that negation nodes holds a child") {
+ PredicateNode::UP node(getPredicateNode());
+ PredicateNode *expected = node.get();
+ Negation neg(std::move(node));
+
+ EXPECT_EQUAL(expected, &neg.getChild());
+}
+
+TEST("require that conjunction nodes holds several children") {
+ vector<PredicateNode *> nodes;
+ nodes.push_back(getPredicateNode().release());
+ nodes.push_back(getPredicateNode().release());
+ Conjunction and_node(nodes);
+
+ ASSERT_EQUAL(2u, and_node.getSize());
+ EXPECT_EQUAL(nodes[0], and_node[0]);
+ EXPECT_EQUAL(nodes[1], and_node[1]);
+}
+
+TEST("require that disjunction nodes holds several children") {
+ vector<PredicateNode *> nodes;
+ nodes.push_back(getPredicateNode().release());
+ nodes.push_back(getPredicateNode().release());
+ Disjunction or_node(nodes);
+
+ ASSERT_EQUAL(2u, or_node.getSize());
+ EXPECT_EQUAL(nodes[0], or_node[0]);
+ EXPECT_EQUAL(nodes[1], or_node[1]);
+}
+
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/primitivefieldvaluetest.cpp b/document/src/tests/primitivefieldvaluetest.cpp
new file mode 100644
index 00000000000..90e51ec071d
--- /dev/null
+++ b/document/src/tests/primitivefieldvaluetest.cpp
@@ -0,0 +1,422 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/vespalib/objects/nbostream.h>
+
+using vespalib::nbostream;
+
+namespace document {
+
+struct PrimitiveFieldValueTest : public CppUnit::TestFixture {
+ void setUp() {}
+ void tearDown() {}
+
+ void testLiterals();
+ void testRaw();
+ void testNumerics();
+ void testFloatDoubleCasts();
+
+ CPPUNIT_TEST_SUITE(PrimitiveFieldValueTest);
+ CPPUNIT_TEST(testLiterals);
+ CPPUNIT_TEST(testRaw);
+ CPPUNIT_TEST(testNumerics);
+ CPPUNIT_TEST(testFloatDoubleCasts);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(PrimitiveFieldValueTest);
+
+namespace {
+template <typename T>
+void deserialize(const ByteBuffer &buffer, T &value) {
+ uint16_t version = Document::getNewestSerializationVersion();
+ nbostream stream(buffer.getBufferAtPos(), buffer.getRemaining());
+ DocumentTypeRepo repo;
+ VespaDocumentDeserializer deserializer(repo, stream, version);
+ deserializer.read(value);
+}
+
+ /**
+ * Test common functionality, such as serialization, comparisons, and
+ * assignment. medium1 and medium2 should be equal, but not the same
+ * instance.
+ */
+ template<typename Type>
+ void
+ testCommon(const Type& smallest, const Type& medium1,
+ const Type& medium2, const Type& largest)
+ {
+ try{
+ // Less
+ CPPUNIT_ASSERT(!(smallest < smallest));
+ CPPUNIT_ASSERT(smallest < medium1);
+ CPPUNIT_ASSERT(smallest < medium2);
+ CPPUNIT_ASSERT(smallest < largest);
+ CPPUNIT_ASSERT(!(medium1 < smallest));
+ CPPUNIT_ASSERT(!(medium1 < medium1));
+ CPPUNIT_ASSERT(!(medium1 < medium2));
+ CPPUNIT_ASSERT(medium1 < largest);
+ CPPUNIT_ASSERT(!(medium2 < smallest));
+ CPPUNIT_ASSERT(!(medium2 < medium1));
+ CPPUNIT_ASSERT(!(medium2 < medium2));
+ CPPUNIT_ASSERT(medium2 < largest);
+ CPPUNIT_ASSERT(!(largest < smallest));
+ CPPUNIT_ASSERT(!(largest < medium1));
+ CPPUNIT_ASSERT(!(largest < medium2));
+ CPPUNIT_ASSERT(!(largest < largest));
+ // Equal
+ CPPUNIT_ASSERT(smallest == smallest);
+ CPPUNIT_ASSERT(!(smallest == medium1));
+ CPPUNIT_ASSERT(!(smallest == medium2));
+ CPPUNIT_ASSERT(!(smallest == largest));
+ CPPUNIT_ASSERT(!(medium1 == smallest));
+ CPPUNIT_ASSERT(medium1 == medium1);
+ CPPUNIT_ASSERT(medium1 == medium2);
+ CPPUNIT_ASSERT(!(medium1 == largest));
+ CPPUNIT_ASSERT(!(medium2 == smallest));
+ CPPUNIT_ASSERT(medium2 == medium1);
+ CPPUNIT_ASSERT(medium2 == medium2);
+ CPPUNIT_ASSERT(!(medium2 == largest));
+ CPPUNIT_ASSERT(!(largest == smallest));
+ CPPUNIT_ASSERT(!(largest == medium1));
+ CPPUNIT_ASSERT(!(largest == medium2));
+ CPPUNIT_ASSERT(largest == largest);
+ // Greater
+ CPPUNIT_ASSERT(!(smallest > smallest));
+ CPPUNIT_ASSERT(!(smallest > medium1));
+ CPPUNIT_ASSERT(!(smallest > medium2));
+ CPPUNIT_ASSERT(!(smallest > largest));
+ CPPUNIT_ASSERT(medium1 > smallest);
+ CPPUNIT_ASSERT(!(medium1 > medium1));
+ CPPUNIT_ASSERT(!(medium1 > medium2));
+ CPPUNIT_ASSERT(!(medium1 > largest));
+ CPPUNIT_ASSERT(medium2 > smallest);
+ CPPUNIT_ASSERT(!(medium2 > medium1));
+ CPPUNIT_ASSERT(!(medium2 > medium2));
+ CPPUNIT_ASSERT(!(medium2 > largest));
+ CPPUNIT_ASSERT(largest > smallest);
+ CPPUNIT_ASSERT(largest > medium1);
+ CPPUNIT_ASSERT(largest > medium2);
+ CPPUNIT_ASSERT(!(largest > largest));
+ // Currently >=, <= and != is deducted from the above, so not
+ // checking separately
+
+ // Serialization
+ Type t;
+ std::unique_ptr<ByteBuffer> buf(smallest.serialize());
+ buf->flip();
+ deserialize(*buf, t);
+ CPPUNIT_ASSERT_EQUAL(smallest, t);
+
+ buf = medium1.serialize();
+ buf->flip();
+ deserialize(*buf, t);
+ CPPUNIT_ASSERT_EQUAL(medium1, t);
+ CPPUNIT_ASSERT_EQUAL(medium2, t);
+
+ buf = largest.serialize();
+ buf->flip();
+ deserialize(*buf, t);
+ CPPUNIT_ASSERT_EQUAL(largest, t);
+
+ // Assignment
+ CPPUNIT_ASSERT_EQUAL(smallest, t = smallest);
+ CPPUNIT_ASSERT_EQUAL(medium1, t = medium1);
+ CPPUNIT_ASSERT_EQUAL(largest, t = largest);
+
+ Type t1(smallest);
+ Type t2(medium1);
+ Type t3(medium2);
+ Type t4(largest);
+ CPPUNIT_ASSERT_EQUAL(smallest, t1);
+ CPPUNIT_ASSERT_EQUAL(medium1, t2);
+ CPPUNIT_ASSERT_EQUAL(medium2, t3);
+ CPPUNIT_ASSERT_EQUAL(largest, t4);
+
+ t.assign(smallest);
+ CPPUNIT_ASSERT_EQUAL(smallest, t);
+ t.assign(medium2);
+ CPPUNIT_ASSERT_EQUAL(medium1, t);
+ t.assign(largest);
+ CPPUNIT_ASSERT_EQUAL(largest, t);
+
+ // Catch errors and say what type there were trouble with.
+ } catch (std::exception& e) {
+ std::cerr << "\nFailed for type " << *smallest.getDataType()
+ << "\n";
+ throw;
+ }
+ }
+
+ template<typename Literal>
+ void
+ testLiteral()
+ {
+ testCommon(Literal(),
+ Literal("bar"),
+ Literal("bar"),
+ Literal("foo"));
+ Literal value("foo");
+ // Textual output
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), value.toString(false, ""));
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), value.toString(true, " "));
+ CPPUNIT_ASSERT_EQUAL(std::string("<value>foo</value>\n"),
+ value.toXml(" "));
+
+ // Conversion
+ CPPUNIT_ASSERT_EQUAL(typename Literal::string(value.getAsString()), value.getValue());
+
+ // Operator =
+ value = "anotherVal";
+ CPPUNIT_ASSERT_EQUAL(typename Literal::string("anotherVal"), value.getValue());
+ value = std::string("yetAnotherVal");
+ CPPUNIT_ASSERT_EQUAL(typename Literal::string("yetAnotherVal"), value.getValue());
+
+ // Test that a just deserialized value can be serialized again
+ // (literals have lazy deserialization so behaves diff then
+ value = "foo";
+ std::unique_ptr<ByteBuffer> buf(value.serialize());
+ buf->flip();
+ Literal value2("Other");
+ deserialize(*buf, value2);
+ buf = value2.serialize();
+ buf->flip();
+ deserialize(*buf, value2);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+
+ // Verify that get value ref gives us ref within original bytebuffer
+ // (operator== use above should not modify this)
+ buf = value.serialize();
+ buf->flip();
+ deserialize(*buf, value2);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(3), value2.getValueRef().size());
+ // Zero termination
+ CPPUNIT_ASSERT(*(value2.getValueRef().c_str() + value2.getValueRef().size()) == '\0');
+ }
+
+}
+
+void
+PrimitiveFieldValueTest::testLiterals()
+{
+ testLiteral<StringFieldValue>();
+}
+
+void
+PrimitiveFieldValueTest::testRaw()
+{
+ testCommon(RawFieldValue(),
+ RawFieldValue("bar\0bar", 7),
+ RawFieldValue("bar\0bar", 7),
+ RawFieldValue("bar\0other", 9));
+ RawFieldValue value("\tfoo\0\r\n", 7);
+ // Textual output
+ CPPUNIT_ASSERT_EQUAL(std::string(
+ "0: 09 66 6f 6f 00 0d 0a .foo..."),
+ value.toString(false, ""));
+ CPPUNIT_ASSERT_EQUAL(std::string(
+ "0: 09 66 6f 6f 00 0d 0a .foo..."),
+ value.toString(true, " "));
+ CPPUNIT_ASSERT_EQUAL(std::string(
+ "<value binaryencoding=\"base64\">CWZvbwANCg==</value>\n"),
+ value.toXml(" "));
+
+ value.setValue("grmpf", 4);
+ CPPUNIT_ASSERT(strncmp("grmpf", value.getValueRef().c_str(),
+ value.getValueRef().size()) == 0);
+}
+
+#define ASSERT_FAILED_CONV(getter, totype, floating) \
+{ \
+ totype toType; \
+ FieldValue::UP copy(value.clone()); \
+ try{ \
+ getter; \
+ std::ostringstream ost; \
+ ost << "Conversion unexpectedly worked from max value of " \
+ << *value.getDataType() << " to " << *toType.getDataType(); \
+ CPPUNIT_FAIL(ost.str().c_str()); \
+ } catch (std::exception& e) { \
+ CPPUNIT_ASSERT_EQUAL( \
+ std::string("bad numeric conversion: positive overflow"), \
+ std::string(e.what())); \
+ } \
+ /* Verify that we can convert to smaller type if value is within \
+ range. Only tests integer to integer. No floating point. */ \
+ if (!floating) { \
+ totype::Number maxV = std::numeric_limits<totype::Number>::max(); \
+ value.setValue((Number) maxV); \
+ getter; \
+ } \
+ value.assign(*copy); \
+}
+
+namespace {
+
+ template<typename Numeric>
+ void
+ testNumeric(const std::string& maxVal, bool floatingPoint)
+ {
+ typedef typename Numeric::Number Number;
+ Number maxValue(std::numeric_limits<Number>::max());
+ // Test common fieldvalue stuff
+ testCommon(Numeric(),
+ Numeric(Number(1)),
+ Numeric(Number(1)),
+ Numeric(Number(maxValue)));
+ Numeric value;
+ value.setValue(maxValue);
+ // Test textual output
+ CPPUNIT_ASSERT_EQUAL(maxVal, value.toString(false, ""));
+ CPPUNIT_ASSERT_EQUAL(maxVal, value.toString(true, " "));
+ CPPUNIT_ASSERT_EQUAL("<value>" + maxVal + "</value>\n",
+ value.toXml(" "));
+ // Test numeric conversions
+ //
+ // Currently, all safe conversion works. For instance, a byte can be
+ // converted to a long, a long can be converted to a byte, given
+ // that it has a value in the range -128 to 127.
+ //
+ // All integers will also convert automatically to floating point
+ // numbers. No checks is done as to how precise floating point
+ // representation can keep the value.
+ if (floatingPoint || sizeof(Number) > sizeof(unsigned char)) {
+ // No longer throws. This is guarded on the perimeter by java code.
+ // ASSERT_FAILED_CONV(value.getAsByte(), ByteFieldValue, floatingPoint);
+ } else {
+ CPPUNIT_ASSERT_EQUAL((char) maxValue, value.getAsByte());
+ }
+ if (floatingPoint || sizeof(Number) > sizeof(int32_t)) {
+ // No longer throws. This is guarded on the perimeter by java code.
+ // ASSERT_FAILED_CONV(value.getAsInt(), IntFieldValue, floatingPoint);
+ } else {
+ CPPUNIT_ASSERT_EQUAL((int32_t) maxValue, value.getAsInt());
+ }
+ if (floatingPoint || sizeof(Number) > sizeof(int64_t)) {
+ // No longer throws. This is guarded on the perimeter by java code.
+ // ASSERT_FAILED_CONV(value.getAsLong(), LongFieldValue, floatingPoint);
+ } else {
+ CPPUNIT_ASSERT_EQUAL((int64_t) maxValue, value.getAsLong());
+ }
+ if (floatingPoint && sizeof(Number) > sizeof(float)) {
+ // No longer throws. This is guarded on the perimeter by java code.
+ // ASSERT_FAILED_CONV(value.getAsFloat(), FloatFieldValue, true);
+ } else {
+ CPPUNIT_ASSERT_EQUAL((float) maxValue, value.getAsFloat());
+ }
+ CPPUNIT_ASSERT_EQUAL((double) maxValue, value.getAsDouble());
+ // Test some simple conversions
+ Numeric a(0);
+ a = std::string("5");
+ CPPUNIT_ASSERT_EQUAL(5, a.getAsInt());
+ }
+
+}
+
+void
+PrimitiveFieldValueTest::testFloatDoubleCasts()
+{
+ float inf(std::numeric_limits<float>::infinity());
+ CPPUNIT_ASSERT_EQUAL(inf, static_cast<float>(static_cast<double>(inf)));
+}
+
+void
+PrimitiveFieldValueTest::testNumerics()
+{
+ testNumeric<ByteFieldValue>("127", false);
+ testNumeric<ShortFieldValue>("32767", false);
+ testNumeric<IntFieldValue>("2147483647", false);
+ testNumeric<LongFieldValue>("9223372036854775807", false);
+ testNumeric<FloatFieldValue>("3.40282e+38", true);
+ testNumeric<DoubleFieldValue>("1.79769e+308", true);
+
+ // Test range
+ ByteFieldValue b1(-128);
+ ByteFieldValue b2(-1);
+ CPPUNIT_ASSERT_EQUAL(-128, (int) b1.getValue());
+ CPPUNIT_ASSERT_EQUAL(-1, (int) b2.getValue());
+
+ ShortFieldValue s1(-32768);
+ ShortFieldValue s2(65535);
+ CPPUNIT_ASSERT_EQUAL((int16_t)-32768, s1.getValue());
+ CPPUNIT_ASSERT_EQUAL((int16_t)65535, s2.getValue());
+ CPPUNIT_ASSERT_EQUAL((int16_t)-1, s2.getValue());
+
+ IntFieldValue i1(-2147483647-1);
+ IntFieldValue i2(4294967295U);
+
+ CPPUNIT_ASSERT_EQUAL((int) -2147483647-1, i1.getValue());
+ CPPUNIT_ASSERT_EQUAL((int) -1, i2.getValue());
+
+ LongFieldValue l1(-9223372036854775807ll-1);
+ LongFieldValue l2(18446744073709551615ull);
+
+ CPPUNIT_ASSERT_EQUAL((int64_t) -9223372036854775807ll-1, l1.getValue());
+ CPPUNIT_ASSERT_EQUAL((int64_t) -1, l2.getValue());
+
+ b1 = "-128";
+ b2 = "255";
+
+ CPPUNIT_ASSERT_EQUAL(-128, (int) b1.getValue());
+ CPPUNIT_ASSERT_EQUAL(-1, (int) b2.getValue());
+ i1 = "-2147483648";
+ i2 = "4294967295";
+ CPPUNIT_ASSERT_EQUAL((int) -2147483647-1, i1.getValue());
+ CPPUNIT_ASSERT_EQUAL((int) -1, i2.getValue());
+
+ l1 = "-9223372036854775808";
+ l2 = "18446744073709551615";
+
+ int64_t bnv = -1;
+ bnv <<= 63;
+ CPPUNIT_ASSERT_EQUAL(bnv, l1.getValue());
+ CPPUNIT_ASSERT_EQUAL((int64_t) -9223372036854775807ll-1, l1.getValue());
+ CPPUNIT_ASSERT_EQUAL((int64_t) -1, l2.getValue());
+
+ // Test some special cases for bytes
+ // (as unsigned char is not always handled as a number)
+ b1 = "0xff";
+ CPPUNIT_ASSERT_EQUAL(-1, (int) b1.getValue());
+ b1 = "53";
+ CPPUNIT_ASSERT_EQUAL(53, (int) b1.getValue());
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("53"), b1.getAsString());
+
+ try{
+ b1 = "-129";
+ CPPUNIT_FAIL("Expected -129 to be invalid byte");
+ } catch (std::exception& e) {}
+ try{
+ b1 = "256";
+ CPPUNIT_FAIL("Expected -129 to be invalid byte");
+ } catch (std::exception& e) {}
+ try{
+ s1 = "-32769";
+ CPPUNIT_FAIL("Expected -32769 to be invalid int");
+ } catch (std::exception& e) {}
+ try{
+ s1 = "65536";
+ CPPUNIT_FAIL("Expected 65536 to be invalid int");
+ } catch (std::exception& e) {}
+ try{
+ i1 = "-2147483649";
+ CPPUNIT_FAIL("Expected -2147483649 to be invalid int");
+ } catch (std::exception& e) {}
+ try{
+ i1 = "4294967296";
+ CPPUNIT_FAIL("Expected 4294967296 to be invalid int");
+ } catch (std::exception& e) {}
+ try{
+ l1 = "-9223372036854775809";
+ CPPUNIT_FAIL("Expected -9223372036854775809 to be invalid long");
+ } catch (std::exception& e) {}
+ try{
+ l1 = "18446744073709551616";
+ CPPUNIT_FAIL("Expected 18446744073709551616 to be invalid long");
+ } catch (std::exception& e) {}
+}
+
+} // document
diff --git a/document/src/tests/repo/.gitignore b/document/src/tests/repo/.gitignore
new file mode 100644
index 00000000000..bd4e77abb07
--- /dev/null
+++ b/document/src/tests/repo/.gitignore
@@ -0,0 +1,5 @@
+*.So
+*_test
+.depend
+Makefile
+document_documenttyperepo_test_app
diff --git a/document/src/tests/repo/CMakeLists.txt b/document/src/tests/repo/CMakeLists.txt
new file mode 100644
index 00000000000..efd74caf954
--- /dev/null
+++ b/document/src/tests/repo/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_documenttyperepo_test_app
+ SOURCES
+ documenttyperepo_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_documenttyperepo_test_app COMMAND document_documenttyperepo_test_app)
diff --git a/document/src/tests/repo/documenttyperepo_test.cpp b/document/src/tests/repo/documenttyperepo_test.cpp
new file mode 100644
index 00000000000..003bd1698af
--- /dev/null
+++ b/document/src/tests/repo/documenttyperepo_test.cpp
@@ -0,0 +1,519 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for documenttyperepo.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("documenttyperepo_test");
+
+#include <vespa/config/print/asciiconfigwriter.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/datatype/annotationreferencedatatype.h>
+#include <vespa/document/datatype/arraydatatype.h>
+#include <vespa/document/datatype/documenttype.h>
+#include <vespa/document/datatype/mapdatatype.h>
+#include <vespa/document/datatype/weightedsetdatatype.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <stdlib.h>
+#include <vespa/vespalib/objects/identifiable.h>
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <set>
+#include <vespa/config/helper/configgetter.h>
+
+using config::AsciiConfigWriter;
+using std::set;
+using std::vector;
+using vespalib::Identifiable;
+using vespalib::IllegalArgumentException;
+using vespalib::string;
+
+using namespace document::config_builder;
+using namespace document;
+
+namespace {
+
+const string type_name = "test";
+const int32_t doc_type_id = 787121340;
+const string header_name = type_name + ".header";
+const int32_t header_id = 30;
+const string body_name = type_name + ".body";
+const int32_t body_id = 31;
+const string type_name_2 = "test_2";
+const string header_name_2 = type_name_2 + ".header";
+const string body_name_2 = type_name_2 + ".body";
+const int32_t comp_level = 10;
+const int32_t comp_minres = 80;
+const size_t comp_minsize = 120;
+const string field_name = "field_name";
+const string derived_name = "derived";
+
+TEST("requireThatDocumentTypeCanBeLookedUp") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name).setId(header_id),
+ Struct(body_name).setId(body_id));
+ DocumentTypeRepo repo(builder.config());
+
+ const DocumentType *type = repo.getDocumentType(type_name);
+ ASSERT_TRUE(type);
+ EXPECT_EQUAL(type_name, type->getName());
+ EXPECT_EQUAL(doc_type_id, type->getId());
+ EXPECT_EQUAL(header_name, type->getFieldsType().getName());
+/*
+ TODO(vekterli): Check fields struct ID after it has been determined which ID it should get
+ EXPECT_EQUAL(header_id, type->getHeader().getId());
+ EXPECT_EQUAL(header_name, type->getHeader().getName());
+ EXPECT_EQUAL(body_id, type->getBody().getId());
+ EXPECT_EQUAL(body_name, type->getBody().getName());
+*/
+}
+
+TEST("requireThatDocumentTypeCanBeLookedUpWhenIdIsNotAHash") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id + 2, type_name,
+ Struct(header_name).setId(header_id),
+ Struct(body_name).setId(body_id));
+ DocumentTypeRepo repo(builder.config());
+
+ const DocumentType *type = repo.getDocumentType(type_name);
+ ASSERT_TRUE(type);
+}
+
+TEST("requireThatStructsCanConfigureCompression") {
+ DocumenttypesConfigBuilderHelper builder;
+ typedef DocumenttypesConfig::Documenttype::Datatype::Sstruct Sstruct;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).setCompression(
+ Sstruct::Compression::LZ4,
+ comp_level, comp_minres, comp_minsize));
+ DocumentTypeRepo repo(builder.config());
+
+ const CompressionConfig &comp_config =
+ repo.getDocumentType(type_name)->getFieldsType()
+ .getCompressionConfig();
+ EXPECT_EQUAL(CompressionConfig::LZ4, comp_config.type);
+ EXPECT_EQUAL(comp_level, comp_config.compressionLevel);
+ EXPECT_EQUAL(comp_minres, comp_config.threshold);
+ EXPECT_EQUAL(comp_minsize, comp_config.minSize);
+}
+
+TEST("requireThatStructsCanHaveFields") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name, DataType::T_INT));
+ DocumentTypeRepo repo(builder.config());
+
+ const StructDataType &s = repo.getDocumentType(type_name)->getFieldsType();
+ ASSERT_EQUAL(1u, s.getFieldCount());
+ const Field &field = s.getField(field_name);
+ EXPECT_EQUAL(DataType::T_INT, field.getDataType().getId());
+}
+
+template <typename T>
+const T &getFieldDataType(const DocumentTypeRepo &repo) {
+ const DataType &d = repo.getDocumentType(type_name)
+ ->getFieldsType().getField(field_name).getDataType();
+ const T *t = dynamic_cast<const T *>(&d);
+ ASSERT_TRUE(t);
+ return *t;
+}
+
+TEST("requireThatArraysCanBeConfigured") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name,
+ Array(DataType::T_STRING)));
+ DocumentTypeRepo repo(builder.config());
+
+ const ArrayDataType &a = getFieldDataType<ArrayDataType>(repo);
+ EXPECT_EQUAL(DataType::T_STRING, a.getNestedType().getId());
+}
+
+TEST("requireThatWsetsCanBeConfigured") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name,
+ Wset(DataType::T_INT)
+ .removeIfZero().createIfNonExistent()));
+ DocumentTypeRepo repo(builder.config());
+
+ const WeightedSetDataType &w = getFieldDataType<WeightedSetDataType>(repo);
+ EXPECT_EQUAL(DataType::T_INT, w.getNestedType().getId());
+ EXPECT_TRUE(w.createIfNonExistent());
+ EXPECT_TRUE(w.removeIfZero());
+}
+
+TEST("requireThatMapsCanBeConfigured") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name,
+ Map(DataType::T_INT, DataType::T_STRING)));
+ DocumentTypeRepo repo(builder.config());
+
+ const MapDataType &m = getFieldDataType<MapDataType>(repo);
+ EXPECT_EQUAL(DataType::T_INT, m.getKeyType().getId());
+ EXPECT_EQUAL(DataType::T_STRING, m.getValueType().getId());
+}
+
+TEST("requireThatAnnotationReferencesCanBeConfigured") {
+ int32_t annotation_type_id = 424;
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name,
+ AnnotationRef(annotation_type_id)))
+ .annotationType(annotation_type_id, "foo", -1);
+ DocumentTypeRepo repo(builder.config());
+
+ const AnnotationReferenceDataType &ar =
+ getFieldDataType<AnnotationReferenceDataType>(repo);
+ EXPECT_EQUAL(annotation_type_id, ar.getAnnotationType().getId());
+}
+
+TEST("requireThatFieldsCanNotBeHeaderAndBody") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name).addField(field_name,
+ DataType::T_STRING),
+ Struct(body_name).addField(field_name,
+ DataType::T_INT));
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException,
+ "Failed to add field 'field_name' to struct 'test.header': "
+ "Name in use by field with different id");
+}
+
+TEST("requireThatDocumentStructsAreCalledHeaderAndBody") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name, Struct("foo"), Struct("bar"));
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException,
+ "Previously defined as \"test.header\".");
+}
+
+TEST("requireThatDocumentsCanInheritFields") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name, DataType::T_INT));
+ builder.document(doc_type_id + 1, derived_name,
+ Struct("derived.header"),
+ Struct("derived.body").addField("derived_field",
+ DataType::T_STRING))
+ .inherit(doc_type_id);
+ DocumentTypeRepo repo(builder.config());
+
+ const StructDataType &s =
+ repo.getDocumentType(doc_type_id + 1)->getFieldsType();
+ ASSERT_EQUAL(2u, s.getFieldCount());
+ const Field &field = s.getField(field_name);
+ const DataType &type = field.getDataType();
+ EXPECT_EQUAL(DataType::T_INT, type.getId());
+}
+
+TEST("requireThatDocumentsCanUseInheritedTypes") {
+ const int32_t id = 64;
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField("foo",
+ Array(DataType::T_INT).setId(id)));
+ builder.document(doc_type_id + 1, derived_name,
+ Struct("derived.header"),
+ Struct("derived.body").addField(field_name, id))
+ .inherit(doc_type_id);
+ DocumentTypeRepo repo(builder.config());
+
+ const DataType &type =
+ repo.getDocumentType(doc_type_id + 1)->getFieldsType()
+ .getField(field_name).getDataType();
+ EXPECT_EQUAL(id, type.getId());
+ EXPECT_TRUE(dynamic_cast<const ArrayDataType *>(&type));
+}
+
+TEST("requireThatIllegalConfigsCausesExceptions") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name), Struct(body_name))
+ .inherit(doc_type_id + 1);
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Unable to find document");
+
+ builder = DocumenttypesConfigBuilderHelper();
+ builder.document(doc_type_id, type_name,
+ Struct(header_name), Struct(body_name));
+ builder.config().documenttype[0].datatype[0].type =
+ DocumenttypesConfig::Documenttype::Datatype::Type(-1);
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Unknown datatype type -1");
+
+ builder = DocumenttypesConfigBuilderHelper();
+ const int id = 10000;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name,
+ Array(DataType::T_INT).setId(id)));
+ EXPECT_EQUAL(id, builder.config().documenttype[0].datatype[1].id);
+ builder.config().documenttype[0].datatype[1].array.element.id = id;
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Unknown datatype 10000");
+
+ builder = DocumenttypesConfigBuilderHelper();
+ builder.document(doc_type_id, type_name,
+ Struct(header_name).setId(header_id),
+ Struct(body_name).addField("foo",
+ Struct("bar").setId(header_id)));
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Redefinition of data type");
+
+ builder = DocumenttypesConfigBuilderHelper();
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name,
+ AnnotationRef(id)));
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Unknown AnnotationType");
+
+ builder = DocumenttypesConfigBuilderHelper();
+ builder.document(doc_type_id, type_name,
+ Struct(header_name), Struct(body_name))
+ .annotationType(id, type_name, DataType::T_STRING)
+ .annotationType(id, type_name, DataType::T_INT);
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Redefinition of annotation type");
+
+ builder = DocumenttypesConfigBuilderHelper();
+ builder.document(doc_type_id, type_name,
+ Struct(header_name), Struct(body_name))
+ .annotationType(id, type_name, DataType::T_STRING)
+ .annotationType(id, "foobar", DataType::T_STRING);
+ EXPECT_EXCEPTION(DocumentTypeRepo repo(builder.config()),
+ IllegalArgumentException, "Redefinition of annotation type");
+}
+
+TEST("requireThatDataTypesCanBeLookedUpById") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name).setId(header_id), Struct(body_name));
+ builder.document(doc_type_id + 1, type_name_2,
+ Struct(header_name_2), Struct(body_name_2));
+ DocumentTypeRepo repo(builder.config());
+
+ const DataType *type =
+ repo.getDataType(*repo.getDocumentType(doc_type_id), header_id);
+ ASSERT_TRUE(type);
+ EXPECT_EQUAL(header_name, type->getName());
+ EXPECT_EQUAL(header_id, type->getId());
+
+ ASSERT_TRUE(!repo.getDataType(*repo.getDocumentType(doc_type_id), -1));
+ ASSERT_TRUE(!repo.getDataType(*repo.getDocumentType(doc_type_id + 1),
+ header_id));
+}
+
+TEST("requireThatDataTypesCanBeLookedUpByName") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name).setId(header_id),
+ Struct(body_name));
+ builder.document(doc_type_id + 1, type_name_2,
+ Struct(header_name_2), Struct(body_name_2));
+ DocumentTypeRepo repo(builder.config());
+
+ const DataType *type =
+ repo.getDataType(*repo.getDocumentType(doc_type_id), header_name);
+ ASSERT_TRUE(type);
+ EXPECT_EQUAL(header_name, type->getName());
+ EXPECT_EQUAL(header_id, type->getId());
+
+ EXPECT_TRUE(repo.getDataType(*repo.getDocumentType(doc_type_id),
+ type_name));
+ EXPECT_TRUE(!repo.getDataType(*repo.getDocumentType(doc_type_id),
+ field_name));
+ EXPECT_TRUE(!repo.getDataType(*repo.getDocumentType(doc_type_id + 1),
+ body_name));
+}
+
+TEST("requireThatInheritingDocCanRedefineIdenticalField") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name)
+ .addField(field_name, DataType::T_STRING)
+ .setId(body_id));
+ builder.document(doc_type_id + 1, derived_name,
+ Struct("derived.header"),
+ Struct("derived.body")
+ .addField(field_name, DataType::T_STRING)
+ .setId(body_id))
+ .inherit(doc_type_id);
+ DocumentTypeRepo repo(builder.config());
+
+ const StructDataType &s = repo.getDocumentType(doc_type_id + 1)->getFieldsType();
+ ASSERT_EQUAL(1u, s.getFieldCount());
+}
+
+TEST("requireThatAnnotationTypesCanBeConfigured") {
+ const int32_t a_id = 654;
+ const string a_name = "annotation_name";
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name), Struct(body_name))
+ .annotationType(a_id, a_name, DataType::T_STRING);
+ DocumentTypeRepo repo(builder.config());
+
+ const DocumentType *type = repo.getDocumentType(doc_type_id);
+ const AnnotationType *a_type = repo.getAnnotationType(*type, a_id);
+ ASSERT_TRUE(a_type);
+ EXPECT_EQUAL(a_name, a_type->getName());
+ ASSERT_TRUE(a_type->getDataType());
+ EXPECT_EQUAL(DataType::T_STRING, a_type->getDataType()->getId());
+}
+
+TEST("requireThatFieldIdV6IsDifferentFromV7") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name, DataType::T_INT));
+ DocumentTypeRepo repo(builder.config());
+
+ const StructDataType &s = repo.getDocumentType(type_name)->getFieldsType();
+ ASSERT_EQUAL(1u, s.getFieldCount());
+ const Field &field = s.getField(field_name);
+ ASSERT_TRUE(field.getId(7) != field.getId(6));
+}
+
+TEST("requireThatDocumentsCanUseOtherDocumentTypes") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id + 1, type_name_2,
+ Struct(header_name_2),
+ Struct(body_name_2));
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(type_name_2,
+ doc_type_id + 1).setId(body_id));
+ DocumentTypeRepo repo(builder.config());
+
+ const DataType &type = repo.getDocumentType(doc_type_id)->getFieldsType()
+ .getField(type_name_2).getDataType();
+ EXPECT_EQUAL(doc_type_id + 1, type.getId());
+ EXPECT_TRUE(dynamic_cast<const DocumentType *>(&type));
+}
+
+void storeId(set<int> *s, const DocumentType &type) {
+ s->insert(type.getId());
+}
+
+TEST("requireThatDocumentTypesCanBeIterated") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name), Struct(body_name));
+ builder.document(doc_type_id + 1, type_name_2,
+ Struct(header_name_2), Struct(body_name_2));
+ DocumentTypeRepo repo(builder.config());
+
+ set<int> ids;
+ repo.forEachDocumentType(*makeClosure(storeId, &ids));
+
+ EXPECT_EQUAL(3u, ids.size());
+ ASSERT_TRUE(ids.count(DataType::T_DOCUMENT));
+ ASSERT_TRUE(ids.count(doc_type_id));
+ ASSERT_TRUE(ids.count(doc_type_id + 1));
+}
+
+TEST("requireThatDocumentLookupChecksName") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name_2,
+ Struct(header_name_2), Struct(body_name_2));
+ DocumentTypeRepo repo(builder.config());
+
+ // "type_name" will generate the document type id
+ // "doc_type_id". However, this config assigns that id to a
+ // different type.
+ const DocumentType *type = repo.getDocumentType(type_name);
+ ASSERT_TRUE(!type);
+}
+
+TEST("requireThatBuildFromConfigWorks") {
+ DocumentTypeRepo repo(readDocumenttypesConfig("documenttypes.cfg"));
+ ASSERT_TRUE(repo.getDocumentType("document"));
+ ASSERT_TRUE(repo.getDocumentType("types"));
+ ASSERT_TRUE(repo.getDocumentType("types_search"));
+}
+
+TEST("requireThatStructsCanBeRecursive") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name).setId(header_id).addField(field_name,
+ header_id),
+ Struct(body_name));
+ DocumentTypeRepo repo(builder.config());
+
+ const StructDataType &s = repo.getDocumentType(type_name)->getFieldsType();
+ ASSERT_EQUAL(1u, s.getFieldCount());
+}
+
+} // namespace
+
+TEST("requireThatMissingFileCausesException") {
+ EXPECT_EXCEPTION(readDocumenttypesConfig("illegal/missing_file"),
+ IllegalArgumentException, "Unable to open file");
+}
+
+TEST("requireThatFieldsCanHaveAnyDocumentType") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name,
+ Struct(header_name),
+ Struct(body_name).addField(field_name, doc_type_id + 1));
+ // Circular dependency
+ builder.document(doc_type_id + 1, type_name_2,
+ Struct(header_name_2),
+ Struct(body_name_2).addField(field_name, doc_type_id));
+ DocumentTypeRepo repo(builder.config());
+ const DocumentType *type1 = repo.getDocumentType(doc_type_id);
+ const DocumentType *type2 = repo.getDocumentType(doc_type_id + 1);
+ ASSERT_TRUE(type1);
+ EXPECT_EQUAL(type1, repo.getDataType(*type1, doc_type_id));
+ EXPECT_EQUAL(type2, repo.getDataType(*type1, doc_type_id + 1));
+ EXPECT_TRUE(type1->getFieldsType().hasField(field_name));
+ ASSERT_TRUE(type2);
+ EXPECT_EQUAL(type1, repo.getDataType(*type2, doc_type_id));
+ EXPECT_EQUAL(type2, repo.getDataType(*type2, doc_type_id + 1));
+ EXPECT_TRUE(type2->getFieldsType().hasField(field_name));
+}
+
+TEST("requireThatBodyCanOccurBeforeHeaderInConfig") {
+ DocumenttypesConfigBuilderHelper builder;
+ // Add header and body in reverse order, then swap the ids.
+ builder.document(doc_type_id, type_name,
+ Struct(body_name).setId(body_id).addField("bodystuff",
+ DataType::T_STRING),
+ Struct(header_name).setId(header_id).addField(
+ "headerstuff", DataType::T_INT));
+ std::swap(builder.config().documenttype[0].headerstruct,
+ builder.config().documenttype[0].bodystruct);
+
+ DocumentTypeRepo repo(builder.config());
+ const StructDataType &s = repo.getDocumentType(type_name)->getFieldsType();
+ // Should have both fields in fields struct
+ EXPECT_TRUE(s.hasField("headerstuff"));
+ EXPECT_TRUE(s.hasField("bodystuff"));
+}
+
+TEST("Require that Array can have nested DocumentType") {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, type_name, Struct(header_name),
+ Struct(body_name)
+ .addField(field_name, Array(doc_type_id)));
+ DocumentTypeRepo repo(builder.config());
+ const DocumentType *type = repo.getDocumentType(doc_type_id);
+ ASSERT_TRUE(type);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/repo/documenttypes.cfg b/document/src/tests/repo/documenttypes.cfg
new file mode 100644
index 00000000000..4641663a94b
--- /dev/null
+++ b/document/src/tests/repo/documenttypes.cfg
@@ -0,0 +1,421 @@
+documenttype[2]
+documenttype[0].bodystruct 348447225
+documenttype[0].headerstruct 1328581348
+documenttype[0].id -853072901
+documenttype[0].name "types"
+documenttype[0].version 0
+documenttype[0].datatype[27]
+documenttype[0].datatype[00].id 49942803
+documenttype[0].datatype[00].type ARRAY
+documenttype[0].datatype[00].array.element.id 16
+documenttype[0].datatype[01].id 339965458
+documenttype[0].datatype[01].type MAP
+documenttype[0].datatype[01].map.key.id 2
+documenttype[0].datatype[01].map.value.id 2
+documenttype[0].datatype[02].id 69621385
+documenttype[0].datatype[02].type ARRAY
+documenttype[0].datatype[02].array.element.id 339965458
+documenttype[0].datatype[03].id -2092985853
+documenttype[0].datatype[03].type STRUCT
+documenttype[0].datatype[03].sstruct.name "mystruct"
+documenttype[0].datatype[03].sstruct.version 0
+documenttype[0].datatype[03].sstruct.field[4]
+documenttype[0].datatype[03].sstruct.field[bytearr].datatype 49942803
+documenttype[0].datatype[03].sstruct.field[bytearr].id 1079701754
+documenttype[0].datatype[03].sstruct.field[bytearr].id_v6 1198855694
+documenttype[0].datatype[03].sstruct.field[bytearr].name "bytearr"
+documenttype[0].datatype[03].sstruct.field[mymap].datatype 339965458
+documenttype[0].datatype[03].sstruct.field[mymap].id 1954178122
+documenttype[0].datatype[03].sstruct.field[mymap].id_v6 707189723
+documenttype[0].datatype[03].sstruct.field[mymap].name "mymap"
+documenttype[0].datatype[03].sstruct.field[structfield].datatype 2
+documenttype[0].datatype[03].sstruct.field[structfield].id 1726890940
+documenttype[0].datatype[03].sstruct.field[structfield].id_v6 418303145
+documenttype[0].datatype[03].sstruct.field[structfield].name "structfield"
+documenttype[0].datatype[03].sstruct.field[title].datatype 2
+documenttype[0].datatype[03].sstruct.field[title].id 567626448
+documenttype[0].datatype[03].sstruct.field[title].id_v6 29129762
+documenttype[0].datatype[03].sstruct.field[title].name "title"
+documenttype[0].datatype[04].id 759956026
+documenttype[0].datatype[04].type ARRAY
+documenttype[0].datatype[04].array.element.id -2092985853
+documenttype[0].datatype[05].id 2125328771
+documenttype[0].datatype[05].type WSET
+documenttype[0].datatype[05].wset.createifnonexistent false
+documenttype[0].datatype[05].wset.removeifzero true
+documenttype[0].datatype[05].wset.key.id 2
+documenttype[0].datatype[06].id -1486737430
+documenttype[0].datatype[06].type ARRAY
+documenttype[0].datatype[06].array.element.id 2
+documenttype[0].datatype[07].id 1707615575
+documenttype[0].datatype[07].type ARRAY
+documenttype[0].datatype[07].array.element.id -1486737430
+documenttype[0].datatype[08].id 171503364
+documenttype[0].datatype[08].type MAP
+documenttype[0].datatype[08].map.key.id 1707615575
+documenttype[0].datatype[08].map.value.id 0
+documenttype[0].datatype[09].id 1100964733
+documenttype[0].datatype[09].type ARRAY
+documenttype[0].datatype[09].array.element.id 171503364
+documenttype[0].datatype[10].id -1715531035
+documenttype[0].datatype[10].type MAP
+documenttype[0].datatype[10].map.key.id 0
+documenttype[0].datatype[10].map.value.id 4
+documenttype[0].datatype[11].id -794985308
+documenttype[0].datatype[11].type ARRAY
+documenttype[0].datatype[11].array.element.id 1707615575
+documenttype[0].datatype[12].id 109267174
+documenttype[0].datatype[12].type STRUCT
+documenttype[0].datatype[12].sstruct.name "sct"
+documenttype[0].datatype[12].sstruct.version 0
+documenttype[0].datatype[12].sstruct.field[2]
+documenttype[0].datatype[12].sstruct.field[s1].datatype 2
+documenttype[0].datatype[12].sstruct.field[s1].id 2146820765
+documenttype[0].datatype[12].sstruct.field[s1].id_v6 142373281
+documenttype[0].datatype[12].sstruct.field[s1].name "s1"
+documenttype[0].datatype[12].sstruct.field[s2].datatype 2
+documenttype[0].datatype[12].sstruct.field[s2].id 45366795
+documenttype[0].datatype[12].sstruct.field[s2].id_v6 31106270
+documenttype[0].datatype[12].sstruct.field[s2].name "s2"
+documenttype[0].datatype[13].id -1244829667
+documenttype[0].datatype[13].type ARRAY
+documenttype[0].datatype[13].array.element.id 109267174
+documenttype[0].datatype[14].id -1865479609
+documenttype[0].datatype[14].type MAP
+documenttype[0].datatype[14].map.key.id 2
+documenttype[0].datatype[14].map.value.id 4
+documenttype[0].datatype[15].id 2138385264
+documenttype[0].datatype[15].type MAP
+documenttype[0].datatype[15].map.key.id 0
+documenttype[0].datatype[15].map.value.id 5
+documenttype[0].datatype[16].id 1328286588
+documenttype[0].datatype[16].type WSET
+documenttype[0].datatype[16].wset.createifnonexistent false
+documenttype[0].datatype[16].wset.removeifzero false
+documenttype[0].datatype[16].wset.key.id 2
+documenttype[0].datatype[17].id 294108848
+documenttype[0].datatype[17].type STRUCT
+documenttype[0].datatype[17].sstruct.name "folder"
+documenttype[0].datatype[17].sstruct.version 0
+documenttype[0].datatype[17].sstruct.field[4]
+documenttype[0].datatype[17].sstruct.field[FlagsCounter].datatype -1865479609
+documenttype[0].datatype[17].sstruct.field[FlagsCounter].id 1741227606
+documenttype[0].datatype[17].sstruct.field[FlagsCounter].id_v6 1287497652
+documenttype[0].datatype[17].sstruct.field[FlagsCounter].name "FlagsCounter"
+documenttype[0].datatype[17].sstruct.field[Name].datatype 2
+documenttype[0].datatype[17].sstruct.field[Name].id 2002760220
+documenttype[0].datatype[17].sstruct.field[Name].id_v6 62942997
+documenttype[0].datatype[17].sstruct.field[Name].name "Name"
+documenttype[0].datatype[17].sstruct.field[Version].datatype 0
+documenttype[0].datatype[17].sstruct.field[Version].id 64430502
+documenttype[0].datatype[17].sstruct.field[Version].id_v6 634243672
+documenttype[0].datatype[17].sstruct.field[Version].name "Version"
+documenttype[0].datatype[17].sstruct.field[anotherfolder].datatype 294108848
+documenttype[0].datatype[17].sstruct.field[anotherfolder].id 1582421848
+documenttype[0].datatype[17].sstruct.field[anotherfolder].id_v6 1898725199
+documenttype[0].datatype[17].sstruct.field[anotherfolder].name "anotherfolder"
+documenttype[0].datatype[18].id 2065577986
+documenttype[0].datatype[18].type WSET
+documenttype[0].datatype[18].wset.createifnonexistent true
+documenttype[0].datatype[18].wset.removeifzero false
+documenttype[0].datatype[18].wset.key.id 2
+documenttype[0].datatype[19].id 1901258752
+documenttype[0].datatype[19].type MAP
+documenttype[0].datatype[19].map.key.id 0
+documenttype[0].datatype[19].map.value.id -2092985853
+documenttype[0].datatype[20].id 348447225
+documenttype[0].datatype[20].type STRUCT
+documenttype[0].datatype[20].sstruct.name "types.body"
+documenttype[0].datatype[20].sstruct.version 0
+documenttype[0].datatype[20].sstruct.field[1]
+documenttype[0].datatype[20].sstruct.field[complexarray].datatype 1100964733
+documenttype[0].datatype[20].sstruct.field[complexarray].id 1028383787
+documenttype[0].datatype[20].sstruct.field[complexarray].id_v6 658530305
+documenttype[0].datatype[20].sstruct.field[complexarray].name "complexarray"
+documenttype[0].datatype[21].id 2125154557
+documenttype[0].datatype[21].type MAP
+documenttype[0].datatype[21].map.key.id 2
+documenttype[0].datatype[21].map.value.id 1
+documenttype[0].datatype[22].id -389833101
+documenttype[0].datatype[22].type MAP
+documenttype[0].datatype[22].map.key.id 0
+documenttype[0].datatype[22].map.value.id 294108848
+documenttype[0].datatype[23].id -1245117006
+documenttype[0].datatype[23].type ARRAY
+documenttype[0].datatype[23].array.element.id 0
+documenttype[0].datatype[24].id -1584287606
+documenttype[0].datatype[24].type MAP
+documenttype[0].datatype[24].map.key.id 2
+documenttype[0].datatype[24].map.value.id 0
+documenttype[0].datatype[25].id 435886609
+documenttype[0].datatype[25].type MAP
+documenttype[0].datatype[25].map.key.id 2
+documenttype[0].datatype[25].map.value.id -1245117006
+documenttype[0].datatype[26].id 1328581348
+documenttype[0].datatype[26].type STRUCT
+documenttype[0].datatype[26].sstruct.name "types.header"
+documenttype[0].datatype[26].sstruct.version 0
+documenttype[0].datatype[26].sstruct.field[25]
+documenttype[0].datatype[26].sstruct.field[Folders].datatype -389833101
+documenttype[0].datatype[26].sstruct.field[Folders].id 34575524
+documenttype[0].datatype[26].sstruct.field[Folders].id_v6 280569744
+documenttype[0].datatype[26].sstruct.field[Folders].name "Folders"
+documenttype[0].datatype[26].sstruct.field[abyte].datatype 16
+documenttype[0].datatype[26].sstruct.field[abyte].id 110138156
+documenttype[0].datatype[26].sstruct.field[abyte].id_v6 1369099343
+documenttype[0].datatype[26].sstruct.field[abyte].name "abyte"
+documenttype[0].datatype[26].sstruct.field[album0].datatype 18
+documenttype[0].datatype[26].sstruct.field[album0].id 764312262
+documenttype[0].datatype[26].sstruct.field[album0].id_v6 1409364160
+documenttype[0].datatype[26].sstruct.field[album0].name "album0"
+documenttype[0].datatype[26].sstruct.field[album1].datatype 18
+documenttype[0].datatype[26].sstruct.field[album1].id 1967160809
+documenttype[0].datatype[26].sstruct.field[album1].id_v6 1833811264
+documenttype[0].datatype[26].sstruct.field[album1].name "album1"
+documenttype[0].datatype[26].sstruct.field[along].datatype 4
+documenttype[0].datatype[26].sstruct.field[along].id 1206464520
+documenttype[0].datatype[26].sstruct.field[along].id_v6 871280609
+documenttype[0].datatype[26].sstruct.field[along].name "along"
+documenttype[0].datatype[26].sstruct.field[arrarr].datatype -794985308
+documenttype[0].datatype[26].sstruct.field[arrarr].id 1962567166
+documenttype[0].datatype[26].sstruct.field[arrarr].id_v6 885141301
+documenttype[0].datatype[26].sstruct.field[arrarr].name "arrarr"
+documenttype[0].datatype[26].sstruct.field[arrayfield].datatype -1245117006
+documenttype[0].datatype[26].sstruct.field[arrayfield].id 965790107
+documenttype[0].datatype[26].sstruct.field[arrayfield].id_v6 1010955705
+documenttype[0].datatype[26].sstruct.field[arrayfield].name "arrayfield"
+documenttype[0].datatype[26].sstruct.field[arraymapfield].datatype 435886609
+documenttype[0].datatype[26].sstruct.field[arraymapfield].id 1670805928
+documenttype[0].datatype[26].sstruct.field[arraymapfield].id_v6 1940354311
+documenttype[0].datatype[26].sstruct.field[arraymapfield].name "arraymapfield"
+documenttype[0].datatype[26].sstruct.field[doublemapfield].datatype 2138385264
+documenttype[0].datatype[26].sstruct.field[doublemapfield].id 877047192
+documenttype[0].datatype[26].sstruct.field[doublemapfield].id_v6 957317090
+documenttype[0].datatype[26].sstruct.field[doublemapfield].name "doublemapfield"
+documenttype[0].datatype[26].sstruct.field[floatmapfield].datatype 2125154557
+documenttype[0].datatype[26].sstruct.field[floatmapfield].id 1239120925
+documenttype[0].datatype[26].sstruct.field[floatmapfield].id_v6 1609437589
+documenttype[0].datatype[26].sstruct.field[floatmapfield].name "floatmapfield"
+documenttype[0].datatype[26].sstruct.field[intmapfield].datatype -1584287606
+documenttype[0].datatype[26].sstruct.field[intmapfield].id 121004462
+documenttype[0].datatype[26].sstruct.field[intmapfield].id_v6 1642487905
+documenttype[0].datatype[26].sstruct.field[intmapfield].name "intmapfield"
+documenttype[0].datatype[26].sstruct.field[foo].datatype 4
+documenttype[0].datatype[26].sstruct.field[foo].id 266111229
+documenttype[0].datatype[26].sstruct.field[foo].id_v6 383833303
+documenttype[0].datatype[26].sstruct.field[foo].name "foo"
+documenttype[0].datatype[26].sstruct.field[longmapfield].datatype -1715531035
+documenttype[0].datatype[26].sstruct.field[longmapfield].id 477718745
+documenttype[0].datatype[26].sstruct.field[longmapfield].id_v6 920341968
+documenttype[0].datatype[26].sstruct.field[longmapfield].name "longmapfield"
+documenttype[0].datatype[26].sstruct.field[maparr].datatype 69621385
+documenttype[0].datatype[26].sstruct.field[maparr].id 904375219
+documenttype[0].datatype[26].sstruct.field[maparr].id_v6 63700074
+documenttype[0].datatype[26].sstruct.field[maparr].name "maparr"
+documenttype[0].datatype[26].sstruct.field[mystructarr].datatype 759956026
+documenttype[0].datatype[26].sstruct.field[mystructarr].id 595856991
+documenttype[0].datatype[26].sstruct.field[mystructarr].id_v6 764861972
+documenttype[0].datatype[26].sstruct.field[mystructarr].name "mystructarr"
+documenttype[0].datatype[26].sstruct.field[mystructfield].datatype -2092985853
+documenttype[0].datatype[26].sstruct.field[mystructfield].id 1348513378
+documenttype[0].datatype[26].sstruct.field[mystructfield].id_v6 2033170300
+documenttype[0].datatype[26].sstruct.field[mystructfield].name "mystructfield"
+documenttype[0].datatype[26].sstruct.field[mystructmap].datatype 1901258752
+documenttype[0].datatype[26].sstruct.field[mystructmap].id 1511423250
+documenttype[0].datatype[26].sstruct.field[mystructmap].id_v6 449602635
+documenttype[0].datatype[26].sstruct.field[mystructmap].name "mystructmap"
+documenttype[0].datatype[26].sstruct.field[setfield].datatype 1328286588
+documenttype[0].datatype[26].sstruct.field[setfield].id 761581914
+documenttype[0].datatype[26].sstruct.field[setfield].id_v6 1762943268
+documenttype[0].datatype[26].sstruct.field[setfield].name "setfield"
+documenttype[0].datatype[26].sstruct.field[setfield2].datatype 18
+documenttype[0].datatype[26].sstruct.field[setfield2].id 1066659198
+documenttype[0].datatype[26].sstruct.field[setfield2].id_v6 813038565
+documenttype[0].datatype[26].sstruct.field[setfield2].name "setfield2"
+documenttype[0].datatype[26].sstruct.field[setfield3].datatype 2125328771
+documenttype[0].datatype[26].sstruct.field[setfield3].id 1180155772
+documenttype[0].datatype[26].sstruct.field[setfield3].id_v6 1697232199
+documenttype[0].datatype[26].sstruct.field[setfield3].name "setfield3"
+documenttype[0].datatype[26].sstruct.field[setfield4].datatype 2065577986
+documenttype[0].datatype[26].sstruct.field[setfield4].id 1254131631
+documenttype[0].datatype[26].sstruct.field[setfield4].id_v6 119755202
+documenttype[0].datatype[26].sstruct.field[setfield4].name "setfield4"
+documenttype[0].datatype[26].sstruct.field[stringmapfield].datatype 339965458
+documenttype[0].datatype[26].sstruct.field[stringmapfield].id 117465687
+documenttype[0].datatype[26].sstruct.field[stringmapfield].id_v6 1492788095
+documenttype[0].datatype[26].sstruct.field[stringmapfield].name "stringmapfield"
+documenttype[0].datatype[26].sstruct.field[structarrayfield].datatype -1244829667
+documenttype[0].datatype[26].sstruct.field[structarrayfield].id 335048518
+documenttype[0].datatype[26].sstruct.field[structarrayfield].id_v6 607034174
+documenttype[0].datatype[26].sstruct.field[structarrayfield].name "structarrayfield"
+documenttype[0].datatype[26].sstruct.field[structfield].datatype 109267174
+documenttype[0].datatype[26].sstruct.field[structfield].id 486207386
+documenttype[0].datatype[26].sstruct.field[structfield].id_v6 418303145
+documenttype[0].datatype[26].sstruct.field[structfield].name "structfield"
+documenttype[0].datatype[26].sstruct.field[tagfield].datatype 18
+documenttype[0].datatype[26].sstruct.field[tagfield].id 1653562069
+documenttype[0].datatype[26].sstruct.field[tagfield].id_v6 938523246
+documenttype[0].datatype[26].sstruct.field[tagfield].name "tagfield"
+documenttype[0].inherits[1]
+documenttype[0].inherits[document.0].id 8
+documenttype[1].bodystruct -1408707420
+documenttype[1].headerstruct 625114831
+documenttype[1].id 666563184
+documenttype[1].name "types_search"
+documenttype[1].version 0
+documenttype[1].datatype[12]
+documenttype[1].datatype[00].id 1328286588
+documenttype[1].datatype[00].type WSET
+documenttype[1].datatype[00].wset.createifnonexistent false
+documenttype[1].datatype[00].wset.removeifzero false
+documenttype[1].datatype[00].wset.key.id 2
+documenttype[1].datatype[01].id -1865479609
+documenttype[1].datatype[01].type MAP
+documenttype[1].datatype[01].map.key.id 2
+documenttype[1].datatype[01].map.value.id 4
+documenttype[1].datatype[02].id 109267174
+documenttype[1].datatype[02].type STRUCT
+documenttype[1].datatype[02].sstruct.name "sct"
+documenttype[1].datatype[02].sstruct.version 0
+documenttype[1].datatype[02].sstruct.field[2]
+documenttype[1].datatype[02].sstruct.field[s1].datatype 2
+documenttype[1].datatype[02].sstruct.field[s1].id 2146820765
+documenttype[1].datatype[02].sstruct.field[s1].id_v6 142373281
+documenttype[1].datatype[02].sstruct.field[s1].name "s1"
+documenttype[1].datatype[02].sstruct.field[s2].datatype 2
+documenttype[1].datatype[02].sstruct.field[s2].id 45366795
+documenttype[1].datatype[02].sstruct.field[s2].id_v6 31106270
+documenttype[1].datatype[02].sstruct.field[s2].name "s2"
+documenttype[1].datatype[03].id 294108848
+documenttype[1].datatype[03].type STRUCT
+documenttype[1].datatype[03].sstruct.name "folder"
+documenttype[1].datatype[03].sstruct.version 0
+documenttype[1].datatype[03].sstruct.field[4]
+documenttype[1].datatype[03].sstruct.field[FlagsCounter].datatype -1865479609
+documenttype[1].datatype[03].sstruct.field[FlagsCounter].id 1741227606
+documenttype[1].datatype[03].sstruct.field[FlagsCounter].id_v6 1287497652
+documenttype[1].datatype[03].sstruct.field[FlagsCounter].name "FlagsCounter"
+documenttype[1].datatype[03].sstruct.field[Name].datatype 2
+documenttype[1].datatype[03].sstruct.field[Name].id 2002760220
+documenttype[1].datatype[03].sstruct.field[Name].id_v6 62942997
+documenttype[1].datatype[03].sstruct.field[Name].name "Name"
+documenttype[1].datatype[03].sstruct.field[Version].datatype 0
+documenttype[1].datatype[03].sstruct.field[Version].id 64430502
+documenttype[1].datatype[03].sstruct.field[Version].id_v6 634243672
+documenttype[1].datatype[03].sstruct.field[Version].name "Version"
+documenttype[1].datatype[03].sstruct.field[anotherfolder].datatype 294108848
+documenttype[1].datatype[03].sstruct.field[anotherfolder].id 1582421848
+documenttype[1].datatype[03].sstruct.field[anotherfolder].id_v6 1898725199
+documenttype[1].datatype[03].sstruct.field[anotherfolder].name "anotherfolder"
+documenttype[1].datatype[04].id 2125328771
+documenttype[1].datatype[04].type WSET
+documenttype[1].datatype[04].wset.createifnonexistent false
+documenttype[1].datatype[04].wset.removeifzero true
+documenttype[1].datatype[04].wset.key.id 2
+documenttype[1].datatype[05].id -1245117006
+documenttype[1].datatype[05].type ARRAY
+documenttype[1].datatype[05].array.element.id 0
+documenttype[1].datatype[06].id 2065577986
+documenttype[1].datatype[06].type WSET
+documenttype[1].datatype[06].wset.createifnonexistent true
+documenttype[1].datatype[06].wset.removeifzero false
+documenttype[1].datatype[06].wset.key.id 2
+documenttype[1].datatype[07].id 339965458
+documenttype[1].datatype[07].type MAP
+documenttype[1].datatype[07].map.key.id 2
+documenttype[1].datatype[07].map.value.id 2
+documenttype[1].datatype[08].id 625114831
+documenttype[1].datatype[08].type STRUCT
+documenttype[1].datatype[08].sstruct.name "types_search.header"
+documenttype[1].datatype[08].sstruct.version 0
+documenttype[1].datatype[08].sstruct.field[15]
+documenttype[1].datatype[08].sstruct.field[abyte].datatype 16
+documenttype[1].datatype[08].sstruct.field[abyte].id 110138156
+documenttype[1].datatype[08].sstruct.field[abyte].id_v6 1369099343
+documenttype[1].datatype[08].sstruct.field[abyte].name "abyte"
+documenttype[1].datatype[08].sstruct.field[album0].datatype 18
+documenttype[1].datatype[08].sstruct.field[album0].id 764312262
+documenttype[1].datatype[08].sstruct.field[album0].id_v6 1409364160
+documenttype[1].datatype[08].sstruct.field[album0].name "album0"
+documenttype[1].datatype[08].sstruct.field[album1].datatype 18
+documenttype[1].datatype[08].sstruct.field[album1].id 1967160809
+documenttype[1].datatype[08].sstruct.field[album1].id_v6 1833811264
+documenttype[1].datatype[08].sstruct.field[album1].name "album1"
+documenttype[1].datatype[08].sstruct.field[along].datatype 4
+documenttype[1].datatype[08].sstruct.field[along].id 1206464520
+documenttype[1].datatype[08].sstruct.field[along].id_v6 871280609
+documenttype[1].datatype[08].sstruct.field[along].name "along"
+documenttype[1].datatype[08].sstruct.field[arrayfield].datatype -1245117006
+documenttype[1].datatype[08].sstruct.field[arrayfield].id 965790107
+documenttype[1].datatype[08].sstruct.field[arrayfield].id_v6 1010955705
+documenttype[1].datatype[08].sstruct.field[arrayfield].name "arrayfield"
+documenttype[1].datatype[08].sstruct.field[foo].datatype 4
+documenttype[1].datatype[08].sstruct.field[foo].id 266111229
+documenttype[1].datatype[08].sstruct.field[foo].id_v6 383833303
+documenttype[1].datatype[08].sstruct.field[foo].name "foo"
+documenttype[1].datatype[08].sstruct.field[other].datatype 4
+documenttype[1].datatype[08].sstruct.field[other].id 2443357
+documenttype[1].datatype[08].sstruct.field[other].id_v6 903806222
+documenttype[1].datatype[08].sstruct.field[other].name "other"
+documenttype[1].datatype[08].sstruct.field[rankfeatures].datatype 2
+documenttype[1].datatype[08].sstruct.field[rankfeatures].id 1883197392
+documenttype[1].datatype[08].sstruct.field[rankfeatures].id_v6 699950698
+documenttype[1].datatype[08].sstruct.field[rankfeatures].name "rankfeatures"
+documenttype[1].datatype[08].sstruct.field[setfield].datatype 1328286588
+documenttype[1].datatype[08].sstruct.field[setfield].id 761581914
+documenttype[1].datatype[08].sstruct.field[setfield].id_v6 1762943268
+documenttype[1].datatype[08].sstruct.field[setfield].name "setfield"
+documenttype[1].datatype[08].sstruct.field[setfield2].datatype 18
+documenttype[1].datatype[08].sstruct.field[setfield2].id 1066659198
+documenttype[1].datatype[08].sstruct.field[setfield2].id_v6 813038565
+documenttype[1].datatype[08].sstruct.field[setfield2].name "setfield2"
+documenttype[1].datatype[08].sstruct.field[setfield3].datatype 2125328771
+documenttype[1].datatype[08].sstruct.field[setfield3].id 1180155772
+documenttype[1].datatype[08].sstruct.field[setfield3].id_v6 1697232199
+documenttype[1].datatype[08].sstruct.field[setfield3].name "setfield3"
+documenttype[1].datatype[08].sstruct.field[setfield4].datatype 2065577986
+documenttype[1].datatype[08].sstruct.field[setfield4].id 1254131631
+documenttype[1].datatype[08].sstruct.field[setfield4].id_v6 119755202
+documenttype[1].datatype[08].sstruct.field[setfield4].name "setfield4"
+documenttype[1].datatype[08].sstruct.field[stringmapfield].datatype 339965458
+documenttype[1].datatype[08].sstruct.field[stringmapfield].id 117465687
+documenttype[1].datatype[08].sstruct.field[stringmapfield].id_v6 1492788095
+documenttype[1].datatype[08].sstruct.field[stringmapfield].name "stringmapfield"
+documenttype[1].datatype[08].sstruct.field[summaryfeatures].datatype 2
+documenttype[1].datatype[08].sstruct.field[summaryfeatures].id 1840337115
+documenttype[1].datatype[08].sstruct.field[summaryfeatures].id_v6 1981648971
+documenttype[1].datatype[08].sstruct.field[summaryfeatures].name "summaryfeatures"
+documenttype[1].datatype[08].sstruct.field[tagfield].datatype 18
+documenttype[1].datatype[08].sstruct.field[tagfield].id 1653562069
+documenttype[1].datatype[08].sstruct.field[tagfield].id_v6 938523246
+documenttype[1].datatype[08].sstruct.field[tagfield].name "tagfield"
+documenttype[1].datatype[09].id 49942803
+documenttype[1].datatype[09].type ARRAY
+documenttype[1].datatype[09].array.element.id 16
+documenttype[1].datatype[10].id -2092985853
+documenttype[1].datatype[10].type STRUCT
+documenttype[1].datatype[10].sstruct.name "mystruct"
+documenttype[1].datatype[10].sstruct.version 0
+documenttype[1].datatype[10].sstruct.field[4]
+documenttype[1].datatype[10].sstruct.field[bytearr].datatype 49942803
+documenttype[1].datatype[10].sstruct.field[bytearr].id 1079701754
+documenttype[1].datatype[10].sstruct.field[bytearr].id_v6 1198855694
+documenttype[1].datatype[10].sstruct.field[bytearr].name "bytearr"
+documenttype[1].datatype[10].sstruct.field[mymap].datatype 339965458
+documenttype[1].datatype[10].sstruct.field[mymap].id 1954178122
+documenttype[1].datatype[10].sstruct.field[mymap].id_v6 707189723
+documenttype[1].datatype[10].sstruct.field[mymap].name "mymap"
+documenttype[1].datatype[10].sstruct.field[structfield].datatype 2
+documenttype[1].datatype[10].sstruct.field[structfield].id 1726890940
+documenttype[1].datatype[10].sstruct.field[structfield].id_v6 418303145
+documenttype[1].datatype[10].sstruct.field[structfield].name "structfield"
+documenttype[1].datatype[10].sstruct.field[title].datatype 2
+documenttype[1].datatype[10].sstruct.field[title].id 567626448
+documenttype[1].datatype[10].sstruct.field[title].id_v6 29129762
+documenttype[1].datatype[10].sstruct.field[title].name "title"
+documenttype[1].datatype[11].id -1408707420
+documenttype[1].datatype[11].type STRUCT
+documenttype[1].datatype[11].sstruct.name "types_search.body"
+documenttype[1].datatype[11].sstruct.version 0
+documenttype[1].inherits[1]
+documenttype[1].inherits[document.0].id 8
diff --git a/document/src/tests/serialization/.gitignore b/document/src/tests/serialization/.gitignore
new file mode 100644
index 00000000000..9f5bc440533
--- /dev/null
+++ b/document/src/tests/serialization/.gitignore
@@ -0,0 +1,6 @@
+*_test
+.depend
+Makefile
+document_annotationserializer_test_app
+document_compression_test_app
+document_vespadocumentserializer_test_app
diff --git a/document/src/tests/serialization/CMakeLists.txt b/document/src/tests/serialization/CMakeLists.txt
new file mode 100644
index 00000000000..dcbf067e4a5
--- /dev/null
+++ b/document/src/tests/serialization/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_vespadocumentserializer_test_app
+ SOURCES
+ vespadocumentserializer_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_vespadocumentserializer_test_app COMMAND document_vespadocumentserializer_test_app)
+vespa_add_executable(document_annotationserializer_test_app
+ SOURCES
+ annotationserializer_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_annotationserializer_test_app COMMAND document_annotationserializer_test_app)
+vespa_add_executable(document_compression_test_app
+ SOURCES
+ compression_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_compression_test_app COMMAND document_compression_test_app)
diff --git a/document/src/tests/serialization/annotation.serialize.test.cfg b/document/src/tests/serialization/annotation.serialize.test.cfg
new file mode 100644
index 00000000000..a8a79ddc6f2
--- /dev/null
+++ b/document/src/tests/serialization/annotation.serialize.test.cfg
@@ -0,0 +1,77 @@
+enablecompression false
+datatype[5]
+datatype[0].id 10001
+datatype[0].arraytype[0]
+datatype[0].weightedsettype[0]
+datatype[0].structtype[1]
+datatype[0].structtype[0].name "empty"
+datatype[0].structtype[0].version 0
+datatype[0].structtype[0].field[0]
+datatype[0].structtype[0].inherits[0]
+datatype[0].documenttype[0]
+datatype[0].annotationreftype[0]
+datatype[1].id 10012
+datatype[1].arraytype[0]
+datatype[1].weightedsettype[0]
+datatype[1].structtype[1]
+datatype[1].structtype[0].name "myposition"
+datatype[1].structtype[0].version 0
+datatype[1].structtype[0].field[2]
+datatype[1].structtype[0].field[0].name "latitude"
+datatype[1].structtype[0].field[0].datatype 4
+datatype[1].structtype[0].field[0].id[0]
+datatype[1].structtype[0].field[1].name "longitude"
+datatype[1].structtype[0].field[1].datatype 4
+datatype[1].structtype[0].field[1].id[0]
+datatype[1].structtype[0].inherits[0]
+datatype[1].documenttype[0]
+datatype[1].annotationreftype[0]
+datatype[2].id 10022
+datatype[2].arraytype[0]
+datatype[2].weightedsettype[0]
+datatype[2].structtype[0]
+datatype[2].documenttype[0]
+datatype[2].annotationreftype[1]
+datatype[2].annotationreftype[0].annotation "text"
+datatype[3].id 10032
+datatype[3].arraytype[1]
+datatype[3].arraytype[0].datatype 10022
+datatype[3].weightedsettype[0]
+datatype[3].structtype[0]
+datatype[3].documenttype[0]
+datatype[3].annotationreftype[0]
+datatype[4].id 10002
+datatype[4].arraytype[0]
+datatype[4].weightedsettype[0]
+datatype[4].structtype[1]
+datatype[4].structtype[0].name "annotation.city"
+datatype[4].structtype[0].version 0
+datatype[4].structtype[0].field[2]
+datatype[4].structtype[0].field[0].name "position"
+datatype[4].structtype[0].field[0].datatype 10012
+datatype[4].structtype[0].field[0].id[0]
+datatype[4].structtype[0].field[1].name "references"
+datatype[4].structtype[0].field[1].datatype 10032
+datatype[4].structtype[0].field[1].id[0]
+datatype[4].structtype[0].inherits[0]
+datatype[4].documenttype[0]
+datatype[4].annotationreftype[0]
+annotationtype[6]
+annotationtype[0].name "text"
+annotationtype[0].id 20000
+annotationtype[0].datatype 10001
+annotationtype[1].name "begintag"
+annotationtype[1].id 20001
+annotationtype[1].datatype 10001
+annotationtype[2].name "endtag"
+annotationtype[2].id 20002
+annotationtype[2].datatype 10001
+annotationtype[3].name "body"
+annotationtype[3].id 20003
+annotationtype[3].datatype 10001
+annotationtype[4].name "paragraph"
+annotationtype[4].id 20004
+annotationtype[4].datatype 10001
+annotationtype[5].name "city"
+annotationtype[5].id 20005
+annotationtype[5].datatype 10002
diff --git a/document/src/tests/serialization/annotation.serialize.test.repo.cfg b/document/src/tests/serialization/annotation.serialize.test.repo.cfg
new file mode 100644
index 00000000000..d7fa447fa34
--- /dev/null
+++ b/document/src/tests/serialization/annotation.serialize.test.repo.cfg
@@ -0,0 +1,130 @@
+enablecompression false
+documenttype[1]
+documenttype[0].id 1000
+documenttype[0].name "my_document"
+documenttype[0].version 0
+documenttype[0].headerstruct -284186494
+documenttype[0].bodystruct -2039820521
+documenttype[0].inherits[0]
+documenttype[0].datatype[5]
+documenttype[0].datatype[0].id 10001
+documenttype[0].datatype[0].type STRUCT
+documenttype[0].datatype[0].array.element.id 0
+documenttype[0].datatype[0].map.key.id 0
+documenttype[0].datatype[0].map.value.id 0
+documenttype[0].datatype[0].wset.key.id 0
+documenttype[0].datatype[0].wset.createifnonexistent false
+documenttype[0].datatype[0].wset.removeifzero false
+documenttype[0].datatype[0].annotationref.annotation.id 0
+documenttype[0].datatype[0].sstruct.name "empty"
+documenttype[0].datatype[0].sstruct.version 0
+documenttype[0].datatype[0].sstruct.compression.type NONE
+documenttype[0].datatype[0].sstruct.compression.level 0
+documenttype[0].datatype[0].sstruct.compression.threshold 90
+documenttype[0].datatype[0].sstruct.compression.minsize 0
+documenttype[0].datatype[0].sstruct.field[0]
+documenttype[0].datatype[1].id 10012
+documenttype[0].datatype[1].type STRUCT
+documenttype[0].datatype[1].array.element.id 0
+documenttype[0].datatype[1].map.key.id 0
+documenttype[0].datatype[1].map.value.id 0
+documenttype[0].datatype[1].wset.key.id 0
+documenttype[0].datatype[1].wset.createifnonexistent false
+documenttype[0].datatype[1].wset.removeifzero false
+documenttype[0].datatype[1].annotationref.annotation.id 0
+documenttype[0].datatype[1].sstruct.name "myposition"
+documenttype[0].datatype[1].sstruct.version 0
+documenttype[0].datatype[1].sstruct.compression.type NONE
+documenttype[0].datatype[1].sstruct.compression.level 0
+documenttype[0].datatype[1].sstruct.compression.threshold 90
+documenttype[0].datatype[1].sstruct.compression.minsize 0
+documenttype[0].datatype[1].sstruct.field[2]
+documenttype[0].datatype[1].sstruct.field[0].name "latitude"
+documenttype[0].datatype[1].sstruct.field[0].id 945500241
+documenttype[0].datatype[1].sstruct.field[0].id_v6 1437005468
+documenttype[0].datatype[1].sstruct.field[0].datatype 4
+documenttype[0].datatype[1].sstruct.field[1].name "longitude"
+documenttype[0].datatype[1].sstruct.field[1].id 1458007305
+documenttype[0].datatype[1].sstruct.field[1].id_v6 1517975127
+documenttype[0].datatype[1].sstruct.field[1].datatype 4
+documenttype[0].datatype[2].id 10022
+documenttype[0].datatype[2].type ANNOTATIONREF
+documenttype[0].datatype[2].array.element.id 0
+documenttype[0].datatype[2].map.key.id 0
+documenttype[0].datatype[2].map.value.id 0
+documenttype[0].datatype[2].wset.key.id 0
+documenttype[0].datatype[2].wset.createifnonexistent false
+documenttype[0].datatype[2].wset.removeifzero false
+documenttype[0].datatype[2].annotationref.annotation.id 20000
+documenttype[0].datatype[2].sstruct.name ""
+documenttype[0].datatype[2].sstruct.version 0
+documenttype[0].datatype[2].sstruct.compression.type NONE
+documenttype[0].datatype[2].sstruct.compression.level 0
+documenttype[0].datatype[2].sstruct.compression.threshold 95
+documenttype[0].datatype[2].sstruct.compression.minsize 200
+documenttype[0].datatype[2].sstruct.field[0]
+documenttype[0].datatype[3].id 10032
+documenttype[0].datatype[3].type ARRAY
+documenttype[0].datatype[3].array.element.id 10022
+documenttype[0].datatype[3].map.key.id 0
+documenttype[0].datatype[3].map.value.id 0
+documenttype[0].datatype[3].wset.key.id 0
+documenttype[0].datatype[3].wset.createifnonexistent false
+documenttype[0].datatype[3].wset.removeifzero false
+documenttype[0].datatype[3].annotationref.annotation.id 0
+documenttype[0].datatype[3].sstruct.name ""
+documenttype[0].datatype[3].sstruct.version 0
+documenttype[0].datatype[3].sstruct.compression.type NONE
+documenttype[0].datatype[3].sstruct.compression.level 0
+documenttype[0].datatype[3].sstruct.compression.threshold 95
+documenttype[0].datatype[3].sstruct.compression.minsize 200
+documenttype[0].datatype[3].sstruct.field[0]
+documenttype[0].datatype[4].id 10002
+documenttype[0].datatype[4].type STRUCT
+documenttype[0].datatype[4].array.element.id 0
+documenttype[0].datatype[4].map.key.id 0
+documenttype[0].datatype[4].map.value.id 0
+documenttype[0].datatype[4].wset.key.id 0
+documenttype[0].datatype[4].wset.createifnonexistent false
+documenttype[0].datatype[4].wset.removeifzero false
+documenttype[0].datatype[4].annotationref.annotation.id 0
+documenttype[0].datatype[4].sstruct.name "annotation.city"
+documenttype[0].datatype[4].sstruct.version 0
+documenttype[0].datatype[4].sstruct.compression.type NONE
+documenttype[0].datatype[4].sstruct.compression.level 0
+documenttype[0].datatype[4].sstruct.compression.threshold 90
+documenttype[0].datatype[4].sstruct.compression.minsize 0
+documenttype[0].datatype[4].sstruct.field[2]
+documenttype[0].datatype[4].sstruct.field[0].name "position"
+documenttype[0].datatype[4].sstruct.field[0].id 818217702
+documenttype[0].datatype[4].sstruct.field[0].id_v6 632310942
+documenttype[0].datatype[4].sstruct.field[0].datatype 10012
+documenttype[0].datatype[4].sstruct.field[1].name "references"
+documenttype[0].datatype[4].sstruct.field[1].id 1581548957
+documenttype[0].datatype[4].sstruct.field[1].id_v6 839497794
+documenttype[0].datatype[4].sstruct.field[1].datatype 10032
+documenttype[0].annotationtype[6]
+documenttype[0].annotationtype[0].id 20000
+documenttype[0].annotationtype[0].name "text"
+documenttype[0].annotationtype[0].datatype 10001
+documenttype[0].annotationtype[0].inherits[0]
+documenttype[0].annotationtype[1].id 20001
+documenttype[0].annotationtype[1].name "begintag"
+documenttype[0].annotationtype[1].datatype 10001
+documenttype[0].annotationtype[1].inherits[0]
+documenttype[0].annotationtype[2].id 20002
+documenttype[0].annotationtype[2].name "endtag"
+documenttype[0].annotationtype[2].datatype 10001
+documenttype[0].annotationtype[2].inherits[0]
+documenttype[0].annotationtype[3].id 20003
+documenttype[0].annotationtype[3].name "body"
+documenttype[0].annotationtype[3].datatype 10001
+documenttype[0].annotationtype[3].inherits[0]
+documenttype[0].annotationtype[4].id 20004
+documenttype[0].annotationtype[4].name "paragraph"
+documenttype[0].annotationtype[4].datatype 10001
+documenttype[0].annotationtype[4].inherits[0]
+documenttype[0].annotationtype[5].id 20005
+documenttype[0].annotationtype[5].name "city"
+documenttype[0].annotationtype[5].datatype 10002
+documenttype[0].annotationtype[5].inherits[0]
diff --git a/document/src/tests/serialization/annotationserializer_test.cpp b/document/src/tests/serialization/annotationserializer_test.cpp
new file mode 100644
index 00000000000..1cc3fc19373
--- /dev/null
+++ b/document/src/tests/serialization/annotationserializer_test.cpp
@@ -0,0 +1,274 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for annotation serialization.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("annotationserializer_test");
+
+#include <stdlib.h>
+#include <vespa/document/annotation/alternatespanlist.h>
+#include <vespa/document/annotation/annotation.h>
+#include <vespa/document/annotation/span.h>
+#include <vespa/document/annotation/spanlist.h>
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/fixedtyperepo.h>
+#include <vespa/document/serialization/annotationdeserializer.h>
+#include <vespa/document/serialization/annotationserializer.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <fstream>
+#include <sstream>
+#include <vector>
+
+using document::DocumenttypesConfig;
+using std::fstream;
+using std::ostringstream;
+using std::string;
+using std::vector;
+using vespalib::nbostream;
+using namespace document;
+
+namespace {
+
+class Test : public vespalib::TestApp {
+ StringFieldValue::SpanTrees readSpanTree(const string &file_name, const FixedTypeRepo &repo);
+
+ void requireThatSimpleSpanTreeIsDeserialized();
+ void requireThatAdvancedSpanTreeIsDeserialized();
+ void requireThatSpanTreeCanBeSerialized();
+ void requireThatUnknownAnnotationIsSkipped();
+
+public:
+ int Main();
+};
+
+int
+Test::Main()
+{
+ if (getenv("TEST_SUBSET") != 0) { return 0; }
+ TEST_INIT("annotationserializer_test");
+ TEST_DO(requireThatSimpleSpanTreeIsDeserialized());
+ TEST_DO(requireThatAdvancedSpanTreeIsDeserialized());
+ TEST_DO(requireThatSpanTreeCanBeSerialized());
+ TEST_DO(requireThatUnknownAnnotationIsSkipped());
+
+ TEST_DONE();
+}
+
+template <typename T, int N> int arraysize(const T (&)[N]) { return N; }
+
+StringFieldValue::SpanTrees
+Test::readSpanTree(const string &file_name, const FixedTypeRepo &repo) {
+ FastOS_File file(file_name.c_str());
+ ASSERT_TRUE(file.OpenReadOnlyExisting());
+ char buffer[1024];
+ ssize_t size = file.Read(buffer, arraysize(buffer));
+ ASSERT_TRUE(size != -1);
+
+ nbostream stream(buffer, size);
+ VespaDocumentDeserializer deserializer(repo, stream, 8);
+ StringFieldValue value;
+ deserializer.read(value);
+
+ EXPECT_EQUAL(0u, stream.size());
+ ASSERT_TRUE(value.hasSpanTrees());
+ return value.getSpanTrees();
+}
+
+void Test::requireThatSimpleSpanTreeIsDeserialized() {
+ DocumentTypeRepo type_repo(readDocumenttypesConfig("annotation.serialize.test.repo.cfg"));
+ FixedTypeRepo repo(type_repo);
+ SpanTree::UP span_tree = std::move(readSpanTree("test_data_serialized_simple", repo).front());
+
+ EXPECT_EQUAL("html", span_tree->getName());
+ const SimpleSpanList *root = dynamic_cast<const SimpleSpanList *>(&span_tree->getRoot());
+ ASSERT_TRUE(root);
+ EXPECT_EQUAL(5u, root->size());
+ SimpleSpanList::const_iterator it = root->begin();
+ EXPECT_EQUAL(Span(0, 19), (*it++));
+ EXPECT_EQUAL(Span(19, 5), (*it++));
+ EXPECT_EQUAL(Span(24, 21), (*it++));
+ EXPECT_EQUAL(Span(45, 23), (*it++));
+ EXPECT_EQUAL(Span(68, 14), (*it++));
+ EXPECT_TRUE(it == root->end());
+}
+
+struct AnnotationComparator {
+ vector<string> expect;
+ vector<string> actual;
+ template <typename ITR>
+ AnnotationComparator &addActual(ITR pos, ITR end) {
+ for (; pos != end; ++pos) {
+ ostringstream ost;
+ pos->print(ost, true, "");
+ actual.push_back(ost.str());
+ }
+ return *this;
+ }
+ AnnotationComparator &addExpected(const string &e) {
+ expect.push_back(e);
+ return *this;
+ }
+ void compare() {
+ std::sort(expect.begin(), expect.end());
+ std::sort(actual.begin(), actual.end());
+ EXPECT_EQUAL(expect.size(), actual.size());
+ for (size_t i = 0; i < expect.size() && i < actual.size(); ++i) {
+ EXPECT_EQUAL(expect[i].size(), actual[i].size());
+ EXPECT_EQUAL(expect[i], actual[i]);
+ }
+ }
+};
+
+void Test::requireThatAdvancedSpanTreeIsDeserialized() {
+ DocumentTypeRepo type_repo(
+ readDocumenttypesConfig("annotation.serialize.test.repo.cfg"));
+ FixedTypeRepo repo(type_repo, "my_document");
+ SpanTree::UP span_tree = std::move(readSpanTree("test_data_serialized_advanced", repo).front());
+
+ EXPECT_EQUAL("html", span_tree->getName());
+ const SpanList *root = dynamic_cast<const SpanList *>(&span_tree->getRoot());
+ ASSERT_TRUE(root);
+ EXPECT_EQUAL(4u, root->size());
+ SpanList::const_iterator it = root->begin();
+ EXPECT_EQUAL(Span(0, 6), *(static_cast<Span *>(*it++)));
+ AlternateSpanList *alt_list = dynamic_cast<AlternateSpanList *>(*it++);
+ EXPECT_EQUAL(Span(27, 9), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(36, 8), *(static_cast<Span *>(*it++)));
+ EXPECT_TRUE(it == root->end());
+
+ ASSERT_TRUE(alt_list);
+ EXPECT_EQUAL(2u, alt_list->getNumSubtrees());
+ EXPECT_EQUAL(0.9, alt_list->getProbability(0));
+ EXPECT_EQUAL(0.1, alt_list->getProbability(1));
+ EXPECT_EQUAL(4u, alt_list->getSubtree(0).size());
+ it = alt_list->getSubtree(0).begin();
+ EXPECT_EQUAL(Span(6, 3), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(9, 10), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(19, 4), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(23, 4), *(static_cast<Span *>(*it++)));
+ EXPECT_TRUE(it == alt_list->getSubtree(0).end());
+ EXPECT_EQUAL(2u, alt_list->getSubtree(1).size());
+ it = alt_list->getSubtree(1).begin();
+ EXPECT_EQUAL(Span(6, 13), *(static_cast<Span *>(*it++)));
+ EXPECT_EQUAL(Span(19, 8), *(static_cast<Span *>(*it++)));
+ EXPECT_TRUE(it == alt_list->getSubtree(1).end());
+
+ EXPECT_EQUAL(12u, span_tree->numAnnotations());
+
+ AnnotationComparator comparator;
+ comparator.addActual(span_tree->begin(), span_tree->end())
+ .addExpected("Annotation(AnnotationType(20001, begintag)\n"
+ " Span(6, 3))")
+ .addExpected("Annotation(AnnotationType(20000, text)\n"
+ " Span(9, 10))")
+ .addExpected("Annotation(AnnotationType(20000, text)\n"
+ " Span(19, 4))")
+ .addExpected("Annotation(AnnotationType(20002, endtag)\n"
+ " Span(23, 4))")
+ .addExpected("Annotation(AnnotationType(20000, text)\n"
+ " Span(6, 13))")
+ .addExpected("Annotation(AnnotationType(20003, body)\n"
+ " Span(19, 8))")
+ .addExpected("Annotation(AnnotationType(20004, paragraph)\n"
+ " AlternateSpanList(\n"
+ " Probability 0.9 : SpanList(\n"
+ " Span(6, 3)\n"
+ " Span(9, 10)\n"
+ " Span(19, 4)\n"
+ " Span(23, 4)),\n"
+ " Probability 0.1 : SpanList(\n"
+ " Span(6, 13)\n"
+ " Span(19, 8))))")
+ .addExpected("Annotation(AnnotationType(20001, begintag)\n"
+ " Span(0, 6))")
+ .addExpected("Annotation(AnnotationType(20000, text)\n"
+ " Span(27, 9))")
+ .addExpected("Annotation(AnnotationType(20002, endtag)\n"
+ " Span(36, 8))")
+ .addExpected("Annotation(AnnotationType(20003, body)\n"
+ " SpanList(\n"
+ " Span(0, 6)\n"
+ " AlternateSpanList(\n"
+ " Probability 0.9 : SpanList(\n"
+ " Span(6, 3)\n"
+ " Span(9, 10)\n"
+ " Span(19, 4)\n"
+ " Span(23, 4)),\n"
+ " Probability 0.1 : SpanList(\n"
+ " Span(6, 13)\n"
+ " Span(19, 8)))\n"
+ " Span(27, 9)\n"
+ " Span(36, 8)))")
+ .addExpected("Annotation(AnnotationType(20005, city)\n"
+ " Struct annotation.city(\n"
+ " position - Struct myposition(\n"
+ " latitude - 37,\n"
+ " longitude - -122\n"
+ " ),\n"
+ " references - Array(size: 2,\n"
+ " AnnotationReferenceFieldValue(n),\n"
+ " AnnotationReferenceFieldValue(n)\n"
+ " )\n"
+ " ))");
+ TEST_DO(comparator.compare());
+}
+
+void Test::requireThatSpanTreeCanBeSerialized() {
+ DocumentTypeRepo type_repo(
+ readDocumenttypesConfig("annotation.serialize.test.repo.cfg"));
+ FixedTypeRepo repo(type_repo, "my_document");
+ string file_name = "test_data_serialized_advanced";
+
+ FastOS_File file(file_name.c_str());
+ ASSERT_TRUE(file.OpenReadOnlyExisting());
+ char buffer[1024];
+ ssize_t size = file.Read(buffer, arraysize(buffer));
+ ASSERT_TRUE(size != -1);
+
+ nbostream stream(buffer, size);
+ VespaDocumentDeserializer deserializer(repo, stream, 8);
+ StringFieldValue value;
+ deserializer.read(value);
+
+ SpanTree::UP span_tree = std::move(value.getSpanTrees().front());
+ EXPECT_EQUAL("html", span_tree->getName());
+ EXPECT_EQUAL(0u, stream.size());
+
+ stream.clear();
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(value);
+ EXPECT_EQUAL(size, static_cast<ssize_t>(stream.size()));
+ int diff_count = 0;
+ for (size_t i = 0; i < stream.size(); ++i) {
+ if (buffer[i] != stream.peek()[i]) {
+ ++diff_count;
+ }
+ EXPECT_EQUAL((int) buffer[i], (int) stream.peek()[i]);
+ }
+ EXPECT_EQUAL(0, diff_count);
+}
+
+void Test::requireThatUnknownAnnotationIsSkipped() {
+ AnnotationType type(42, "my type");
+ Annotation annotation(type, FieldValue::UP(new StringFieldValue("foo")));
+ nbostream stream;
+ AnnotationSerializer serializer(stream);
+ serializer.write(annotation);
+
+ DocumentTypeRepo type_repo; // Doesn't know any annotation types.
+ FixedTypeRepo repo(type_repo);
+ AnnotationDeserializer deserializer(repo, stream, 8);
+ Annotation a;
+ deserializer.readAnnotation(a);
+ EXPECT_FALSE(a.valid());
+ EXPECT_EQUAL(0u, stream.size());
+}
+
+} // namespace
+
+TEST_APPHOOK(Test);
diff --git a/document/src/tests/serialization/compression_test.cpp b/document/src/tests/serialization/compression_test.cpp
new file mode 100644
index 00000000000..01e19ae946b
--- /dev/null
+++ b/document/src/tests/serialization/compression_test.cpp
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for annotation serialization.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/stllike/string.h>
+#include <vespa/document/util/compressor.h>
+
+LOG_SETUP("compression_test");
+
+using namespace document;
+using namespace vespalib;
+
+static vespalib::string _G_compressableText("AAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEE"
+ "AAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEE"
+ "AAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEE"
+ "AAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEE"
+ "AAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEE");
+
+TEST("requireThatLZ4CompressFine") {
+ CompressionConfig cfg(CompressionConfig::Type::LZ4);
+ ConstBufferRef ref(_G_compressableText.c_str(), _G_compressableText.size());
+ DataBuffer compressed;
+ EXPECT_EQUAL(CompressionConfig::Type::LZ4, compress(cfg, ref, compressed, false));
+}
+
+TEST_MAIN() {
+ TEST_RUN_ALL();
+}
diff --git a/document/src/tests/serialization/test_data_serialized_advanced b/document/src/tests/serialization/test_data_serialized_advanced
new file mode 100644
index 00000000000..d5d6ca6022a
--- /dev/null
+++ b/document/src/tests/serialization/test_data_serialized_advanced
Binary files differ
diff --git a/document/src/tests/serialization/test_data_serialized_simple b/document/src/tests/serialization/test_data_serialized_simple
new file mode 100644
index 00000000000..ae65a2eefb4
--- /dev/null
+++ b/document/src/tests/serialization/test_data_serialized_simple
Binary files differ
diff --git a/document/src/tests/serialization/vespadocumentserializer_test.cpp b/document/src/tests/serialization/vespadocumentserializer_test.cpp
new file mode 100644
index 00000000000..0043548a906
--- /dev/null
+++ b/document/src/tests/serialization/vespadocumentserializer_test.cpp
@@ -0,0 +1,828 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for vespadocumentserializer.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("vespadocumentserializer_test");
+
+#include <vespa/document/annotation/annotation.h>
+#include <vespa/document/annotation/span.h>
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/datatype/annotationreferencedatatype.h>
+#include <vespa/document/datatype/arraydatatype.h>
+#include <vespa/document/datatype/documenttype.h>
+#include <vespa/document/fieldvalue/annotationreferencefieldvalue.h>
+#include <vespa/document/fieldvalue/arrayfieldvalue.h>
+#include <vespa/document/fieldvalue/bytefieldvalue.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/doublefieldvalue.h>
+#include <vespa/document/fieldvalue/floatfieldvalue.h>
+#include <vespa/document/fieldvalue/intfieldvalue.h>
+#include <vespa/document/fieldvalue/longfieldvalue.h>
+#include <vespa/document/fieldvalue/mapfieldvalue.h>
+#include <vespa/document/fieldvalue/predicatefieldvalue.h>
+#include <vespa/document/fieldvalue/rawfieldvalue.h>
+#include <vespa/document/fieldvalue/shortfieldvalue.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/fieldvalue/structfieldvalue.h>
+#include <vespa/document/fieldvalue/weightedsetfieldvalue.h>
+#include <vespa/document/fieldvalue/tensorfieldvalue.h>
+#include <vespa/document/predicate/predicate.h>
+#include <vespa/document/predicate/predicate_slime_builder.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/repo/fixedtyperepo.h>
+#include <vespa/document/serialization/util.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <vespa/document/serialization/annotationserializer.h>
+#include <vespa/vespalib/tensor/types.h>
+#include <vespa/vespalib/tensor/tensor_builder.h>
+#include <vespa/vespalib/tensor/tensor.h>
+#include <vespa/vespalib/tensor/default_tensor.h>
+#include <vespa/vespalib/tensor/tensor_factory.h>
+#include <stdlib.h>
+#include <vespa/vespalib/data/slime/cursor.h>
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/io/fileutil.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using document::DocumenttypesConfig;
+using vespalib::File;
+using vespalib::Slime;
+using vespalib::nbostream;
+using vespalib::slime::Cursor;
+using vespalib::tensor::Tensor;
+using vespalib::tensor::TensorBuilder;
+using vespalib::tensor::TensorCells;
+using vespalib::tensor::TensorDimensions;
+using namespace document;
+using std::string;
+using std::vector;
+using namespace document::config_builder;
+
+namespace {
+
+const int doc_type_id = 1234;
+const string doc_name = "my document";
+const int body_id = 94;
+const int inner_type_id = 95;
+const int outer_type_id = 96;
+const string type_name = "outer doc";
+const string inner_name = "inner doc";
+const int a_id = 12345;
+const string a_name = "annotation";
+const int predicate_doc_type_id = 321;
+const string predicate_field_name = "my_predicate";
+
+DocumenttypesConfig getDocTypesConfig() {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(doc_type_id, doc_name,
+ Struct("my document.header")
+ .addField("header field", DataType::T_INT),
+ Struct("my document.body")
+ .addField("body field", DataType::T_STRING))
+ .annotationType(42, "foo_type", DataType::T_INT);
+ builder.document(inner_type_id, inner_name,
+ Struct(inner_name + ".header"),
+ Struct(inner_name + ".body")
+ .addField("str", DataType::T_STRING))
+ .annotationType(a_id, a_name, DataType::T_STRING);
+ builder.document(outer_type_id, type_name,
+ Struct(type_name + ".header"),
+ Struct(type_name + ".body")
+ .addField(inner_name, inner_type_id).setId(body_id));
+ builder.document(predicate_doc_type_id, "my_type",
+ Struct("my_type.header"),
+ Struct("my_type.body")
+ .addField(predicate_field_name, DataType::T_PREDICATE));
+ return builder.config();
+}
+
+const DocumentTypeRepo doc_repo(getDocTypesConfig());
+const FixedTypeRepo repo(doc_repo, *doc_repo.getDocumentType(doc_type_id));
+
+template <typename T> T newFieldValue(const T&) { return T(); }
+template <> ArrayFieldValue newFieldValue(const ArrayFieldValue &value)
+{ return ArrayFieldValue(*value.getDataType()); }
+template <> MapFieldValue newFieldValue(const MapFieldValue &value)
+{ return MapFieldValue(*value.getDataType()); }
+template <> WeightedSetFieldValue newFieldValue(
+ const WeightedSetFieldValue &value)
+{ return WeightedSetFieldValue(*value.getDataType()); }
+template <> StructFieldValue newFieldValue(const StructFieldValue &value)
+{ return StructFieldValue(*value.getDataType()); }
+template <> AnnotationReferenceFieldValue newFieldValue(
+ const AnnotationReferenceFieldValue &value)
+{ return AnnotationReferenceFieldValue(*value.getDataType()); }
+
+template<typename T>
+void testDeserializeAndClone(const T& value, const nbostream &stream, bool checkEqual=true) {
+ T read_value = newFieldValue(value);
+ vespalib::MallocPtr buf(stream.size());
+ memcpy(buf.str(), stream.peek(), stream.size());
+ nbostream is(buf.c_str(), buf.size(), true);
+ VespaDocumentDeserializer deserializer(repo, is, 8);
+ deserializer.read(read_value);
+
+ EXPECT_EQUAL(0u, is.size());
+ if (checkEqual) {
+ EXPECT_EQUAL(value, read_value);
+ }
+ T clone(read_value);
+ buf.reset();
+ if (checkEqual) {
+ EXPECT_EQUAL(value, clone);
+ }
+}
+
+// Leaves the stream's read position at the start of the serialized object.
+template<typename T>
+void serializeAndDeserialize(const T& value, nbostream &stream, bool checkEqual=true) {
+ size_t start_size = stream.size();
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(value);
+ size_t serialized_size = stream.size() - start_size;
+
+ testDeserializeAndClone(value, stream, checkEqual);
+ T read_value = newFieldValue(value);
+
+ VespaDocumentDeserializer deserializer(repo, stream, 8);
+ deserializer.read(read_value);
+
+ EXPECT_EQUAL(0u, stream.size());
+ if (checkEqual) {
+ EXPECT_EQUAL(value, read_value);
+ }
+ stream.adjustReadPos(-serialized_size);
+}
+
+template <typename T> struct ValueType { typedef typename T::Number Type; };
+template <> struct ValueType<IntFieldValue> { typedef uint32_t Type; };
+template <> struct ValueType<LongFieldValue> { typedef uint64_t Type; };
+
+template<typename T>
+void serializeAndDeserializeNumber(const T& value) {
+ const typename ValueType<T>::Type val = value.getValue();
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ typename ValueType<T>::Type read_val;
+ stream >> read_val;
+ EXPECT_EQUAL(val, read_val);
+}
+
+TEST("requireThatPrimitiveTypeFieldValueCanBeSerialized") {
+ TEST_DO(serializeAndDeserializeNumber(ByteFieldValue(42)));
+ TEST_DO(serializeAndDeserializeNumber(ShortFieldValue(0x1234)));
+ TEST_DO(serializeAndDeserializeNumber(IntFieldValue(0x12345678)));
+ TEST_DO(serializeAndDeserializeNumber(DoubleFieldValue(34567890.123456)));
+ TEST_DO(serializeAndDeserializeNumber(FloatFieldValue(3456.1234f)));
+ TEST_DO(serializeAndDeserializeNumber(LongFieldValue(0x12345678123456LL)));
+}
+
+template <typename SizeType>
+void checkLiteralFieldValue(nbostream &stream, const string &val) {
+ uint8_t read_coding;
+ SizeType size;
+ stream >> read_coding >> size;
+ EXPECT_EQUAL(0, read_coding);
+ size &= (SizeType(-1) >> 1); // Clear MSB.
+ vespalib::string read_val;
+ read_val.assign(stream.peek(), size);
+ stream.adjustReadPos(size);
+ EXPECT_EQUAL(val.size(), read_val.size());
+ EXPECT_EQUAL(val, read_val);
+}
+
+template <typename SizeType>
+void checkStringFieldValue(const string &val) {
+ StringFieldValue value(val);
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ string val_with_null(val.c_str(), val.size() + 1);
+ checkLiteralFieldValue<SizeType>(stream, val_with_null);
+}
+
+void setSpanTree(StringFieldValue & str, const SpanTree & tree) {
+ nbostream os;
+ AnnotationSerializer serializer(os);
+ serializer.write(tree);
+ str.setSpanTrees(vespalib::ConstBufferRef(os.peek(), os.size()), repo, VespaDocumentSerializer::getCurrentVersion(), false);
+}
+
+void checkStringFieldValueWithAnnotation() {
+ StringFieldValue value("foo");
+ Span::UP root(new Span(2, 3));
+ SpanTree::UP tree(new SpanTree("test", std::move(root)));
+ AnnotationType annotation_type(42, "foo_type");
+ tree->annotate(tree->getRoot(), annotation_type);
+
+ nbostream os;
+ AnnotationSerializer serializer(os);
+ serializer.write(*tree);
+
+ value.setSpanTrees(vespalib::ConstBufferRef(os.peek(), os.size()), repo, VespaDocumentSerializer::getCurrentVersion(), true);
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+}
+
+TEST("requireThatStringFieldValueCanBeSerialized") {
+ TEST_DO(checkStringFieldValue<uint8_t>("foo bar baz"));
+ TEST_DO(checkStringFieldValue<uint32_t>(string(200, 'a')));
+ TEST_DO(checkStringFieldValueWithAnnotation());
+}
+
+template <typename SizeType>
+void checkRawFieldValue(const string &val) {
+ RawFieldValue value(val);
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ uint32_t size;
+ stream >> size;
+ string read_val(stream.peek(), size);
+ stream.adjustReadPos(size);
+ EXPECT_EQUAL(val, read_val);
+}
+
+TEST("requireThatRawFieldValueCanBeSerialized") {
+ TEST_DO(checkRawFieldValue<uint8_t>("foo bar"));
+ TEST_DO(checkRawFieldValue<uint32_t>(string(200, 'b')));
+}
+
+TEST("requireThatPredicateFieldValueCanBeSerialized") {
+ PredicateSlimeBuilder builder;
+ builder.neg().feature("foo").value("bar").value("baz");
+ PredicateFieldValue value(builder.build());
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+}
+
+template <typename SizeType>
+SizeType readSize1_2_4(nbostream &stream) {
+ SizeType size;
+ stream >> size;
+ if (sizeof(SizeType) > 1) {
+ size &= (SizeType(-1) >> 2); // Clear MSBs
+ }
+ return size;
+}
+
+template <typename SizeType>
+void checkArrayFieldValue(SizeType value_count) {
+ ArrayDataType array_type(*DataType::INT);
+ ArrayFieldValue value(array_type);
+ for (uint32_t i = 0; i < value_count; ++i) {
+ value.add(static_cast<int32_t>(i));
+ }
+
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ SizeType size = readSize1_2_4<SizeType>(stream);
+ EXPECT_EQUAL(value_count, size);
+ uint32_t child;
+ for (uint32_t i = 0; i < value_count; ++i) {
+ stream >> child;
+ EXPECT_EQUAL(i, child);
+ }
+}
+
+TEST("requireThatArrayFieldValueCanBeSerialized") {
+ TEST_DO(checkArrayFieldValue<uint8_t>(2));
+ TEST_DO(checkArrayFieldValue<uint8_t>(0x7f));
+ TEST_DO(checkArrayFieldValue<uint16_t>(0x80));
+ TEST_DO(checkArrayFieldValue<uint16_t>(0x3fff));
+ TEST_DO(checkArrayFieldValue<uint32_t>(0x4000));
+}
+
+TEST("requireThatOldVersionArrayFieldValueCanBeDeserialized") {
+ uint16_t old_version = 6;
+
+ nbostream stream;
+ uint32_t type_id = 3;
+ uint32_t size = 2;
+ uint32_t element_size = 4;
+ uint32_t element1 = 21, element2 = 42;
+ stream << type_id << size
+ << element_size << element1
+ << element_size << element2;
+
+ ArrayDataType array_type(*DataType::INT);
+ ArrayFieldValue value(array_type);
+ VespaDocumentDeserializer deserializer(repo, stream, old_version);
+ deserializer.read(value);
+ ASSERT_TRUE(EXPECT_EQUAL(size, value.size()));
+ IntFieldValue *int_value = dynamic_cast<IntFieldValue *>(&value[0]);
+ ASSERT_TRUE(int_value);
+ EXPECT_EQUAL(element1, static_cast<uint32_t>(int_value->getValue()));
+ int_value = dynamic_cast<IntFieldValue *>(&value[1]);
+ ASSERT_TRUE(int_value);
+ EXPECT_EQUAL(element2, static_cast<uint32_t>(int_value->getValue()));
+}
+
+template <typename SizeType>
+void checkMapFieldValue(SizeType value_count, bool check_equal) {
+ MapDataType map_type(*DataType::LONG, *DataType::BYTE);
+ MapFieldValue value(map_type);
+ for (SizeType i = 0; i < value_count; ++i) {
+ value.push_back(LongFieldValue(i), ByteFieldValue(i));
+ }
+
+ nbostream stream;
+ serializeAndDeserialize(value, stream, check_equal);
+
+ SizeType size = readSize1_2_4<SizeType>(stream);
+ EXPECT_EQUAL(value_count, size);
+ uint64_t key;
+ uint8_t val;
+ for (SizeType i = 0; i < value_count; ++i) {
+ stream >> key >> val;
+ EXPECT_EQUAL(i, key);
+ EXPECT_EQUAL(i % 256, val);
+ }
+}
+
+TEST("requireThatMapFieldValueCanBeSerialized") {
+ TEST_DO(checkMapFieldValue<uint8_t>(2, true));
+ TEST_DO(checkMapFieldValue<uint8_t>(0x7f, true));
+ TEST_DO(checkMapFieldValue<uint16_t>(0x80, true));
+ TEST_DO(checkMapFieldValue<uint16_t>(0x3fff, false));
+ TEST_DO(checkMapFieldValue<uint32_t>(0x4000, false));
+}
+
+TEST("requireThatOldVersionMapFieldValueCanBeDeserialized") {
+ uint16_t old_version = 6;
+
+ nbostream stream;
+ uint32_t type_id = 4;
+ uint32_t size = 2;
+ uint32_t element_size = 9;
+ uint64_t key1 = 21, key2 = 42;
+ uint8_t val1 = 1, val2 = 2;
+ stream << type_id << size
+ << element_size << key1 << val1
+ << element_size << key2 << val2;
+
+ MapDataType map_type(*DataType::LONG, *DataType::BYTE);
+ MapFieldValue value(map_type);
+ VespaDocumentDeserializer deserializer(repo, stream, old_version);
+ deserializer.read(value);
+ ASSERT_TRUE(EXPECT_EQUAL(size, value.size()));
+
+ ASSERT_TRUE(value.contains(LongFieldValue(key1)));
+ ASSERT_TRUE(value.contains(LongFieldValue(key2)));
+ EXPECT_EQUAL(ByteFieldValue(val1),
+ *value.find(LongFieldValue(key1))->second);
+ EXPECT_EQUAL(ByteFieldValue(val2),
+ *value.find(LongFieldValue(key2))->second);
+}
+
+TEST("requireThatWeightedSetFieldValueCanBeSerialized") {
+ WeightedSetDataType ws_type(*DataType::DOUBLE, false, false);
+ WeightedSetFieldValue value(ws_type);
+ value.add(DoubleFieldValue(3.14), 2);
+ value.add(DoubleFieldValue(2.71), 3);
+
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ uint32_t type_id;
+ uint32_t size;
+ stream >> type_id >> size;
+ EXPECT_EQUAL(2u, size);
+ double val;
+ uint32_t weight;
+ stream >> size >> val >> weight;
+ EXPECT_EQUAL(12u, size);
+ EXPECT_EQUAL(3.14, val);
+ EXPECT_EQUAL(2u, weight);
+ stream >> size >> val >> weight;
+ EXPECT_EQUAL(12u, size);
+ EXPECT_EQUAL(2.71, val);
+ EXPECT_EQUAL(3u, weight);
+}
+
+const Field field1("field1", *DataType::INT, false);
+const Field field2("field2", *DataType::STRING, false);
+
+StructDataType getStructDataType() {
+ StructDataType struct_type("struct");
+ struct_type.addField(field1);
+ struct_type.addField(field2);
+ return struct_type;
+}
+
+StructFieldValue getStructFieldValue(const StructDataType& structType) {
+ StructFieldValue value(structType);
+ value.setValue(field1, IntFieldValue(42));
+ value.setValue(field2, StringFieldValue("foooooooooooooooooooooobaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
+ return value;
+}
+
+void checkStructSerialization(const StructFieldValue &value,
+ CompressionConfig::Type comp_type) {
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+ uint32_t data_size;
+ uint8_t compression_type;
+ uint16_t uncompressed_size;
+ uint8_t field_count;
+ int32_t element1_id, element2_id;
+ uint16_t element1_size, element2_size;
+ stream >> data_size >> compression_type;
+ if (CompressionConfig::isCompressed(comp_type)) {
+ stream >> uncompressed_size;
+ EXPECT_EQUAL(24u, data_size);
+ EXPECT_EQUAL(64u, uncompressed_size);
+ } else {
+ EXPECT_EQUAL(64u, data_size);
+ }
+ stream >> field_count
+ >> element1_id >> element1_size
+ >> element2_id >> element2_size;
+ EXPECT_EQUAL(comp_type, compression_type);
+ EXPECT_EQUAL(2u, field_count);
+ EXPECT_EQUAL(field1.getId(8), element1_id & 0x7fffffff);
+ EXPECT_EQUAL(4u, element1_size);
+ EXPECT_EQUAL(field2.getId(8), element2_id & 0x7fffffff);
+ EXPECT_EQUAL(60u, element2_size);
+}
+
+TEST("requireThatUncompressedStructFieldValueCanBeSerialized") {
+ StructDataType structType(getStructDataType());
+ StructFieldValue value = getStructFieldValue(structType);
+ checkStructSerialization(value, CompressionConfig::NONE);
+}
+
+TEST("requireThatCompressedStructFieldValueCanBeSerialized") {
+ StructDataType structType(getStructDataType());
+ StructFieldValue value = getStructFieldValue(structType);
+ const_cast<StructDataType &>(value.getStructType()).setCompressionConfig(CompressionConfig(CompressionConfig::LZ4, 0, 95));
+ checkStructSerialization(value, CompressionConfig::LZ4);
+}
+
+TEST("requireThatReserializationPreservesCompressionIfUnmodified") {
+ StructDataType structType(getStructDataType());
+ StructFieldValue value = getStructFieldValue(structType);
+ const_cast<StructDataType &>(value.getStructType()).
+ setCompressionConfig(CompressionConfig(CompressionConfig::LZ4, 0, 95));
+
+ checkStructSerialization(value, CompressionConfig::LZ4);
+
+ nbostream os;
+ VespaDocumentSerializer serializer(os);
+ serializer.write(value);
+
+ StructDataType struct_type(getStructDataType());
+ StructFieldValue value2(struct_type);
+ VespaDocumentDeserializer deserializer(repo, os, 8);
+ deserializer.read(value2);
+ checkStructSerialization(value, CompressionConfig::LZ4);
+ // No lazy serialization of structs anymore, only documents
+ checkStructSerialization(value2, CompressionConfig::NONE);
+ EXPECT_EQUAL(value, value2);
+}
+
+template <typename T, int N> int arraysize(const T (&)[N]) { return N; }
+
+TEST("requireThatDocumentCanBeSerialized") {
+ const uint32_t serialization_version = 8;
+ const DocumentType &type = repo.getDocumentType();
+
+ DocumentId doc_id("doc::testdoc");
+ Document value(type, doc_id);
+
+ value.setValue(type.getField("header field"), IntFieldValue(42));
+ value.setValue(type.getField("body field"), StringFieldValue("foobar"));
+
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ uint16_t read_version;
+ uint32_t size;
+ stream >> read_version >> size;
+ EXPECT_EQUAL(serialization_version, read_version);
+ EXPECT_EQUAL(65u, size);
+ EXPECT_EQUAL(doc_id.getScheme().toString(), stream.peek());
+ stream.adjustReadPos(doc_id.getScheme().toString().size() + 1);
+ uint8_t content_code;
+ stream >> content_code;
+ EXPECT_EQUAL(0x07, content_code);
+ EXPECT_EQUAL(type.getName(), stream.peek());
+ stream.adjustReadPos(type.getName().size() + 1);
+ stream >> read_version;
+ EXPECT_EQUAL(0, read_version);
+}
+
+TEST("requireThatOldVersionDocumentCanBeDeserialized") {
+ uint16_t old_version = 6;
+ uint16_t data_size = 432;
+ string doc_id = "doc::testdoc";
+ uint8_t content_code = 0x01;
+ uint32_t crc = 42;
+
+ nbostream stream;
+ stream << old_version << data_size;
+ stream.write(doc_id.c_str(), doc_id.size() + 1);
+ stream << content_code;
+ stream.write(doc_name.c_str(), doc_name.size() + 1);
+ stream << static_cast<uint16_t>(0) << crc; // version (unused)
+
+ Document value;
+ VespaDocumentDeserializer deserializer(repo, stream, old_version);
+ deserializer.read(value);
+
+ EXPECT_EQUAL(doc_id, value.getId().getScheme().toString());
+ EXPECT_EQUAL(doc_name, value.getType().getName());
+ EXPECT_TRUE(value.getFields().empty());
+}
+
+TEST("requireThatUnmodifiedDocumentRetainsUnknownFieldOnSerialization") {
+
+ DocumenttypesConfigBuilderHelper builder1, builder2;
+ builder1.document(doc_type_id, doc_name,
+ Struct("my document.header")
+ .addField("field2", DataType::T_STRING),
+ Struct("my document.body"));
+ builder2.document(doc_type_id, doc_name,
+ Struct("my document.header")
+ .addField("field1", DataType::T_INT)
+ .addField("field2", DataType::T_STRING),
+ Struct("my document.body"));
+
+ DocumentTypeRepo repo1Field(builder1.config());
+ DocumentTypeRepo repo2Fields(builder2.config());
+
+ uint32_t serial_version = 8;
+
+ DocumentId doc_id("doc::testdoc");
+ Document value(*repo2Fields.getDocumentType(doc_type_id), doc_id);
+
+ value.setValue("field1", IntFieldValue(42));
+ value.setValue("field2", StringFieldValue("megafoo"));
+
+ nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(value);
+
+ Document read_value;
+ // Deserialize+serialize with type where field1 is not known.
+ VespaDocumentDeserializer deserializer1(repo1Field, stream, serial_version);
+ deserializer1.read(read_value);
+ EXPECT_EQUAL(0u, stream.size());
+
+ EXPECT_EQUAL(1u, read_value.getSetFieldCount());
+
+ stream.clear();
+ serializer.write(read_value);
+
+ Document read_value_2;
+ // Field should not have vanished.
+ VespaDocumentDeserializer deserializer2(repo2Fields, stream, serial_version);
+ deserializer2.read(read_value_2);
+ EXPECT_EQUAL(value, read_value_2);
+}
+
+TEST("requireThatAnnotationReferenceFieldValueCanBeSerialized") {
+ AnnotationType annotation_type(0, "atype");
+ AnnotationReferenceDataType type(annotation_type, 0);
+ int annotation_id = 420;
+ AnnotationReferenceFieldValue value(type, annotation_id);
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+
+ int read_id = readSize1_2_4<uint16_t>(stream);
+ EXPECT_EQUAL(annotation_id, read_id);
+}
+
+TEST("requireThatDocumentWithDocumentCanBeSerialized") {
+ const DocumentTypeRepo &my_repo = repo.getDocumentTypeRepo();
+ const DocumentType *inner_type = my_repo.getDocumentType(inner_type_id);
+ ASSERT_TRUE(inner_type);
+ const AnnotationType *a_type =
+ my_repo.getAnnotationType(*inner_type, a_id);
+ StringFieldValue str("foo");
+ SpanTree::UP tree(new SpanTree("name", Span::UP(new Span(0, 3))));
+ tree->annotate(Annotation::UP(new Annotation(*a_type)));
+
+
+ setSpanTree(str, *tree);
+ const Field str_field("str", *DataType::STRING, false);
+
+ Document inner(*inner_type, DocumentId("doc::in"));
+ inner.setValue(str_field, str);
+ const DocumentType *type = my_repo.getDocumentType(outer_type_id);
+ ASSERT_TRUE(type);
+ DocumentId doc_id("doc::testdoc");
+ Document value(*type, doc_id);
+ const Field doc_field(inner_name, *inner_type, false);
+ value.setValue(doc_field, inner);
+
+ nbostream stream;
+ serializeAndDeserialize(value, stream);
+}
+
+TEST("requireThatReadDocumentTypeThrowsIfUnknownType") {
+ string my_type("my_unknown_type");
+ nbostream stream;
+ stream.write(my_type.c_str(), my_type.size() + 1);
+ stream << static_cast<uint16_t>(0); // version (unused)
+
+ DocumentType value;
+ VespaDocumentDeserializer deserializer(repo, stream, 8);
+ EXPECT_EXCEPTION(deserializer.read(value), DocumentTypeNotFoundException,
+ "Document type " + my_type + " not found");
+}
+
+template <typename FieldValueT>
+void serializeToFile(FieldValueT &value, const string &file_name,
+ const DocumentType *type, const string &field_name) {
+ DocumentId doc_id("id:test:" + type->getName() + "::foo");
+ Document doc(*type, doc_id);
+ doc.setValue(type->getField(field_name), value);
+
+ nbostream stream;
+ VespaDocumentSerializer serializer(stream);
+ serializer.write(doc);
+
+ File file(file_name);
+ file.open(File::CREATE);
+ file.write(stream.peek(), stream.size(), 0);
+}
+
+void serializeToFile(PredicateFieldValue &value, const string &file_name) {
+ const DocumentType *type = doc_repo.getDocumentType(predicate_doc_type_id);
+ serializeToFile(value, file_name, type, predicate_field_name);
+}
+
+template <typename FieldValueT>
+void deserializeAndCheck(const string &file_name, FieldValueT &value,
+ const FixedTypeRepo &myrepo,
+ const string &field_name) {
+ File file(file_name);
+ file.open(File::READONLY);
+ vector<char> content(file.stat()._size);
+ size_t r = file.read(&content[0], content.size(), 0);
+ ASSERT_EQUAL(content.size(), r);
+
+ nbostream stream(&content[0], content.size(), true);
+ Document doc;
+ VespaDocumentDeserializer deserializer(myrepo, stream, 8);
+ deserializer.read(doc);
+
+ ASSERT_EQUAL(0, value.compare(*doc.getValue(field_name)));
+}
+
+void deserializeAndCheck(const string &file_name, PredicateFieldValue &value) {
+ deserializeAndCheck(file_name, value, repo, predicate_field_name);
+}
+
+void checkDeserialization(const string &name, std::unique_ptr<Slime> slime) {
+ const string data_dir = "../../test/resources/predicates/";
+
+ PredicateFieldValue value(std::move(slime));
+ serializeToFile(value, data_dir + name + "__cpp");
+
+ deserializeAndCheck(data_dir + name + "__cpp", value);
+ deserializeAndCheck(data_dir + name + "__java", value);
+}
+
+TEST("Require that predicate deserialization matches Java") {
+ PredicateSlimeBuilder builder;
+
+ builder.feature("foo").range(6, 9);
+ checkDeserialization("foo_in_6_9", builder.build());
+
+ builder.feature("foo").greaterEqual(6);
+ checkDeserialization("foo_in_6_x", builder.build());
+
+ builder.feature("foo").lessEqual(9);
+ checkDeserialization("foo_in_x_9", builder.build());
+
+ builder.feature("foo").value("bar");
+ checkDeserialization("foo_in_bar", builder.build());
+
+ builder.feature("foo").value("bar").value("baz");
+ checkDeserialization("foo_in_bar_baz", builder.build());
+
+ builder.neg().feature("foo").value("bar");
+ checkDeserialization("not_foo_in_bar", builder.build());
+
+ std::unique_ptr<Slime> slime(new Slime);
+ Cursor *cursor = &slime->setObject();
+ cursor->setString(Predicate::KEY, "foo");
+ cursor->setLong(Predicate::NODE_TYPE, Predicate::TYPE_FEATURE_SET);
+ checkDeserialization("foo_in_x", std::move(slime));
+
+ slime.reset(new Slime);
+ cursor = &slime->setObject();
+ cursor->setString(Predicate::KEY, "foo");
+ cursor->setLong(Predicate::NODE_TYPE, Predicate::TYPE_FEATURE_RANGE);
+ checkDeserialization("foo_in_x_x", std::move(slime));
+
+ std::unique_ptr<Slime> slime1 = builder.feature("foo").value("bar").build();
+ std::unique_ptr<Slime> slime2 = builder.feature("baz").value("cox").build();
+ builder.and_node(std::move(slime1), std::move(slime2));
+ checkDeserialization("foo_in_bar_and_baz_in_cox", builder.build());
+
+ slime1 = builder.feature("foo").value("bar").build();
+ slime2 = builder.feature("baz").value("cox").build();
+ builder.or_node(std::move(slime1), std::move(slime2));
+ checkDeserialization("foo_in_bar_or_baz_in_cox", builder.build());
+
+ builder.true_predicate();
+ checkDeserialization("true", builder.build());
+
+ builder.false_predicate();
+ checkDeserialization("false", builder.build());
+}
+
+namespace
+{
+
+Tensor::UP
+createTensor(const TensorCells &cells, const TensorDimensions &dimensions) {
+ vespalib::tensor::DefaultTensor::builder builder;
+ return vespalib::tensor::TensorFactory::create(cells, dimensions, builder);
+}
+
+
+}
+
+TEST("Require that tensors can be serialized")
+{
+ TensorFieldValue noTensorValue;
+ TensorFieldValue emptyTensorValue;
+ TensorFieldValue twoCellsTwoDimsValue;
+ nbostream stream;
+ serializeAndDeserialize(noTensorValue, stream);
+ stream.clear();
+ emptyTensorValue = createTensor({}, {});
+ serializeAndDeserialize(emptyTensorValue, stream);
+ stream.clear();
+ twoCellsTwoDimsValue = createTensor({ {{{"y", "3"}}, 3},
+ {{{"x", "4"}, {"y", "5"}}, 7} },
+ {"x", "y"});
+ serializeAndDeserialize(twoCellsTwoDimsValue, stream);
+ EXPECT_NOT_EQUAL(noTensorValue, emptyTensorValue);
+ EXPECT_NOT_EQUAL(noTensorValue, twoCellsTwoDimsValue);
+ EXPECT_NOT_EQUAL(emptyTensorValue, twoCellsTwoDimsValue);
+}
+
+
+const int tensor_doc_type_id = 321;
+const string tensor_field_name = "my_tensor";
+
+DocumenttypesConfig getTensorDocTypesConfig() {
+ DocumenttypesConfigBuilderHelper builder;
+ builder.document(tensor_doc_type_id, "my_type",
+ Struct("my_type.header"),
+ Struct("my_type.body")
+ .addField(tensor_field_name, DataType::T_TENSOR));
+ return builder.config();
+}
+
+const DocumentTypeRepo tensor_doc_repo(getTensorDocTypesConfig());
+const FixedTypeRepo tensor_repo(tensor_doc_repo,
+ *tensor_doc_repo.getDocumentType(doc_type_id));
+
+void serializeToFile(TensorFieldValue &value, const string &file_name) {
+ const DocumentType *type =
+ tensor_doc_repo.getDocumentType(tensor_doc_type_id);
+ serializeToFile(value, file_name, type, tensor_field_name);
+}
+
+void deserializeAndCheck(const string &file_name, TensorFieldValue &value) {
+ deserializeAndCheck(file_name, value, tensor_repo, tensor_field_name);
+}
+
+void checkDeserialization(const string &name, std::unique_ptr<Tensor> tensor) {
+ const string data_dir = "../../test/resources/tensor/";
+
+ TensorFieldValue value;
+ if (tensor) {
+ value = tensor->clone();
+ }
+ serializeToFile(value, data_dir + name + "__cpp");
+ deserializeAndCheck(data_dir + name + "__cpp", value);
+ deserializeAndCheck(data_dir + name + "__java", value);
+}
+
+TEST("Require that tensor deserialization matches Java") {
+ checkDeserialization("non_existing_tensor", std::unique_ptr<Tensor>());
+ checkDeserialization("empty_tensor", createTensor({}, {}));
+ checkDeserialization("multi_cell_tensor",
+ createTensor({ {{{"dimX", "a"}, {"dimY", "bb"}}, 2.0 },
+ {{{"dimX", "ccc"},
+ {"dimY", "dddd"}}, 3.0},
+ {{{"dimX", "e"}}, 5.0} },
+ { "dimX", "dimY" }));
+}
+
+} // namespace
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/setfieldvaluetest.h b/document/src/tests/setfieldvaluetest.h
new file mode 100644
index 00000000000..a27aeedf11d
--- /dev/null
+++ b/document/src/tests/setfieldvaluetest.h
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class SetFieldValue_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( SetFieldValue_Test);
+ CPPUNIT_TEST(testSet);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp();
+
+ void tearDown();
+protected:
+ void testSet();
+};
+
+
diff --git a/document/src/tests/stringtokenizertest.cpp b/document/src/tests/stringtokenizertest.cpp
new file mode 100644
index 00000000000..5ac41992621
--- /dev/null
+++ b/document/src/tests/stringtokenizertest.cpp
@@ -0,0 +1,109 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <iostream>
+#include <set>
+#include <sstream>
+#include <vespa/vespalib/text/stringtokenizer.h>
+
+using vespalib::StringTokenizer;
+using std::string;
+
+class StringTokenizerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(StringTokenizerTest);
+ CPPUNIT_TEST(testSimpleUsage);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ void testSimpleUsage();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(StringTokenizerTest);
+
+void StringTokenizerTest::testSimpleUsage()
+{
+ {
+ string s("This,is ,a,,list ,\tof,,sepa rated\n, \rtokens,");
+ StringTokenizer tokenizer(s);
+ StringTokenizer::TokenList result;
+ result.push_back("This");
+ result.push_back("is");
+ result.push_back("a");
+ result.push_back("");
+ result.push_back("list");
+ result.push_back("of");
+ result.push_back("");
+ result.push_back("sepa rated");
+ result.push_back("tokens");
+ result.push_back("");
+
+ CPPUNIT_ASSERT_EQUAL(result.size(),
+ static_cast<size_t>(tokenizer.size()));
+ for (unsigned int i=0; i<result.size(); i++) {
+ CPPUNIT_ASSERT_EQUAL(result[i], tokenizer[i]);
+ }
+ std::set<string> sorted(tokenizer.begin(), tokenizer.end());
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(8u), sorted.size());
+
+ tokenizer.removeEmptyTokens();
+ CPPUNIT_ASSERT_EQUAL(7u, tokenizer.size());
+ }
+ {
+ string s("\tAnother list with some \ntokens, and stuff.");
+ StringTokenizer tokenizer(s, " \t\n", ",.");
+ StringTokenizer::TokenList result;
+ result.push_back("");
+ result.push_back("Another");
+ result.push_back("list");
+ result.push_back("with");
+ result.push_back("some");
+ result.push_back("");
+ result.push_back("tokens");
+ result.push_back("and");
+ result.push_back("stuff");
+
+ CPPUNIT_ASSERT_EQUAL(result.size(),
+ static_cast<size_t>(tokenizer.size()));
+ for (unsigned int i=0; i<result.size(); i++) {
+ CPPUNIT_ASSERT_EQUAL(result[i], tokenizer[i]);
+ }
+ std::set<string> sorted(tokenizer.begin(), tokenizer.end());
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(8u), sorted.size());
+
+ tokenizer.removeEmptyTokens();
+ CPPUNIT_ASSERT_EQUAL(7u, tokenizer.size());
+ }
+ {
+ string s(" ");
+ StringTokenizer tokenizer(s);
+ CPPUNIT_ASSERT_EQUAL(0u, tokenizer.size());
+ }
+
+ {
+ string s("");
+ StringTokenizer tokenizer(s);
+ CPPUNIT_ASSERT_EQUAL(0u, tokenizer.size());
+ }
+ {
+ // Test that there aren't any problems with using signed chars.
+ string s("Here\x01\xff be\xff\xfe dragons\xff");
+ StringTokenizer tokenizer(s, "\xff", "\x01 \xfe");
+ StringTokenizer::TokenList result;
+ result.push_back("Here");
+ result.push_back("be");
+ result.push_back("dragons");
+ result.push_back("");
+
+ CPPUNIT_ASSERT_EQUAL(result.size(),
+ static_cast<size_t>(tokenizer.size()));
+ for (unsigned int i=0; i<result.size(); i++) {
+ CPPUNIT_ASSERT_EQUAL(result[i], tokenizer[i]);
+ }
+ std::set<string> sorted(tokenizer.begin(), tokenizer.end());
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4u), sorted.size());
+
+ tokenizer.removeEmptyTokens();
+ CPPUNIT_ASSERT_EQUAL(3u, tokenizer.size());
+ }
+}
diff --git a/document/src/tests/struct_anno/.gitignore b/document/src/tests/struct_anno/.gitignore
new file mode 100644
index 00000000000..7a5ecfc76aa
--- /dev/null
+++ b/document/src/tests/struct_anno/.gitignore
@@ -0,0 +1,2 @@
+/struct_anno_test
+document_struct_anno_test_app
diff --git a/document/src/tests/struct_anno/CMakeLists.txt b/document/src/tests/struct_anno/CMakeLists.txt
new file mode 100644
index 00000000000..64678282842
--- /dev/null
+++ b/document/src/tests/struct_anno/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_struct_anno_test_app
+ SOURCES
+ struct_anno_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_struct_anno_test_app COMMAND document_struct_anno_test_app)
diff --git a/document/src/tests/struct_anno/document.dat b/document/src/tests/struct_anno/document.dat
new file mode 100644
index 00000000000..243b5f05a92
--- /dev/null
+++ b/document/src/tests/struct_anno/document.dat
Binary files differ
diff --git a/document/src/tests/struct_anno/documentmanager.cfg b/document/src/tests/struct_anno/documentmanager.cfg
new file mode 100644
index 00000000000..94020ca7b69
--- /dev/null
+++ b/document/src/tests/struct_anno/documentmanager.cfg
@@ -0,0 +1,160 @@
+enablecompression false
+annotationtype[0]
+datatype[8]
+datatype[0].id -1049517126
+datatype[0].annotationreftype[0]
+datatype[0].arraytype[0]
+datatype[0].documenttype[1]
+datatype[0].documenttype[0].bodystruct 1008689562
+datatype[0].documenttype[0].headerstruct 166307397
+datatype[0].documenttype[0].name "simple_def"
+datatype[0].documenttype[0].version 0
+datatype[0].documenttype[0].inherits[1]
+datatype[0].documenttype[0].inherits[0].name "document"
+datatype[0].documenttype[0].inherits[0].version 0
+datatype[0].maptype[0]
+datatype[0].structtype[0]
+datatype[0].weightedsettype[0]
+datatype[1].id -1267268530
+datatype[1].annotationreftype[0]
+datatype[1].arraytype[0]
+datatype[1].documenttype[0]
+datatype[1].maptype[0]
+datatype[1].structtype[1]
+datatype[1].structtype[0].compresslevel 0
+datatype[1].structtype[0].compressminsize 800
+datatype[1].structtype[0].compressthreshold 95
+datatype[1].structtype[0].compresstype NONE
+datatype[1].structtype[0].name "simple_def_search.header"
+datatype[1].structtype[0].version 0
+datatype[1].structtype[0].field[3]
+datatype[1].structtype[0].field[0].datatype 111553393
+datatype[1].structtype[0].field[0].name "my_url"
+datatype[1].structtype[0].field[0].id[0]
+datatype[1].structtype[0].field[1].datatype 2
+datatype[1].structtype[0].field[1].name "rankfeatures"
+datatype[1].structtype[0].field[1].id[0]
+datatype[1].structtype[0].field[2].datatype 2
+datatype[1].structtype[0].field[2].name "summaryfeatures"
+datatype[1].structtype[0].field[2].id[0]
+datatype[1].structtype[0].inherits[0]
+datatype[1].weightedsettype[0]
+datatype[2].id -495710767
+datatype[2].annotationreftype[0]
+datatype[2].arraytype[0]
+datatype[2].documenttype[1]
+datatype[2].documenttype[0].bodystruct 1968090595
+datatype[2].documenttype[0].headerstruct -1267268530
+datatype[2].documenttype[0].name "simple_def_search"
+datatype[2].documenttype[0].version 0
+datatype[2].documenttype[0].inherits[1]
+datatype[2].documenttype[0].inherits[0].name "document"
+datatype[2].documenttype[0].inherits[0].version 0
+datatype[2].maptype[0]
+datatype[2].structtype[0]
+datatype[2].weightedsettype[0]
+datatype[3].id 1008689562
+datatype[3].annotationreftype[0]
+datatype[3].arraytype[0]
+datatype[3].documenttype[0]
+datatype[3].maptype[0]
+datatype[3].structtype[1]
+datatype[3].structtype[0].compresslevel 0
+datatype[3].structtype[0].compressminsize 800
+datatype[3].structtype[0].compressthreshold 95
+datatype[3].structtype[0].compresstype NONE
+datatype[3].structtype[0].name "simple_def.body"
+datatype[3].structtype[0].version 0
+datatype[3].structtype[0].field[0]
+datatype[3].structtype[0].inherits[0]
+datatype[3].weightedsettype[0]
+datatype[4].id 111553393
+datatype[4].annotationreftype[0]
+datatype[4].arraytype[0]
+datatype[4].documenttype[0]
+datatype[4].maptype[0]
+datatype[4].structtype[1]
+datatype[4].structtype[0].compresslevel 0
+datatype[4].structtype[0].compressminsize 800
+datatype[4].structtype[0].compressthreshold 95
+datatype[4].structtype[0].compresstype NONE
+datatype[4].structtype[0].name "url"
+datatype[4].structtype[0].version 0
+datatype[4].structtype[0].field[7]
+datatype[4].structtype[0].field[0].datatype 2
+datatype[4].structtype[0].field[0].name "all"
+datatype[4].structtype[0].field[0].id[0]
+datatype[4].structtype[0].field[1].datatype 2
+datatype[4].structtype[0].field[1].name "fragment"
+datatype[4].structtype[0].field[1].id[0]
+datatype[4].structtype[0].field[2].datatype 2
+datatype[4].structtype[0].field[2].name "host"
+datatype[4].structtype[0].field[2].id[0]
+datatype[4].structtype[0].field[3].datatype 2
+datatype[4].structtype[0].field[3].name "path"
+datatype[4].structtype[0].field[3].id[0]
+datatype[4].structtype[0].field[4].datatype 2
+datatype[4].structtype[0].field[4].name "port"
+datatype[4].structtype[0].field[4].id[0]
+datatype[4].structtype[0].field[5].datatype 2
+datatype[4].structtype[0].field[5].name "query"
+datatype[4].structtype[0].field[5].id[0]
+datatype[4].structtype[0].field[6].datatype 2
+datatype[4].structtype[0].field[6].name "scheme"
+datatype[4].structtype[0].field[6].id[0]
+datatype[4].structtype[0].inherits[0]
+datatype[4].weightedsettype[0]
+datatype[5].id 1381038251
+datatype[5].annotationreftype[0]
+datatype[5].arraytype[0]
+datatype[5].documenttype[0]
+datatype[5].maptype[0]
+datatype[5].structtype[1]
+datatype[5].structtype[0].compresslevel 0
+datatype[5].structtype[0].compressminsize 800
+datatype[5].structtype[0].compressthreshold 95
+datatype[5].structtype[0].compresstype NONE
+datatype[5].structtype[0].name "position"
+datatype[5].structtype[0].version 0
+datatype[5].structtype[0].field[2]
+datatype[5].structtype[0].field[0].datatype 0
+datatype[5].structtype[0].field[0].name "x"
+datatype[5].structtype[0].field[0].id[0]
+datatype[5].structtype[0].field[1].datatype 0
+datatype[5].structtype[0].field[1].name "y"
+datatype[5].structtype[0].field[1].id[0]
+datatype[5].structtype[0].inherits[0]
+datatype[5].weightedsettype[0]
+datatype[6].id 166307397
+datatype[6].annotationreftype[0]
+datatype[6].arraytype[0]
+datatype[6].documenttype[0]
+datatype[6].maptype[0]
+datatype[6].structtype[1]
+datatype[6].structtype[0].compresslevel 0
+datatype[6].structtype[0].compressminsize 800
+datatype[6].structtype[0].compressthreshold 95
+datatype[6].structtype[0].compresstype NONE
+datatype[6].structtype[0].name "simple_def.header"
+datatype[6].structtype[0].version 0
+datatype[6].structtype[0].field[1]
+datatype[6].structtype[0].field[0].datatype 111553393
+datatype[6].structtype[0].field[0].name "my_url"
+datatype[6].structtype[0].field[0].id[0]
+datatype[6].structtype[0].inherits[0]
+datatype[6].weightedsettype[0]
+datatype[7].id 1968090595
+datatype[7].annotationreftype[0]
+datatype[7].arraytype[0]
+datatype[7].documenttype[0]
+datatype[7].maptype[0]
+datatype[7].structtype[1]
+datatype[7].structtype[0].compresslevel 0
+datatype[7].structtype[0].compressminsize 800
+datatype[7].structtype[0].compressthreshold 95
+datatype[7].structtype[0].compresstype NONE
+datatype[7].structtype[0].name "simple_def_search.body"
+datatype[7].structtype[0].version 0
+datatype[7].structtype[0].field[0]
+datatype[7].structtype[0].inherits[0]
+datatype[7].weightedsettype[0]
diff --git a/document/src/tests/struct_anno/documenttypes.cfg b/document/src/tests/struct_anno/documenttypes.cfg
new file mode 100644
index 00000000000..38ef6d54125
--- /dev/null
+++ b/document/src/tests/struct_anno/documenttypes.cfg
@@ -0,0 +1,98 @@
+enablecompression false
+documenttype[2]
+documenttype[0].id -495710767
+documenttype[0].name "simple_def_search"
+documenttype[0].version 0
+documenttype[0].headerstruct -1267268530
+documenttype[0].bodystruct 1968090595
+documenttype[0].inherits[0]
+documenttype[0].datatype[2]
+documenttype[0].datatype[0].id -1267268530
+documenttype[0].datatype[0].type STRUCT
+documenttype[0].datatype[0].array.element.id 0
+documenttype[0].datatype[0].map.key.id 0
+documenttype[0].datatype[0].map.value.id 0
+documenttype[0].datatype[0].wset.key.id 0
+documenttype[0].datatype[0].wset.createifnonexistent false
+documenttype[0].datatype[0].wset.removeifzero false
+documenttype[0].datatype[0].annotationref.annotation.id 0
+documenttype[0].datatype[0].sstruct.name "simple_def_search.header"
+documenttype[0].datatype[0].sstruct.version 0
+documenttype[0].datatype[0].sstruct.compression.type NONE
+documenttype[0].datatype[0].sstruct.compression.level 0
+documenttype[0].datatype[0].sstruct.compression.threshold 90
+documenttype[0].datatype[0].sstruct.compression.minsize 0
+documenttype[0].datatype[0].sstruct.field[3]
+documenttype[0].datatype[0].sstruct.field[0].name "my_url"
+documenttype[0].datatype[0].sstruct.field[0].id 1127377169
+documenttype[0].datatype[0].sstruct.field[0].id_v6 707904318
+documenttype[0].datatype[0].sstruct.field[0].datatype 111553393
+documenttype[0].datatype[0].sstruct.field[1].name "rankfeatures"
+documenttype[0].datatype[0].sstruct.field[1].id 1883197392
+documenttype[0].datatype[0].sstruct.field[1].id_v6 699950698
+documenttype[0].datatype[0].sstruct.field[1].datatype 2
+documenttype[0].datatype[0].sstruct.field[2].name "summaryfeatures"
+documenttype[0].datatype[0].sstruct.field[2].id 1840337115
+documenttype[0].datatype[0].sstruct.field[2].id_v6 1981648971
+documenttype[0].datatype[0].sstruct.field[2].datatype 2
+documenttype[0].datatype[1].id 1968090595
+documenttype[0].datatype[1].type STRUCT
+documenttype[0].datatype[1].array.element.id 0
+documenttype[0].datatype[1].map.key.id 0
+documenttype[0].datatype[1].map.value.id 0
+documenttype[0].datatype[1].wset.key.id 0
+documenttype[0].datatype[1].wset.createifnonexistent false
+documenttype[0].datatype[1].wset.removeifzero false
+documenttype[0].datatype[1].annotationref.annotation.id 0
+documenttype[0].datatype[1].sstruct.name "simple_def_search.body"
+documenttype[0].datatype[1].sstruct.version 0
+documenttype[0].datatype[1].sstruct.compression.type NONE
+documenttype[0].datatype[1].sstruct.compression.level 0
+documenttype[0].datatype[1].sstruct.compression.threshold 90
+documenttype[0].datatype[1].sstruct.compression.minsize 0
+documenttype[0].datatype[1].sstruct.field[0]
+documenttype[0].annotationtype[0]
+documenttype[1].id -1049517126
+documenttype[1].name "simple_def"
+documenttype[1].version 0
+documenttype[1].headerstruct 166307397
+documenttype[1].bodystruct 1008689562
+documenttype[1].inherits[0]
+documenttype[1].datatype[2]
+documenttype[1].datatype[0].id 166307397
+documenttype[1].datatype[0].type STRUCT
+documenttype[1].datatype[0].array.element.id 0
+documenttype[1].datatype[0].map.key.id 0
+documenttype[1].datatype[0].map.value.id 0
+documenttype[1].datatype[0].wset.key.id 0
+documenttype[1].datatype[0].wset.createifnonexistent false
+documenttype[1].datatype[0].wset.removeifzero false
+documenttype[1].datatype[0].annotationref.annotation.id 0
+documenttype[1].datatype[0].sstruct.name "simple_def.header"
+documenttype[1].datatype[0].sstruct.version 0
+documenttype[1].datatype[0].sstruct.compression.type NONE
+documenttype[1].datatype[0].sstruct.compression.level 0
+documenttype[1].datatype[0].sstruct.compression.threshold 90
+documenttype[1].datatype[0].sstruct.compression.minsize 0
+documenttype[1].datatype[0].sstruct.field[1]
+documenttype[1].datatype[0].sstruct.field[0].name "my_url"
+documenttype[1].datatype[0].sstruct.field[0].id 1127377169
+documenttype[1].datatype[0].sstruct.field[0].id_v6 707904318
+documenttype[1].datatype[0].sstruct.field[0].datatype 111553393
+documenttype[1].datatype[1].id 1008689562
+documenttype[1].datatype[1].type STRUCT
+documenttype[1].datatype[1].array.element.id 0
+documenttype[1].datatype[1].map.key.id 0
+documenttype[1].datatype[1].map.value.id 0
+documenttype[1].datatype[1].wset.key.id 0
+documenttype[1].datatype[1].wset.createifnonexistent false
+documenttype[1].datatype[1].wset.removeifzero false
+documenttype[1].datatype[1].annotationref.annotation.id 0
+documenttype[1].datatype[1].sstruct.name "simple_def.body"
+documenttype[1].datatype[1].sstruct.version 0
+documenttype[1].datatype[1].sstruct.compression.type NONE
+documenttype[1].datatype[1].sstruct.compression.level 0
+documenttype[1].datatype[1].sstruct.compression.threshold 90
+documenttype[1].datatype[1].sstruct.compression.minsize 0
+documenttype[1].datatype[1].sstruct.field[0]
+documenttype[1].annotationtype[0]
diff --git a/document/src/tests/struct_anno/struct_anno_test.cpp b/document/src/tests/struct_anno/struct_anno_test.cpp
new file mode 100644
index 00000000000..85b3b8f2d0b
--- /dev/null
+++ b/document/src/tests/struct_anno/struct_anno_test.cpp
@@ -0,0 +1,87 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("struct_anno_test");
+
+#include <stdlib.h>
+#include <vespa/document/annotation/alternatespanlist.h>
+#include <vespa/document/annotation/annotation.h>
+#include <vespa/document/annotation/span.h>
+#include <vespa/document/annotation/spanlist.h>
+#include <vespa/document/annotation/spantree.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/document.h>
+#include <vespa/document/fieldvalue/stringfieldvalue.h>
+#include <vespa/document/repo/fixedtyperepo.h>
+#include <vespa/document/serialization/annotationdeserializer.h>
+#include <vespa/document/serialization/annotationserializer.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/document/serialization/vespadocumentserializer.h>
+#include <vespa/vespalib/objects/nbostream.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <sstream>
+#include <vector>
+
+using std::ostringstream;
+using std::string;
+using std::vector;
+using vespalib::nbostream;
+using namespace document;
+
+namespace {
+
+class Test : public vespalib::TestApp {
+ void requireThatStructFieldsCanContainAnnotations();
+
+public:
+ int Main();
+};
+
+int Test::Main() {
+ if (getenv("TEST_SUBSET") != 0) { return 0; }
+ TEST_INIT("struct_anno_test");
+ TEST_DO(requireThatStructFieldsCanContainAnnotations());
+ TEST_DONE();
+}
+
+template <typename T, int N> int arraysize(const T (&)[N]) { return N; }
+
+void Test::requireThatStructFieldsCanContainAnnotations() {
+ DocumentTypeRepo repo(readDocumenttypesConfig("documenttypes.cfg"));
+
+ FastOS_File file("document.dat");
+ ASSERT_TRUE(file.OpenReadOnlyExisting());
+ char buffer[1024];
+ ssize_t size = file.Read(buffer, arraysize(buffer));
+ ASSERT_TRUE(size != -1);
+
+ nbostream stream(buffer, size);
+ VespaDocumentDeserializer deserializer(repo, stream, 8);
+ Document doc;
+ deserializer.read(doc);
+
+ FieldValue::UP urlRef = doc.getValue("my_url");
+ ASSERT_TRUE(urlRef.get() != NULL);
+ const StructFieldValue *url = dynamic_cast<const StructFieldValue*>(urlRef.get());
+ ASSERT_TRUE(url != NULL);
+
+ FieldValue::UP strRef = url->getValue("scheme");
+ const StringFieldValue *str = dynamic_cast<const StringFieldValue*>(strRef.get());
+ ASSERT_TRUE(str != NULL);
+
+ SpanTree::UP tree = std::move(str->getSpanTrees().front());
+
+ EXPECT_EQUAL("my_tree", tree->getName());
+ const SimpleSpanList *root = dynamic_cast<const SimpleSpanList*>(&tree->getRoot());
+ ASSERT_TRUE(root != NULL);
+ EXPECT_EQUAL(1u, root->size());
+ SimpleSpanList::const_iterator it = root->begin();
+ EXPECT_EQUAL(Span(0, 6), (*it++));
+ EXPECT_TRUE(it == root->end());
+
+ EXPECT_EQUAL(1u, tree->numAnnotations());
+}
+
+} // namespace
+
+TEST_APPHOOK(Test);
diff --git a/document/src/tests/structfieldvaluetest.cpp b/document/src/tests/structfieldvaluetest.cpp
new file mode 100644
index 00000000000..c1bf53d5322
--- /dev/null
+++ b/document/src/tests/structfieldvaluetest.cpp
@@ -0,0 +1,185 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/repo/configbuilder.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/vespalib/objects/nbostream.h>
+
+using vespalib::nbostream;
+using document::config_builder::Struct;
+using document::config_builder::Wset;
+using document::config_builder::Array;
+using document::config_builder::Map;
+
+namespace document {
+
+struct StructFieldValueTest : public CppUnit::TestFixture {
+ void setUp() {}
+ void tearDown() {}
+
+ void testStruct();
+
+ CPPUNIT_TEST_SUITE(StructFieldValueTest);
+ CPPUNIT_TEST(testStruct);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(StructFieldValueTest);
+
+namespace {
+template <typename T>
+void deserialize(const ByteBuffer &buffer, T &value, const FixedTypeRepo &repo)
+{
+ uint16_t version = Document::getNewestSerializationVersion();
+ nbostream stream(buffer.getBufferAtPos(), buffer.getRemaining());
+ VespaDocumentDeserializer deserializer(repo, stream, version);
+ deserializer.read(value);
+}
+} // namespace
+
+void StructFieldValueTest::testStruct()
+{
+ config_builder::DocumenttypesConfigBuilderHelper builder;
+ builder.document(42, "test",
+ Struct("test.header")
+ .addField("int", DataType::T_INT)
+ .addField("long", DataType::T_LONG)
+ .addField("content", DataType::T_STRING),
+ Struct("test.body"));
+ DocumentTypeRepo doc_repo(builder.config());
+ const DocumentType *doc_type = doc_repo.getDocumentType(42);
+ CPPUNIT_ASSERT(doc_type);
+ FixedTypeRepo repo(doc_repo, *doc_type);
+ const DataType &type = *repo.getDataType("test.header");
+ StructFieldValue value(type);
+ const Field &intF = value.getField("int");
+ const Field &longF = value.getField("long");
+ const Field &strF = value.getField("content");
+
+ // Initially empty
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value.getSetFieldCount());
+ CPPUNIT_ASSERT(!value.hasValue(intF));
+
+ value.setValue(intF, IntFieldValue(1));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(1), value.getSetFieldCount());
+ CPPUNIT_ASSERT(value.hasValue(intF));
+
+ // Adding some more
+ value.setValue(longF, LongFieldValue(2));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(2), value.getSetFieldCount());
+ CPPUNIT_ASSERT_EQUAL(1, value.getValue(intF)->getAsInt());
+ CPPUNIT_ASSERT_EQUAL(2, value.getValue(longF)->getAsInt());
+
+ // Serialize & equality
+ std::unique_ptr<ByteBuffer> buffer(value.serialize());
+ buffer->flip();
+
+ CPPUNIT_ASSERT_EQUAL(buffer->getLength(), buffer->getLimit());
+ StructFieldValue value2(type);
+ CPPUNIT_ASSERT(value != value2);
+
+ deserialize(*buffer, value2, repo);
+
+ CPPUNIT_ASSERT(value2.hasValue(intF));
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+
+ // Various ways of removing
+ {
+ // By value
+ buffer->setPos(0);
+ deserialize(*buffer, value2, repo);
+ value2.remove(intF);
+ CPPUNIT_ASSERT(!value2.hasValue(intF));
+ CPPUNIT_ASSERT_EQUAL(size_t(1), value2.getSetFieldCount());
+
+ // Clearing all
+ buffer->setPos(0);
+ deserialize(*buffer, value2, repo);
+ value2.clear();
+ CPPUNIT_ASSERT(!value2.hasValue(intF));
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value2.getSetFieldCount());
+ }
+
+ // Updating
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ value2.setValue(strF, StringFieldValue("foo"));
+ CPPUNIT_ASSERT(value2.hasValue(strF));
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("foo"),
+ value2.getValue(strF)->getAsString());
+ CPPUNIT_ASSERT(value != value2);
+ value2.assign(value);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ StructFieldValue::UP valuePtr(value2.clone());
+
+ CPPUNIT_ASSERT(valuePtr.get());
+ CPPUNIT_ASSERT_EQUAL(value, *valuePtr);
+
+ // Iterating
+ const StructFieldValue& constVal(value);
+ for(StructFieldValue::const_iterator it = constVal.begin();
+ it != constVal.end(); ++it)
+ {
+ constVal.getValue(it.field());
+ }
+
+ // Comparison
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(0, value.compare(value2));
+ value2.remove(intF);
+ CPPUNIT_ASSERT(value.compare(value2) < 0);
+ CPPUNIT_ASSERT(value2.compare(value) > 0);
+ value2 = value;
+ value2.setValue(intF, IntFieldValue(5));
+ CPPUNIT_ASSERT(value.compare(value2) < 0);
+ CPPUNIT_ASSERT(value2.compare(value) > 0);
+
+ // Output
+ CPPUNIT_ASSERT_EQUAL(
+ std::string("Struct test.header(\n"
+ " int - 1,\n"
+ " long - 2\n"
+ ")"),
+ value.toString(false));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(" Struct test.header(\n"
+ ".. int - 1,\n"
+ ".. long - 2\n"
+ "..)"),
+ " " + value.toString(true, ".."));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string("<value>\n"
+ " <int>1</int>\n"
+ " <long>2</long>\n"
+ "</value>"),
+ value.toXml(" "));
+
+ // Failure situations.
+
+ // Refuse to accept non-struct types
+ try{
+ StructFieldValue value6(*DataType::DOCUMENT);
+ CPPUNIT_FAIL("Didn't complain about non-struct type");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot generate a struct value with "
+ "non-struct type", e.what());
+ }
+
+ // Refuse to set wrong types
+ try{
+ value2.setValue(intF, StringFieldValue("bar"));
+ CPPUNIT_FAIL("Failed to check type equality in setValue");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot assign value of type", e.what());
+ }
+}
+
+} // document
+
diff --git a/document/src/tests/tensor_fieldvalue/.gitignore b/document/src/tests/tensor_fieldvalue/.gitignore
new file mode 100644
index 00000000000..74ede436f4c
--- /dev/null
+++ b/document/src/tests/tensor_fieldvalue/.gitignore
@@ -0,0 +1 @@
+document_tensor_fieldvalue_test_app
diff --git a/document/src/tests/tensor_fieldvalue/CMakeLists.txt b/document/src/tests/tensor_fieldvalue/CMakeLists.txt
new file mode 100644
index 00000000000..d0fa1526bbc
--- /dev/null
+++ b/document/src/tests/tensor_fieldvalue/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(document_tensor_fieldvalue_test_app
+ SOURCES
+ tensor_fieldvalue_test.cpp
+ DEPENDS
+ document
+ AFTER
+ document_documentconfig
+)
+vespa_add_test(NAME document_tensor_fieldvalue_test_app COMMAND document_tensor_fieldvalue_test_app)
diff --git a/document/src/tests/tensor_fieldvalue/tensor_fieldvalue_test.cpp b/document/src/tests/tensor_fieldvalue/tensor_fieldvalue_test.cpp
new file mode 100644
index 00000000000..f0dd49c472c
--- /dev/null
+++ b/document/src/tests/tensor_fieldvalue/tensor_fieldvalue_test.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Unit tests for tensor_fieldvalue.
+
+#include <vespa/log/log.h>
+LOG_SETUP("fieldvalue_test");
+#include <vespa/fastos/fastos.h>
+
+#include <vespa/document/fieldvalue/tensorfieldvalue.h>
+#include <vespa/vespalib/tensor/tensor.h>
+#include <vespa/vespalib/tensor/types.h>
+#include <vespa/vespalib/tensor/default_tensor.h>
+#include <vespa/vespalib/tensor/tensor_factory.h>
+
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace document;
+using namespace vespalib::tensor;
+
+namespace
+{
+
+Tensor::UP
+createTensor(const TensorCells &cells, const TensorDimensions &dimensions) {
+ vespalib::tensor::DefaultTensor::builder builder;
+ return vespalib::tensor::TensorFactory::create(cells, dimensions, builder);
+}
+
+FieldValue::UP clone(FieldValue &fv) {
+ auto ret = FieldValue::UP(fv.clone());
+ EXPECT_NOT_EQUAL(ret.get(), &fv);
+ EXPECT_EQUAL(*ret, fv);
+ EXPECT_EQUAL(fv, *ret);
+ return ret;
+}
+
+}
+
+TEST("require that TensorFieldValue can be assigned tensors and cloned") {
+ TensorFieldValue noTensorValue;
+ TensorFieldValue emptyTensorValue;
+ TensorFieldValue twoCellsTwoDimsValue;
+ emptyTensorValue = createTensor({}, {});
+ twoCellsTwoDimsValue = createTensor({ {{{"y", "3"}}, 3},
+ {{{"x", "4"}, {"y", "5"}}, 7} },
+ {"x", "y"});
+ EXPECT_NOT_EQUAL(noTensorValue, emptyTensorValue);
+ EXPECT_NOT_EQUAL(noTensorValue, twoCellsTwoDimsValue);
+ EXPECT_NOT_EQUAL(emptyTensorValue, noTensorValue);
+ EXPECT_NOT_EQUAL(emptyTensorValue, twoCellsTwoDimsValue);
+ EXPECT_NOT_EQUAL(twoCellsTwoDimsValue, noTensorValue);
+ EXPECT_NOT_EQUAL(twoCellsTwoDimsValue, emptyTensorValue);
+ FieldValue::UP noneClone = clone(noTensorValue);
+ FieldValue::UP emptyClone = clone(emptyTensorValue);
+ FieldValue::UP twoClone = clone(twoCellsTwoDimsValue);
+ EXPECT_NOT_EQUAL(*noneClone, *emptyClone);
+ EXPECT_NOT_EQUAL(*noneClone, *twoClone);
+ EXPECT_NOT_EQUAL(*emptyClone, *noneClone);
+ EXPECT_NOT_EQUAL(*emptyClone, *twoClone);
+ EXPECT_NOT_EQUAL(*twoClone, *noneClone);
+ EXPECT_NOT_EQUAL(*twoClone, *emptyClone);
+ TensorFieldValue twoCellsTwoDimsValue2;
+ twoCellsTwoDimsValue2 =
+ createTensor({ {{{"y", "3"}}, 3},
+ {{{"x", "4"}, {"y", "5"}}, 7} },
+ {"x", "y"});
+ EXPECT_NOT_EQUAL(*noneClone, twoCellsTwoDimsValue2);
+ EXPECT_NOT_EQUAL(*emptyClone, twoCellsTwoDimsValue2);
+ EXPECT_EQUAL(*twoClone, twoCellsTwoDimsValue2);
+}
+
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/document/src/tests/testbytebuffer.cpp b/document/src/tests/testbytebuffer.cpp
new file mode 100644
index 00000000000..b4b8eb55e95
--- /dev/null
+++ b/document/src/tests/testbytebuffer.cpp
@@ -0,0 +1,493 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+/* $Id$*/
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/util/stringutil.h>
+#include <vespa/document/util/bytebuffer.h>
+#include <vespa/document/fieldvalue/serializablearray.h>
+#include "heapdebugger.h"
+#include <iostream>
+#include "testbytebuffer.h"
+#include <vespa/vespalib/util/macro.h>
+
+using namespace document;
+
+CPPUNIT_TEST_SUITE_REGISTRATION( ByteBuffer_Test );
+
+
+void ByteBuffer_Test::setUp()
+{
+ enableHeapUsageMonitor();
+}
+
+
+void ByteBuffer_Test::test_constructors()
+{
+ size_t MemUsedAtEntry=getHeapUsage();
+
+
+ ByteBuffer* simple=new ByteBuffer();
+ delete simple;
+
+ ByteBuffer* less_simple=new ByteBuffer("hei",3);
+ CPPUNIT_ASSERT(strcmp(less_simple->getBufferAtPos(),"hei")==0);
+ delete less_simple;
+
+ CPPUNIT_ASSERT(getHeapUsage()-MemUsedAtEntry == 0);
+}
+
+void ByteBuffer_Test::test_assignment_operator()
+{
+ size_t MemUsedAtEntry=getHeapUsage();
+ try {
+ ByteBuffer b1;
+ ByteBuffer b2 = b1;
+
+ CPPUNIT_ASSERT_EQUAL(b1.getPos(),b2.getPos());
+ CPPUNIT_ASSERT_EQUAL(b1.getLength(),b2.getLength());
+ CPPUNIT_ASSERT_EQUAL(b1.getLimit(),b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL(b1.getRemaining(),b2.getRemaining());
+
+ } catch (std::exception &e) {
+ fprintf(stderr,"Unexpected exception at %s: \"%s\"\n", VESPA_STRLOC.c_str(),e.what());
+ CPPUNIT_ASSERT(false);
+ }
+
+ try {
+ ByteBuffer b1(100);
+ b1.putInt(1);
+ b1.putInt(2);
+
+ ByteBuffer b2 = b1;
+
+ CPPUNIT_ASSERT_EQUAL(b1.getPos(),b2.getPos());
+ CPPUNIT_ASSERT_EQUAL(b1.getLength(),b2.getLength());
+ CPPUNIT_ASSERT_EQUAL(b1.getLimit(),b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL(b1.getRemaining(),b2.getRemaining());
+
+ int test = 0;
+ b2.flip();
+ b2.getInt(test);
+ CPPUNIT_ASSERT_EQUAL(1,test);
+ b2.getInt(test);
+ CPPUNIT_ASSERT_EQUAL(2,test);
+
+
+ CPPUNIT_ASSERT_EQUAL(b1.getPos(),b2.getPos());
+ CPPUNIT_ASSERT_EQUAL(b1.getLength(),b2.getLength());
+ CPPUNIT_ASSERT_EQUAL((size_t) 8,b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0,b2.getRemaining());
+
+ // Test Selfassignment == no change
+ //
+ b2 = b2;
+
+
+ CPPUNIT_ASSERT_EQUAL(b1.getPos(),b2.getPos());
+ CPPUNIT_ASSERT_EQUAL(b1.getLength(),b2.getLength());
+ CPPUNIT_ASSERT_EQUAL((size_t) 8,b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0,b2.getRemaining());
+
+ ByteBuffer b3;
+ // Empty
+ b2 = b3;
+
+ CPPUNIT_ASSERT_EQUAL((size_t) 0,b2.getPos());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0,b2.getLength());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0,b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL((size_t) 0,b2.getRemaining());
+
+ } catch (std::exception &e) {
+ fprintf(stderr,"Unexpected exception at %s: \"%s\"\n", VESPA_STRLOC.c_str(),e.what());
+ CPPUNIT_ASSERT(false);
+ }
+
+ CPPUNIT_ASSERT(getHeapUsage()-MemUsedAtEntry == 0);
+
+}
+
+void ByteBuffer_Test::test_copy_constructor()
+{
+ size_t MemUsedAtEntry=getHeapUsage();
+ try {
+ // Empty buffer first
+ ByteBuffer b1;
+ ByteBuffer b2(b1);
+
+ CPPUNIT_ASSERT_EQUAL(b1.getPos(),b2.getPos());
+ CPPUNIT_ASSERT_EQUAL(b1.getLength(),b2.getLength());
+ CPPUNIT_ASSERT_EQUAL(b1.getLimit(),b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL(b1.getRemaining(),b2.getRemaining());
+
+ } catch (std::exception &e) {
+ fprintf(stderr,"Unexpected exception at %s: %s\n", VESPA_STRLOC.c_str(),e.what());
+ CPPUNIT_ASSERT(false);
+ }
+
+ try {
+ ByteBuffer b1(100);
+ b1.putInt(1);
+ b1.putInt(2);
+ ByteBuffer b2(b1);
+
+
+ CPPUNIT_ASSERT_EQUAL(b1.getPos(),b2.getPos());
+ CPPUNIT_ASSERT_EQUAL(b1.getLength(),b2.getLength());
+ CPPUNIT_ASSERT_EQUAL(b1.getLimit(),b2.getLimit());
+ CPPUNIT_ASSERT_EQUAL(b1.getRemaining(),b2.getRemaining());
+
+ int test = 0;
+ b2.flip();
+ b2.getInt(test);
+ CPPUNIT_ASSERT_EQUAL(1,test);
+ b2.getInt(test);
+ CPPUNIT_ASSERT_EQUAL(2,test);
+
+ } catch (std::exception &e) {
+ fprintf(stderr,"Unexpected exception at %s: %s\n", VESPA_STRLOC.c_str(),e.what());
+ CPPUNIT_ASSERT(false);
+ }
+
+ CPPUNIT_ASSERT(getHeapUsage()-MemUsedAtEntry == 0);
+
+}
+
+void ByteBuffer_Test::test_slice()
+{
+ ByteBuffer* newBuf=ByteBuffer::copyBuffer("hei der",8);
+
+ ByteBuffer* slice = new ByteBuffer;
+ slice->sliceFrom(*newBuf, 0,newBuf->getLength());
+ delete newBuf;
+ newBuf = NULL;
+
+ CPPUNIT_ASSERT(strcmp(slice->getBufferAtPos(),"hei der")==0);
+
+ ByteBuffer* slice2 = new ByteBuffer;
+ slice2->sliceFrom(*slice, 4, slice->getLength());
+ delete slice;
+ slice = NULL;
+
+ CPPUNIT_ASSERT(strcmp(slice2->getBufferAtPos(),"der")==0);
+ CPPUNIT_ASSERT(strcmp(slice2->getBuffer(),"hei der")==0);
+ delete slice2;
+ slice2 = NULL;
+
+ ByteBuffer* newBuf2=new ByteBuffer("hei der", 8);
+ ByteBuffer* slice3=new ByteBuffer;
+ ByteBuffer* slice4=new ByteBuffer;
+
+ slice3->sliceFrom(*newBuf2, 4, newBuf2->getLength());
+ slice4->sliceFrom(*newBuf2, 0, newBuf2->getLength());
+ delete newBuf2;
+ newBuf2 = NULL;
+
+ CPPUNIT_ASSERT(strcmp(slice3->getBufferAtPos(),"der")==0);
+ CPPUNIT_ASSERT(strcmp(slice4->getBuffer(),"hei der")==0);
+
+ delete slice3;
+ slice3 = NULL;
+
+ CPPUNIT_ASSERT(strcmp(slice4->getBuffer(),"hei der")==0);
+
+ delete slice4;
+ slice4 = NULL;
+}
+
+void ByteBuffer_Test::test_slice2()
+{
+ size_t MemUsedAtEntry=getHeapUsage();
+
+ ByteBuffer* newBuf=ByteBuffer::copyBuffer("hei der",8);
+
+ ByteBuffer slice;
+ slice.sliceFrom(*newBuf, 0, newBuf->getLength());
+
+ delete newBuf;
+ newBuf = NULL;
+
+ CPPUNIT_ASSERT(strcmp(slice.getBufferAtPos(),"hei der")==0);
+
+ ByteBuffer slice2;
+ slice2.sliceFrom(slice, 4, slice.getLength());
+
+ CPPUNIT_ASSERT(strcmp(slice2.getBufferAtPos(),"der")==0);
+ CPPUNIT_ASSERT(strcmp(slice2.getBuffer(),"hei der")==0);
+
+ ByteBuffer* newBuf2=new ByteBuffer("hei der", 8);
+
+ slice.sliceFrom(*newBuf2, 4, newBuf2->getLength());
+ slice2.sliceFrom(*newBuf2, 0, newBuf2->getLength());
+ delete newBuf2;
+ newBuf2 = NULL;
+
+ CPPUNIT_ASSERT(strcmp(slice.getBufferAtPos(),"der")==0);
+ CPPUNIT_ASSERT(strcmp(slice2.getBuffer(),"hei der")==0);
+
+ CPPUNIT_ASSERT(getHeapUsage()-MemUsedAtEntry == 0);
+}
+
+
+void ByteBuffer_Test::test_putGetFlip()
+{
+ ByteBuffer* newBuf=new ByteBuffer(100);
+
+ try {
+ newBuf->putInt(10);
+ int test;
+ newBuf->flip();
+
+ newBuf->getInt(test);
+ CPPUNIT_ASSERT(test==10);
+
+ newBuf->clear();
+ newBuf->putDouble(3.35);
+ newBuf->flip();
+ CPPUNIT_ASSERT(newBuf->getRemaining()==sizeof(double));
+ double test2;
+ newBuf->getDouble(test2);
+ CPPUNIT_ASSERT(test2==3.35);
+
+ newBuf->clear();
+ newBuf->putBytes("heisann",8);
+ newBuf->putInt(4);
+ CPPUNIT_ASSERT(newBuf->getPos()==12);
+ CPPUNIT_ASSERT(newBuf->getLength()==100);
+ newBuf->flip();
+ CPPUNIT_ASSERT(newBuf->getRemaining()==12);
+
+ char testStr[12];
+ newBuf->getBytes(testStr, 8);
+ CPPUNIT_ASSERT(strcmp(testStr,"heisann")==0);
+ newBuf->getInt(test);
+ CPPUNIT_ASSERT(test==4);
+ } catch (std::exception &e) {
+ fprintf(stderr,"Unexpected exception at %s: %s\n", VESPA_STRLOC.c_str(),e.what());
+ CPPUNIT_ASSERT(false);
+ }
+ delete newBuf;
+}
+
+
+void ByteBuffer_Test::test_NumberEncodings()
+{
+ ByteBuffer* buf=new ByteBuffer(1024);
+
+ // Check 0
+ buf->putInt1_2_4Bytes(124);
+ buf->putInt2_4_8Bytes(124);
+ buf->putInt1_4Bytes(124);
+ // Check 1
+ buf->putInt1_2_4Bytes(127);
+ buf->putInt2_4_8Bytes(127);
+ buf->putInt1_4Bytes(127);
+ // Check 2
+ buf->putInt1_2_4Bytes(128);
+ buf->putInt2_4_8Bytes(128);
+ buf->putInt1_4Bytes(128);
+ // Check 3
+ buf->putInt1_2_4Bytes(255);
+ buf->putInt2_4_8Bytes(255);
+ buf->putInt1_4Bytes(255);
+ // Check 4
+ buf->putInt1_2_4Bytes(256);
+ buf->putInt2_4_8Bytes(256);
+ buf->putInt1_4Bytes(256);
+ // Check 5
+ buf->putInt1_2_4Bytes(0);
+ buf->putInt2_4_8Bytes(0);
+ buf->putInt1_4Bytes(0);
+ // Check 6
+ buf->putInt1_2_4Bytes(1);
+ buf->putInt2_4_8Bytes(1);
+ buf->putInt1_4Bytes(1);
+
+ // Check 7
+ try {
+ buf->putInt1_2_4Bytes(0x7FFFFFFF);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+ buf->putInt2_4_8Bytes(0x7FFFFFFFll);
+ buf->putInt1_4Bytes(0x7FFFFFFF);
+
+ try {
+ buf->putInt2_4_8Bytes(0x7FFFFFFFFFFFFFFFll);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+
+ buf->putInt1_2_4Bytes(0x7FFF);
+ // Check 8
+ buf->putInt2_4_8Bytes(0x7FFFll);
+ buf->putInt1_4Bytes(0x7FFF);
+ buf->putInt1_2_4Bytes(0x7F);
+ // Check 9
+ buf->putInt2_4_8Bytes(0x7Fll);
+ buf->putInt1_4Bytes(0x7F);
+
+ try {
+ buf->putInt1_2_4Bytes(-1);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+ try {
+ buf->putInt2_4_8Bytes(-1);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+ try {
+ buf->putInt1_4Bytes(-1);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+
+ try {
+ buf->putInt1_2_4Bytes(-0x7FFFFFFF);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+ try {
+ buf->putInt2_4_8Bytes(-0x7FFFFFFF);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+ try {
+ buf->putInt1_4Bytes(-0x7FFFFFFF);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+
+ try {
+ buf->putInt2_4_8Bytes(-0x7FFFFFFFFFFFFFFFll);
+ CPPUNIT_ASSERT(false);
+ } catch (InputOutOfRangeException& e) { }
+
+ uint32_t endWritePos = buf->getPos();
+ buf->setPos(0);
+
+ int32_t tmp32;
+ int64_t tmp64;
+
+ // Check 0
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(124, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)124, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(124, tmp32);
+ // Check 1
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(127, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)127, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(127, tmp32);
+ // Check 2
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(128, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)128, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(128, tmp32);
+ // Check 3
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(255, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)255, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(255, tmp32);
+ // Check 4
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(256, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)256, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(256, tmp32);
+ // Check 5
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)0, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0, tmp32);
+ // Check 6
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(1, tmp32);
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)1, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(1, tmp32);
+ // Check 7
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)0x7FFFFFFF, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0x7FFFFFFF, tmp32);
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0x7FFF, tmp32);
+ // Check 8
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)0x7FFF, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0x7FFF, tmp32);
+ buf->getInt1_2_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0x7F, tmp32);
+ // Check 9
+ buf->getInt2_4_8Bytes(tmp64);
+ CPPUNIT_ASSERT_EQUAL((int64_t)0x7F, tmp64);
+ buf->getInt1_4Bytes(tmp32);
+ CPPUNIT_ASSERT_EQUAL(0x7F, tmp32);
+
+ uint32_t endReadPos = buf->getPos();
+ CPPUNIT_ASSERT_EQUAL(endWritePos, endReadPos);
+
+ delete buf;
+}
+
+void ByteBuffer_Test::test_NumberLengths()
+{
+ ByteBuffer b;
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_4Bytes(0));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_4Bytes(1));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_4Bytes(4));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_4Bytes(31));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_4Bytes(126));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_4Bytes(127));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_4Bytes(128));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_4Bytes(129));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_4Bytes(255));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_4Bytes(256));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_4Bytes(0x7FFFFFFF));
+
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(0));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(1));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(4));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(31));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(126));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(127));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(128));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize2_4_8Bytes(32767));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize2_4_8Bytes(32768));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize2_4_8Bytes(32769));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize2_4_8Bytes(1030493));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize2_4_8Bytes(0x3FFFFFFF));
+ CPPUNIT_ASSERT_EQUAL((size_t) 8, b.getSerializedSize2_4_8Bytes(0x40000000));
+ CPPUNIT_ASSERT_EQUAL((size_t) 8, b.getSerializedSize2_4_8Bytes(0x40000001));
+
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_2_4Bytes(0));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_2_4Bytes(1));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_2_4Bytes(4));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_2_4Bytes(31));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_2_4Bytes(126));
+ CPPUNIT_ASSERT_EQUAL((size_t) 1, b.getSerializedSize1_2_4Bytes(127));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize1_2_4Bytes(128));
+ CPPUNIT_ASSERT_EQUAL((size_t) 2, b.getSerializedSize1_2_4Bytes(16383));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_2_4Bytes(16384));
+ CPPUNIT_ASSERT_EQUAL((size_t) 4, b.getSerializedSize1_2_4Bytes(16385));
+}
+
+void ByteBuffer_Test::test_SerializableArray()
+{
+ SerializableArray array;
+ array.set(0,"http",4);
+ CPPUNIT_ASSERT_EQUAL(4ul, array.get(0).size());
+ SerializableArray copy(array);
+ CPPUNIT_ASSERT_EQUAL(4ul, array.get(0).size());
+ CPPUNIT_ASSERT_EQUAL(copy.get(0).size(), array.get(0).size());
+ CPPUNIT_ASSERT(copy.get(0).c_str() != array.get(0).c_str());
+ CPPUNIT_ASSERT_EQUAL(0, strcmp(copy.get(0).c_str(), array.get(0).c_str()));
+ CPPUNIT_ASSERT_EQUAL(16ul, sizeof(SerializableArray::Entry));
+}
diff --git a/document/src/tests/testbytebuffer.h b/document/src/tests/testbytebuffer.h
new file mode 100644
index 00000000000..58962d73449
--- /dev/null
+++ b/document/src/tests/testbytebuffer.h
@@ -0,0 +1,73 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+/**
+ CPPUnit test case for ByteBuffer class.
+*/
+class ByteBuffer_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( ByteBuffer_Test);
+ CPPUNIT_TEST(test_constructors);
+ CPPUNIT_TEST(test_copy_constructor);
+ CPPUNIT_TEST(test_assignment_operator);
+ CPPUNIT_TEST(test_slice);
+ CPPUNIT_TEST(test_slice2);
+ CPPUNIT_TEST(test_putGetFlip);
+ CPPUNIT_TEST(test_NumberEncodings);
+ CPPUNIT_TEST(test_NumberLengths);
+ CPPUNIT_TEST(test_SerializableArray);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ /**
+ Initialization.
+ */
+ void setUp();
+
+protected:
+ /**
+ Test construction and deletion.
+ */
+ void test_constructors();
+ void test_SerializableArray();
+
+ /**
+ Test copy constructor
+ */
+ void test_copy_constructor();
+ /**
+ Test construction and deletion.
+ */
+ void test_assignment_operator();
+
+ /**
+ Test the slice() method
+ */
+ void test_slice();
+
+ /**
+ Test the slice2() method
+ */
+ void test_slice2();
+
+ /**
+ Test put(), get() and flip() methods.
+ */
+ void test_putGetFlip();
+
+ /**
+ Test writing integers with funny encodings.
+ */
+ void test_NumberEncodings();
+
+ /**
+ Tests lengths of those encodings.
+ */
+ void test_NumberLengths();
+
+};
+
+
diff --git a/document/src/tests/testcase.h b/document/src/tests/testcase.h
new file mode 100644
index 00000000000..68ac5066c2d
--- /dev/null
+++ b/document/src/tests/testcase.h
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class Document_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( Document_Test);
+ CPPUNIT_TEST(testShit);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ void testShit();
+};
+
+
diff --git a/document/src/tests/testdocmantest.cpp b/document/src/tests/testdocmantest.cpp
new file mode 100644
index 00000000000..14140b4b508
--- /dev/null
+++ b/document/src/tests/testdocmantest.cpp
@@ -0,0 +1,62 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+/* $Id$*/
+
+#include <vespa/fastos/fastos.h>
+#include <iostream>
+#include <set>
+#include <sstream>
+#include <vespa/document/base/testdocman.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+
+namespace document {
+
+class TestDocManTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TestDocManTest);
+ CPPUNIT_TEST(testSimpleUsage);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ void testSimpleUsage();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestDocManTest);
+
+void TestDocManTest::testSimpleUsage()
+{
+ TestDocMan testdm;
+ Document::UP doc1(testdm.createRandomDocument());
+ Document::UP doc2(testdm.createRandomDocument());
+ Document::UP doc3(testdm.createRandomDocument(1));
+ {
+ FieldValue::UP v(doc1->getValue(doc1->getField("content")));
+ StringFieldValue& sval(dynamic_cast<StringFieldValue&>(*v));
+ CPPUNIT_ASSERT_EQUAL(std::string("To be, or "),
+ std::string(sval.getValue().c_str()));
+
+ FieldValue::UP v2(doc2->getValue(doc2->getField("content")));
+ StringFieldValue& sval2(dynamic_cast<StringFieldValue&>(*v));
+ CPPUNIT_ASSERT_EQUAL(std::string(sval.getValue().c_str()),
+ std::string(sval2.getValue().c_str()));
+ }
+ {
+ FieldValue::UP v(doc3->getValue(doc3->getField("content")));
+ StringFieldValue& sval(dynamic_cast<StringFieldValue&>(*v));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string("To be, or not to be: that is the question:\n"
+ "Whether 'tis nobler in the mind to suffer\n"
+ "The slings and a"),
+ std::string(sval.getValue().c_str()));
+ }
+ CPPUNIT_ASSERT_EQUAL(
+ vespalib::string("id:mail:testdoctype1:n=51019:192.html"),
+ doc1->getId().toString());
+ CPPUNIT_ASSERT_EQUAL(
+ vespalib::string("id:mail:testdoctype1:n=51019:192.html"),
+ doc2->getId().toString());
+ CPPUNIT_ASSERT_EQUAL(
+ vespalib::string("id:mail:testdoctype1:n=10744:245.html"),
+ doc3->getId().toString());
+}
+
+} // document
diff --git a/document/src/tests/testrunner.cpp b/document/src/tests/testrunner.cpp
new file mode 100644
index 00000000000..aabdbb3f605
--- /dev/null
+++ b/document/src/tests/testrunner.cpp
@@ -0,0 +1,15 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+#include <vespa/vdstestlib/cppunit/cppunittestrunner.h>
+
+LOG_SETUP("documentcppunittests");
+
+int
+main(int argc, char **argv)
+{
+ vdstestlib::CppUnitTestRunner testRunner;
+ return testRunner.run(argc, argv);
+}
+
diff --git a/document/src/tests/teststringutil.cpp b/document/src/tests/teststringutil.cpp
new file mode 100644
index 00000000000..9d04372a19b
--- /dev/null
+++ b/document/src/tests/teststringutil.cpp
@@ -0,0 +1,112 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Test program for StringUtil methods
+ *
+ * @version $Id$
+ */
+
+#include <vespa/fastos/fastos.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <vespa/document/util/stringutil.h>
+#include "heapdebugger.h"
+#include "teststringutil.h"
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION( StringUtil_Test );
+
+using namespace document;
+using vespalib::string;
+
+void StringUtil_Test::setUp()
+{
+ enableHeapUsageMonitor();
+}
+
+void StringUtil_Test::test_escape()
+{
+ CPPUNIT_ASSERT_EQUAL(string("abz019ABZ"), StringUtil::escape("abz019ABZ"));
+ CPPUNIT_ASSERT_EQUAL(string("\\t"), StringUtil::escape("\t"));
+ CPPUNIT_ASSERT_EQUAL(string("\\n"), StringUtil::escape("\n"));
+ CPPUNIT_ASSERT_EQUAL(string("\\r"), StringUtil::escape("\r"));
+ CPPUNIT_ASSERT_EQUAL(string("\\\""), StringUtil::escape("\""));
+ CPPUNIT_ASSERT_EQUAL(string("\\f"), StringUtil::escape("\f"));
+ CPPUNIT_ASSERT_EQUAL(string("\\\\"), StringUtil::escape("\\"));
+ CPPUNIT_ASSERT_EQUAL(string("\\x05"), StringUtil::escape("\x05"));
+ CPPUNIT_ASSERT_EQUAL(string("\\tA\\ncombined\\r\\x055test"),
+ StringUtil::escape("\tA\ncombined\r\x05""5test"));
+ CPPUNIT_ASSERT_EQUAL(string("A\\x20space\\x20separated\\x20string"),
+ StringUtil::escape("A space separated string", ' '));
+}
+
+void StringUtil_Test::test_unescape()
+{
+ CPPUNIT_ASSERT_EQUAL(string("abz019ABZ"),
+ StringUtil::unescape("abz019ABZ"));
+ CPPUNIT_ASSERT_EQUAL(string("\t"), StringUtil::unescape("\\t"));
+ CPPUNIT_ASSERT_EQUAL(string("\n"), StringUtil::unescape("\\n"));
+ CPPUNIT_ASSERT_EQUAL(string("\r"), StringUtil::unescape("\\r"));
+ CPPUNIT_ASSERT_EQUAL(string("\""), StringUtil::unescape("\\\""));
+ CPPUNIT_ASSERT_EQUAL(string("\f"), StringUtil::unescape("\\f"));
+ CPPUNIT_ASSERT_EQUAL(string("\\"), StringUtil::unescape("\\\\"));
+ CPPUNIT_ASSERT_EQUAL(string("\x05"), StringUtil::unescape("\\x05"));
+ CPPUNIT_ASSERT_EQUAL(string("\tA\ncombined\r\x05""5test"),
+ StringUtil::unescape("\\tA\\ncombined\\r\\x055test"));
+ CPPUNIT_ASSERT_EQUAL(string("A space separated string"),
+ StringUtil::unescape("A\\x20space\\x20separated\\x20string"));
+}
+
+void StringUtil_Test::test_printAsHex()
+{
+ std::vector<char> asciitable(256);
+ for (uint32_t i=0; i<256; ++i) asciitable[i] = i;
+ std::ostringstream ost;
+ ost << "\n ";
+ StringUtil::printAsHex(ost, &asciitable[0], asciitable.size(),
+ 16, true, " ");
+ std::string expected("\n"
+ " 0: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"
+ " 16: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f\n"
+ " 32: 20 ! \" # $ % & ' ( ) * + , - . /\n"
+ " 48: 0 1 2 3 4 5 6 7 8 9 : ; < = > ?\n"
+ " 64: @ A B C D E F G H I J K L M N O\n"
+ " 80: P Q R S T U V W X Y Z [ \\ ] ^ _\n"
+ " 96: ` a b c d e f g h i j k l m n o\n"
+ " 112: p q r s t u v w x y z { | } ~ 7f\n"
+ " 128: 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f\n"
+ " 144: 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f\n"
+ " 160: a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af\n"
+ " 176: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf\n"
+ " 192: c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf\n"
+ " 208: d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df\n"
+ " 224: e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef\n"
+ " 240: f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff");
+ CPPUNIT_ASSERT_EQUAL(expected, ost.str());
+
+ ost.str("");
+ ost << "\n";
+ StringUtil::printAsHex(ost, &asciitable[0], asciitable.size(),
+ 15, false);
+ expected = "\n"
+ " 0: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e ...............\n"
+ " 15: 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d ...............\n"
+ " 30: 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c ...!\"#$%&'()*+,\n"
+ " 45: 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b -./0123456789:;\n"
+ " 60: 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a <=>?@ABCDEFGHIJ\n"
+ " 75: 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 KLMNOPQRSTUVWXY\n"
+ " 90: 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 Z[\\]^_`abcdefgh\n"
+ "105: 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 ijklmnopqrstuvw\n"
+ "120: 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 xyz{|}~........\n"
+ "135: 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 ...............\n"
+ "150: 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 ...............\n"
+ "165: a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 ...............\n"
+ "180: b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 ...............\n"
+ "195: c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 ...............\n"
+ "210: d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 ...............\n"
+ "225: e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ...............\n"
+ "240: f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ...............\n"
+ "255: ff .";
+ CPPUNIT_ASSERT_EQUAL(expected, ost.str());
+}
diff --git a/document/src/tests/teststringutil.h b/document/src/tests/teststringutil.h
new file mode 100644
index 00000000000..be25c9667d3
--- /dev/null
+++ b/document/src/tests/teststringutil.h
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+/* $Id$*/
+
+#pragma once
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class StringUtil_Test : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE( StringUtil_Test);
+ CPPUNIT_TEST(test_escape);
+ CPPUNIT_TEST(test_unescape);
+ CPPUNIT_TEST(test_printAsHex);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp();
+
+protected:
+ void test_escape();
+ void test_unescape();
+ void test_printAsHex();
+
+};
+
+
diff --git a/document/src/tests/testxml.cpp b/document/src/tests/testxml.cpp
new file mode 100644
index 00000000000..7729c748b3f
--- /dev/null
+++ b/document/src/tests/testxml.cpp
@@ -0,0 +1,153 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+/* $Id$*/
+
+#include <vespa/fastos/fastos.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <iostream>
+#include <set>
+#include <sstream>
+#include <vespa/document/update/documentupdate.h>
+#include <vespa/document/update/fieldupdate.h>
+#include <vespa/document/update/addvalueupdate.h>
+#include <vespa/document/update/assignvalueupdate.h>
+#include <vespa/document/update/removevalueupdate.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/vespalib/text/stringtokenizer.h>
+
+using vespalib::StringTokenizer;
+
+namespace document {
+
+class TestXml : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TestXml);
+ CPPUNIT_TEST(testSimpleUsage);
+ CPPUNIT_TEST(testDocumentUpdate);
+ CPPUNIT_TEST_SUITE_END();
+
+ void testSimpleUsage();
+ void testDocumentUpdate();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestXml);
+
+namespace {
+
+Document::UP createTestDocument(const DocumentTypeRepo& repo)
+{
+ const DocumentType* type(repo.getDocumentType("testdoc"));
+ Document::UP
+ doc(new Document(*type,
+ DocumentId("doc:crawler/http://www.ntnu.no/")));
+ doc->setRepo(repo);
+ std::string s("humlepungens buffer");
+ ByteBuffer bb(s.c_str(), s.size());
+
+ doc->setValue(doc->getField("intattr"), IntFieldValue(50));
+ doc->setValue(doc->getField("rawattr"), RawFieldValue("readable hei der", 7));
+ doc->setValue(doc->getField("floatattr"), FloatFieldValue(3.56));
+ doc->setValue(doc->getField("stringattr"), StringFieldValue("tjo hei"));
+
+ doc->setValue(doc->getField("doubleattr"), DoubleFieldValue(17.78623142376453));
+ doc->setValue(doc->getField("longattr"), LongFieldValue(346234765345239657LL));
+ doc->setValue(doc->getField("byteattr"), ByteFieldValue('J'));
+
+ ArrayFieldValue val(doc->getField("rawarrayattr").getDataType());
+ RawFieldValue rawVal("readable hei", 3);
+ val.add(rawVal);
+ RawFieldValue rawVal2("readable hallo", 5);
+ val.add(rawVal2);
+ RawFieldValue rawVal3("readable hei der", 7);
+ val.add(rawVal3);
+ doc->setValue(doc->getField("rawarrayattr"), val);
+
+ Document::UP doc2(new Document(*type, DocumentId(
+ "doc:crawler/http://www.ntnu.no/2")));
+ doc2->setValue(doc2->getField("stringattr"), StringFieldValue("tjo hei paa du"));
+ doc->setValue(doc->getField("docfield"), *doc2);
+
+ return doc;
+}
+
+DocumentUpdate::UP
+createTestDocumentUpdate(const DocumentTypeRepo& repo)
+{
+ const DocumentType* type(repo.getDocumentType("testdoc"));
+ DocumentId id("doc:crawler/http://www.ntnu.no/");
+
+ DocumentUpdate::UP up(new DocumentUpdate(*type, id));
+ up->addUpdate(FieldUpdate(type->getField("intattr"))
+ .addUpdate(AssignValueUpdate(IntFieldValue(7))));
+ up->addUpdate(FieldUpdate(type->getField("stringattr"))
+ .addUpdate(AssignValueUpdate(
+ StringFieldValue("New value"))));
+ up->addUpdate(FieldUpdate(type->getField("arrayattr"))
+ .addUpdate(AddValueUpdate(IntFieldValue(123)))
+ .addUpdate(AddValueUpdate(IntFieldValue(456))));
+ up->addUpdate(FieldUpdate(type->getField("arrayattr"))
+ .addUpdate(RemoveValueUpdate(IntFieldValue(123)))
+ .addUpdate(RemoveValueUpdate(IntFieldValue(456)))
+ .addUpdate(RemoveValueUpdate(IntFieldValue(789))));
+ return up;
+}
+
+} // anonymous ns
+
+void TestXml::testSimpleUsage()
+{
+ DocumentTypeRepo repo(readDocumenttypesConfig("data/defaultdoctypes.cfg"));
+ Document::UP doc1(createTestDocument(repo));
+ doc1->setValue(doc1->getField("stringattr"), StringFieldValue("tjohei���"));
+
+ std::string expected =
+ "<document documenttype=\"testdoc\" documentid=\"doc:crawler/http://www.ntnu.no/\">\n"
+ " <doubleattr>17.7862</doubleattr>\n"
+ " <intattr>50</intattr>\n"
+ " <floatattr>3.56</floatattr>\n"
+ " <longattr>346234765345239657</longattr>\n"
+ " <byteattr>74</byteattr>\n"
+ " <rawarrayattr>\n"
+ " <item binaryencoding=\"base64\">cmVh</item>\n"
+ " <item binaryencoding=\"base64\">cmVhZGE=</item>\n"
+ " <item binaryencoding=\"base64\">cmVhZGFibA==</item>\n"
+ " </rawarrayattr>\n"
+ " <rawattr binaryencoding=\"base64\">cmVhZGFibA==</rawattr>\n"
+ " <stringattr>tjohei���</stringattr>\n"
+ " <docfield>\n"
+ " <document documenttype=\"testdoc\" documentid=\"doc:crawler/http://www.ntnu.no/2\">\n"
+ " <stringattr>tjo hei paa du</stringattr>\n"
+ " </document>\n"
+ " </docfield>\n"
+ " <content type=\"contenttype\" encoding=\"encoding\" language=\"language\">humlepungens buffer</content>\n"
+ "</document>";
+}
+
+void TestXml::testDocumentUpdate()
+{
+ DocumentTypeRepo repo(readDocumenttypesConfig("data/defaultdoctypes.cfg"));
+ DocumentUpdate::UP up1(createTestDocumentUpdate(repo));
+
+ std::string expected =
+ "<document type=\"testdoc\" id=\"doc:crawler/http://www.ntnu.no/\">\n"
+ " <alter field=\"intattr\">\n"
+ " <assign>7</assign>\n"
+ " </alter>\n"
+ " <alter field=\"stringattr\">\n"
+ " <assign>New value</assign>\n"
+ " </alter>\n"
+ " <alter field=\"arrayattr\">\n"
+ " <add weight=\"1\">123</add>\n"
+ " <add weight=\"1\">456</add>\n"
+ " </alter>\n"
+ " <alter field=\"arrayattr\">\n"
+ " <remove>123</remove>\n"
+ " <remove>456</remove>\n"
+ " <remove>789</remove>\n"
+ " </alter>\n"
+ "</document>";
+ CPPUNIT_ASSERT_EQUAL(expected, up1->toXml(" "));
+}
+
+} // document
diff --git a/document/src/tests/urltypetest.cpp b/document/src/tests/urltypetest.cpp
new file mode 100644
index 00000000000..0aa447b9e40
--- /dev/null
+++ b/document/src/tests/urltypetest.cpp
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/datatype/urldatatype.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace document {
+
+class UrlTypeTest : public CppUnit::TestFixture {
+public:
+ void requireThatNameIsCorrect();
+ void requireThatExpectedFieldsAreThere();
+
+ CPPUNIT_TEST_SUITE(UrlTypeTest);
+ CPPUNIT_TEST(requireThatNameIsCorrect);
+ CPPUNIT_TEST(requireThatExpectedFieldsAreThere);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(UrlTypeTest);
+
+void
+UrlTypeTest::requireThatNameIsCorrect()
+{
+ const StructDataType &type = UrlDataType::getInstance();
+ CPPUNIT_ASSERT_EQUAL(vespalib::string("url"), type.getName());
+}
+
+void
+UrlTypeTest::requireThatExpectedFieldsAreThere()
+{
+ const StructDataType &type = UrlDataType::getInstance();
+ Field field = type.getField("all");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+
+ field = type.getField("scheme");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+
+ field = type.getField("host");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+
+ field = type.getField("port");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+
+ field = type.getField("path");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+
+ field = type.getField("query");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+
+ field = type.getField("fragment");
+ CPPUNIT_ASSERT_EQUAL(*DataType::STRING, field.getDataType());
+}
+
+} // document
diff --git a/document/src/tests/vespaxml/fieldpathupdates.xml b/document/src/tests/vespaxml/fieldpathupdates.xml
new file mode 100755
index 00000000000..c775509f070
--- /dev/null
+++ b/document/src/tests/vespaxml/fieldpathupdates.xml
@@ -0,0 +1,54 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <update documenttype="news" documentid="doc:test:http://www.ntnu.no/">
+ <assign fieldpath="url">assignUrl</assign>
+ <assign fieldpath="title">assignTitle</assign>
+ <assign fieldpath="last_downloaded">1</assign>
+ <assign fieldpath="value_long">2</assign>
+ <assign fieldpath="value_content">assignContent</assign>
+ <assign fieldpath="stringarr">
+ <item>assignString1</item>
+ <item>assignString2</item>
+ </assign>
+ <assign fieldpath="intarr">
+ <item>3</item>
+ <item>4</item>
+ </assign>
+ <assign fieldpath="longarr">
+ <item>5</item>
+ <item>6</item>
+ </assign>
+ <assign fieldpath="bytearr">
+ <item>7</item>
+ <item>8</item>
+ </assign>
+ <assign fieldpath="floatarr">
+ <item>9</item>
+ <item>10</item>
+ </assign>
+ <assign fieldpath="weightedsetint">
+ <item weight="11">11</item>
+ <item weight="12">12</item>
+ </assign>
+ <assign fieldpath="weightedsetstring">
+ <item weight="13">assign13</item>
+ <item weight="14">assign14</item>
+ </assign>
+
+ <add fieldpath="stringarr">
+ <item>addString1</item>
+ <item>addString2</item>
+ </add>
+ <add fieldpath="longarr">
+ <item>5</item>
+ </add>
+
+ <assign fieldpath="weightedsetint{13}">13</assign>
+ <assign fieldpath="weightedsetint{14}">14</assign>
+ <assign fieldpath="weightedsetstring{add13}">1</assign>
+ <assign fieldpath="weightedsetstring{assign13}">130</assign>
+
+ <remove fieldpath="weightedsetstring{assign14}"/>
+ <remove fieldpath="bytearr"/>
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test1.expected.xml b/document/src/tests/vespaxml/test1.expected.xml
new file mode 100644
index 00000000000..a369cfff4a6
--- /dev/null
+++ b/document/src/tests/vespaxml/test1.expected.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<document documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+</document>
diff --git a/document/src/tests/vespaxml/test1.xml b/document/src/tests/vespaxml/test1.xml
new file mode 100644
index 00000000000..1c155182b14
--- /dev/null
+++ b/document/src/tests/vespaxml/test1.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd idprefix="doc:crawler:">
+ <document documenttype="news" documentid="http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test10.xml b/document/src/tests/vespaxml/test10.xml
new file mode 100644
index 00000000000..b310f14da1c
--- /dev/null
+++ b/document/src/tests/vespaxml/test10.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <title>Test<Title</title>
+ <last_downloaded>hundred</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test11.xml b/document/src/tests/vespaxml/test11.xml
new file mode 100644
index 00000000000..d8cdb32697e
--- /dev/null
+++ b/document/src/tests/vespaxml/test11.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <document type="news" id="doc:crawler/http://www.ntnu���.no/">
+ </document>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test12.xml b/document/src/tests/vespaxml/test12.xml
new file mode 100644
index 00000000000..1f2f3375ade
--- /dev/null
+++ b/document/src/tests/vespaxml/test12.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <title></title>
+ <last_downloaded></last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test13.xml b/document/src/tests/vespaxml/test13.xml
new file mode 100644
index 00000000000..8f12ea42c93
--- /dev/null
+++ b/document/src/tests/vespaxml/test13.xml
@@ -0,0 +1,7 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+</vespa>
diff --git a/document/src/tests/vespaxml/test14.xml b/document/src/tests/vespaxml/test14.xml
new file mode 100644
index 00000000000..7e0eaa270ae
--- /dev/null
+++ b/document/src/tests/vespaxml/test14.xml
@@ -0,0 +1,8 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <last_downloaded>123</last_downloaded>
+ </doc>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test15.xml b/document/src/tests/vespaxml/test15.xml
new file mode 100644
index 00000000000..f4b25cd0aea
--- /dev/null
+++ b/document/src/tests/vespaxml/test15.xml
@@ -0,0 +1,8 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <title>TestTitle</tit>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test16.xml b/document/src/tests/vespaxml/test16.xml
new file mode 100644
index 00000000000..727e1dc834b
--- /dev/null
+++ b/document/src/tests/vespaxml/test16.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <documentid type="news" id="doc:crawler:http://www.ntnu.no/">
+ </documentid>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test17.xml b/document/src/tests/vespaxml/test17.xml
new file mode 100644
index 00000000000..c0046253c79
--- /dev/null
+++ b/document/src/tests/vespaxml/test17.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/1">
+ </document>
+ </vespaadd>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/2">
+ </document>
+ </vespaadd>
diff --git a/document/src/tests/vespaxml/test18.xml b/document/src/tests/vespaxml/test18.xml
new file mode 100644
index 00000000000..c8798f8fc43
--- /dev/null
+++ b/document/src/tests/vespaxml/test18.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="article" id="doc:crawler/http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test2.expected.xml b/document/src/tests/vespaxml/test2.expected.xml
new file mode 100644
index 00000000000..a369cfff4a6
--- /dev/null
+++ b/document/src/tests/vespaxml/test2.expected.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<document documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+</document>
diff --git a/document/src/tests/vespaxml/test2.xml b/document/src/tests/vespaxml/test2.xml
new file mode 100644
index 00000000000..9dae17e830f
--- /dev/null
+++ b/document/src/tests/vespaxml/test2.xml
@@ -0,0 +1,7 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <document documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test20.xml b/document/src/tests/vespaxml/test20.xml
new file mode 100644
index 00000000000..867c4829c36
--- /dev/null
+++ b/document/src/tests/vespaxml/test20.xml
@@ -0,0 +1,8 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <endoffeed>
+ <name>default</name>
+ <generations>10</generations>
+ <increment>11</increment>
+ </endoffeed>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test21.xml b/document/src/tests/vespaxml/test21.xml
new file mode 100644
index 00000000000..daad994b84b
--- /dev/null
+++ b/document/src/tests/vespaxml/test21.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>21474836480</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test22.xml b/document/src/tests/vespaxml/test22.xml
new file mode 100644
index 00000000000..ec20024693d
--- /dev/null
+++ b/document/src/tests/vespaxml/test22.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <value_long>9223372036854775807</value_long>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test23.xml b/document/src/tests/vespaxml/test23.xml
new file mode 100644
index 00000000000..68afd6ec565
--- /dev/null
+++ b/document/src/tests/vespaxml/test23.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <value_long>-9223372036854775807</value_long>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test24.xml b/document/src/tests/vespaxml/test24.xml
new file mode 100644
index 00000000000..b386bb2c092
--- /dev/null
+++ b/document/src/tests/vespaxml/test24.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <value_long>18446744073709551615</value_long>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test25.xml b/document/src/tests/vespaxml/test25.xml
new file mode 100644
index 00000000000..72a19e8da60
--- /dev/null
+++ b/document/src/tests/vespaxml/test25.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <value_long>18446744073709551616</value_long>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test26.xml b/document/src/tests/vespaxml/test26.xml
new file mode 100644
index 00000000000..698e615c274
--- /dev/null
+++ b/document/src/tests/vespaxml/test26.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>0x123</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test27.xml b/document/src/tests/vespaxml/test27.xml
new file mode 100644
index 00000000000..94c5f3c1d32
--- /dev/null
+++ b/document/src/tests/vespaxml/test27.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler/http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>0x123</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test28.xml b/document/src/tests/vespaxml/test28.xml
new file mode 100644
index 00000000000..f1995d295bb
--- /dev/null
+++ b/document/src/tests/vespaxml/test28.xml
@@ -0,0 +1,10 @@
+<!-- Test content -->
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitleContent</title>
+ <value_content contenttype="text/html" encoding="utf-8" language="en"><![CDATA[<html><body><h1>This is the title</h1></body></html>]]></value_content>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test29.xml b/document/src/tests/vespaxml/test29.xml
new file mode 100644
index 00000000000..1f33b9f7555
--- /dev/null
+++ b/document/src/tests/vespaxml/test29.xml
@@ -0,0 +1,18 @@
+<!-- Test field attribute -->
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news">
+ <field name="uri">doc:crawler:http://www.ntnu.no/</field>
+ <field name="title">TestTitleContent</field>
+ <field name="value_content" contenttype="text/html" encoding="UTF-8" language="NO">This is content</field>
+ <last_downloaded>345</last_downloaded>
+ </document>
+ <document type="news">
+ <field name="uri">doc:crawler:http://www.ntnu.no/2</field>
+ <field name="title">TestTitleContent2</field>
+ <field name="value_content">This is content2</field>
+ <last_downloaded>345</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test3.xml b/document/src/tests/vespaxml/test3.xml
new file mode 100644
index 00000000000..81d87679211
--- /dev/null
+++ b/document/src/tests/vespaxml/test3.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove idprefix="doc:crawler:">
+ <documentid type="news">
+ <uri>http://www.ntnu.no/</uri>
+ <last_downloaded></last_downloaded>
+ </documentid>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test30.xml b/document/src/tests/vespaxml/test30.xml
new file mode 100644
index 00000000000..6fbb6fae8ba
--- /dev/null
+++ b/document/src/tests/vespaxml/test30.xml
@@ -0,0 +1,15 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed xmlns:xsd="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title xsi:type="xsd:hexBinary">E5AEB6E59BADE3808220044BE680AC</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ <document type="news" id="doc:crawler:http://www.ntnu.org/">
+ <title xsi:type="xsd:base64Binary">5a625Zut44CCIARL5oCs</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
+
diff --git a/document/src/tests/vespaxml/test32.xml b/document/src/tests/vespaxml/test32.xml
new file mode 100644
index 00000000000..b2f7976c124
--- /dev/null
+++ b/document/src/tests/vespaxml/test32.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <documentid id="doc:crawler:http://www.ntnu���.no/"/>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test33.xml b/document/src/tests/vespaxml/test33.xml
new file mode 100644
index 00000000000..264be7f3e02
--- /dev/null
+++ b/document/src/tests/vespaxml/test33.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <documentid>
+ <uri>http://www.ntnu.no/</uri>
+ </documentid>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test34.xml b/document/src/tests/vespaxml/test34.xml
new file mode 100644
index 00000000000..a3c80415899
--- /dev/null
+++ b/document/src/tests/vespaxml/test34.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <documentid id="http://www.ntnu.no/"/>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test35.xml b/document/src/tests/vespaxml/test35.xml
new file mode 100644
index 00000000000..e02a755cc99
--- /dev/null
+++ b/document/src/tests/vespaxml/test35.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title binaryencoding="base64">VmVzcGEgcnVsZXM=</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test36.xml b/document/src/tests/vespaxml/test36.xml
new file mode 100644
index 00000000000..3eed0c4cb2a
--- /dev/null
+++ b/document/src/tests/vespaxml/test36.xml
@@ -0,0 +1,43 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd idprefix="doc:crawler:">
+ <document type="news" id="http://www.ntnu.no/1">
+ <title>TestTitle1</title>
+ <last_downloaded>100</last_downloaded>
+ <stringarr>
+ <item>one</item>
+ <item>two</item>
+ <item>three</item>
+ <item>four</item>
+ <item>five</item>
+ </stringarr>
+ <intarr>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ </intarr>
+ </document>
+ <document type="news" id="http://www.ntnu.no/2">
+ <title>TestTitle2</title>
+ <intarr>
+ <item>o1</item>
+ </intarr>
+ </document>
+ <document type="news" id="http://www.ntnu.no/3">
+ <title>TestTitle3</title>
+ <intarr>
+ <tem>1</tem>
+ </intarr>
+ </document>
+ <document type="news" id="http://www.ntnu.no/4">
+ <title>TestTitle4</title>
+ <intarr></intarr>
+ </document>
+ <document type="news" id="http://www.ntnu.no/5">
+ <title>TestTitle5</title>
+ <intarr>1</intarr>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test37.xml b/document/src/tests/vespaxml/test37.xml
new file mode 100644
index 00000000000..1c2f6e01006
--- /dev/null
+++ b/document/src/tests/vespaxml/test37.xml
@@ -0,0 +1,23 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd idprefix="doc:crawler:">
+ <document type="news" id="http://www.ntnu.no/1">
+ <title>TestTitle1</title>
+ <last_downloaded>100</last_downloaded>
+ <weightedsetstring>
+ <item>one</item>
+ <item>two</item>
+ <item>three</item>
+ <item>four</item>
+ <item>five</item>
+ </weightedsetstring>
+ <weightedsetint>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ </weightedsetint>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test4.xml b/document/src/tests/vespaxml/test4.xml
new file mode 100644
index 00000000000..7c0c3682cd3
--- /dev/null
+++ b/document/src/tests/vespaxml/test4.xml
@@ -0,0 +1,23 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+
+ <vespaadd idprefix="doc:crawler:">
+ <document type="news" id="http://www.uio.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+
+ <document type="news" id="http://www.dagbladet.no/">
+ <title>Title2</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+
+ <vespaadd>
+ <document type="news" id="doc:crawler2:http://www.vg.no/">
+ <title>Title2</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test40.xml b/document/src/tests/vespaxml/test40.xml
new file mode 100644
index 00000000000..ecfb745415a
--- /dev/null
+++ b/document/src/tests/vespaxml/test40.xml
@@ -0,0 +1,6 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <assign field="title">TestTitle</assign>
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test41.xml b/document/src/tests/vespaxml/test41.xml
new file mode 100644
index 00000000000..052f4076025
--- /dev/null
+++ b/document/src/tests/vespaxml/test41.xml
@@ -0,0 +1,22 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <assign field="stringarr">
+ <item>First</item>
+ <item>Second</item>
+ </assign>
+ <add field="stringarr">
+ <item>Third</item>
+ <item>Fourth</item>
+ </add>
+ <remove field="stringarr">
+ <item>Fifth</item>
+ <item>Sixth</item>
+ </remove>
+ <assign field="intarr">
+ <item>100</item>
+ <item>200</item>
+ </assign>
+ <assign field="intarr"></assign>
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test42.xml b/document/src/tests/vespaxml/test42.xml
new file mode 100644
index 00000000000..da3e94410df
--- /dev/null
+++ b/document/src/tests/vespaxml/test42.xml
@@ -0,0 +1,22 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <assign field="weightedsetstring">
+ <item weight="1">First</item>
+ <item weight="2">Second</item>
+ </assign>
+ <add field="weightedsetstring">
+ <item weight="3">Third</item>
+ <item weight="4">Fourth</item>
+ </add>
+ <remove field="weightedsetstring">
+ <item>Fifth</item>
+ <item>Sixth</item>
+ </remove>
+ <assign field="weightedsetint">
+ <item weight="1">100</item>
+ <item weight="2">200</item>
+ </assign>
+ <assign field="weightedsetint"></assign>
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test43.xml b/document/src/tests/vespaxml/test43.xml
new file mode 100644
index 00000000000..16944dde9fa
--- /dev/null
+++ b/document/src/tests/vespaxml/test43.xml
@@ -0,0 +1,32 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <document documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <value_long>1008</value_long>
+ <weightedsetstring>
+ <item weight="1">First</item>
+ <item weight="2">Second</item>
+ </weightedsetstring>
+ </document>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <increment field="value_long" by="1" />
+ <decrement field="value_long" by="2" />
+ <divide field="value_long" by="3" />
+ <multiply field="value_long" by="4" />
+ </update>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <alter field="value_long">
+ <increment by="5" />
+ <decrement by="6" />
+ </alter>
+ <alter field="value_long">
+ <divide by="7" />
+ <multiply by="8" />
+ </alter>
+ </update>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <increment field="weightedsetstring" by="9">
+ <key>First</key>
+ <key>Second</key>
+ </increment>
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test45.xml b/document/src/tests/vespaxml/test45.xml
new file mode 100644
index 00000000000..eb13e59a0da
--- /dev/null
+++ b/document/src/tests/vespaxml/test45.xml
@@ -0,0 +1,6 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <update documenttype="news" documentid="doc:crawler:http://www.ntnu.no/">
+ <remove field="floatarr" />
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test46.xml b/document/src/tests/vespaxml/test46.xml
new file mode 100644
index 00000000000..75d69f3a8b5
--- /dev/null
+++ b/document/src/tests/vespaxml/test46.xml
@@ -0,0 +1,10 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <document documenttype="news" documentid="doc:crawler:http://www.blargh.example.com/" lastmodified="98798787">
+ <value_long>1008</value_long>
+ <weightedsetstring>
+ <item weight="1">First</item>
+ <item weight="2">Second</item>
+ </weightedsetstring>
+ </document>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test47.xml b/document/src/tests/vespaxml/test47.xml
new file mode 100644
index 00000000000..5944158c7c6
--- /dev/null
+++ b/document/src/tests/vespaxml/test47.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- VERY BAD: -->
+ <vespaadd>
+ <documentid type="news">
+ <uri>doc:this:is:very:bad</uri>
+ </documentid>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test48.xml b/document/src/tests/vespaxml/test48.xml
new file mode 100644
index 00000000000..a9bdd94d098
--- /dev/null
+++ b/document/src/tests/vespaxml/test48.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- VERY BAD: -->
+ <documentid type="news" id="doc:this:is:even:worse" />
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test49.xml b/document/src/tests/vespaxml/test49.xml
new file mode 100644
index 00000000000..dc8a67c64bb
--- /dev/null
+++ b/document/src/tests/vespaxml/test49.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- VERY BAD: -->
+ <documentid type="news">
+ <uri>doc:this:is:very:very:ugly</uri>
+ </documentid>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test5.expected.xml b/document/src/tests/vespaxml/test5.expected.xml
new file mode 100644
index 00000000000..164799749c8
--- /dev/null
+++ b/document/src/tests/vespaxml/test5.expected.xml
@@ -0,0 +1,3 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<remove documentid="doc:crawler:http://www.ntnu.no/"/>
+
diff --git a/document/src/tests/vespaxml/test5.xml b/document/src/tests/vespaxml/test5.xml
new file mode 100644
index 00000000000..a6a6f8f0f9f
--- /dev/null
+++ b/document/src/tests/vespaxml/test5.xml
@@ -0,0 +1,6 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <documentid documentid="doc:crawler:http://www.ntnu.no/"/>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test50.xml b/document/src/tests/vespaxml/test50.xml
new file mode 100644
index 00000000000..0909bcf2795
--- /dev/null
+++ b/document/src/tests/vespaxml/test50.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- OK: -->
+ <vesparemove>
+ <documentid type="news" id="doc:this:an:ok:removal"/>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test51.xml b/document/src/tests/vespaxml/test51.xml
new file mode 100644
index 00000000000..698a305d33c
--- /dev/null
+++ b/document/src/tests/vespaxml/test51.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- OK: -->
+ <remove documentid="doc:this:is:also:an:ok:removal"/>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test52.xml b/document/src/tests/vespaxml/test52.xml
new file mode 100644
index 00000000000..d569f5ada1b
--- /dev/null
+++ b/document/src/tests/vespaxml/test52.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- OK: -->
+ <vespaadd>
+ <document documentid="doc:blah:blah:blah" documenttype="news">
+ <value_long>2345</value_long>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test53.xml b/document/src/tests/vespaxml/test53.xml
new file mode 100644
index 00000000000..28c2161c956
--- /dev/null
+++ b/document/src/tests/vespaxml/test53.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- HALF BAD, BUT NOT ALLOWED: -->
+ <vespaadd>
+ <document documentid="doc:blah:blah:blah" documenttype="news">
+ <value_long>2345</value_long>
+ </document>
+ <documentid type="news" id="doc:half:bad:add"/>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test54.xml b/document/src/tests/vespaxml/test54.xml
new file mode 100644
index 00000000000..90f41b6d32d
--- /dev/null
+++ b/document/src/tests/vespaxml/test54.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- VERY BAD: -->
+ <vesparemove>
+ <document documentid="doc:bluh:bluh:bluh" documenttype="news">
+ <value_long>45</value_long>
+ </document>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test55.xml b/document/src/tests/vespaxml/test55.xml
new file mode 100644
index 00000000000..8fcd4a31d1f
--- /dev/null
+++ b/document/src/tests/vespaxml/test55.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- HALF BAD, BUT NOT ALLOWED: -->
+ <vesparemove>
+ <documentid type="news" id="doc:this:remove:is:half:bad"/>
+ <document documentid="doc:bluh:bluh:bluh" documenttype="news">
+ <value_long>45</value_long>
+ </document>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test56.xml b/document/src/tests/vespaxml/test56.xml
new file mode 100644
index 00000000000..489ff494fc0
--- /dev/null
+++ b/document/src/tests/vespaxml/test56.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- VERY BAD: -->
+ <vesparemove>
+ <document documentid="doc:bluh:bluh:bluh" documenttype="news" />
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test57.xml b/document/src/tests/vespaxml/test57.xml
new file mode 100644
index 00000000000..6653b0260af
--- /dev/null
+++ b/document/src/tests/vespaxml/test57.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <!-- OK: -->
+ <document documentid="doc:blih:blih:blih" documenttype="news">
+ <value_long>235</value_long>
+ </document>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test58.xml b/document/src/tests/vespaxml/test58.xml
new file mode 100644
index 00000000000..2577f3a0e26
--- /dev/null
+++ b/document/src/tests/vespaxml/test58.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+
+ <document type="news" id="doc:music:http://music.yahoo.com">
+ <url>http://music.yahoo.com</url>
+ <stringarr>
+ <item>yahoo<item>
+ <item>hello</item>
+ </stringarr>
+ </document>
+
+
+
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test59.xml b/document/src/tests/vespaxml/test59.xml
new file mode 100644
index 00000000000..970f5232e7b
--- /dev/null
+++ b/document/src/tests/vespaxml/test59.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <update documentid="doc:blih:blih:blih" documenttype="news">
+ <increment field="title" by="3549" />
+ </update>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test6.xml b/document/src/tests/vespaxml/test6.xml
new file mode 100644
index 00000000000..c67352bbb22
--- /dev/null
+++ b/document/src/tests/vespaxml/test6.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <documentid type="news" id="doc:crawler:http://www.ntnu���.no/"/>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test7.xml b/document/src/tests/vespaxml/test7.xml
new file mode 100644
index 00000000000..2c346a5b5cf
--- /dev/null
+++ b/document/src/tests/vespaxml/test7.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed idprefix="doc:crawler:">
+ <vespaadd>
+ <document type="news" id="http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100.5</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test8.xml b/document/src/tests/vespaxml/test8.xml
new file mode 100644
index 00000000000..cb9617fefd8
--- /dev/null
+++ b/document/src/tests/vespaxml/test8.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" id="doc:crawler:http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>hundred</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test9.xml b/document/src/tests/vespaxml/test9.xml
new file mode 100644
index 00000000000..8b86da414b8
--- /dev/null
+++ b/document/src/tests/vespaxml/test9.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vesparemove>
+ <documentid id="doc:crawler/http://www.ntnu���.no/"/>
+ </vesparemove>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_arraystruct.xml b/document/src/tests/vespaxml/test_arraystruct.xml
new file mode 100644
index 00000000000..5eeae4aab2d
--- /dev/null
+++ b/document/src/tests/vespaxml/test_arraystruct.xml
@@ -0,0 +1,19 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <document documenttype="news" documentid="doc:test:struct">
+ <mystructarr>
+ <item>
+ <intval>36</intval>
+ <stringval>test</stringval>
+ </item>
+ <item>
+ <intval>39</intval>
+ <stringval>test2</stringval>
+ </item>
+ <item>
+ <intval>100</intval>
+ <stringval>cooool</stringval>
+ </item>
+ </mystructarr>
+ </document>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_doc5.xml b/document/src/tests/vespaxml/test_doc5.xml
new file mode 100644
index 00000000000..fa6d9c4726e
--- /dev/null
+++ b/document/src/tests/vespaxml/test_doc5.xml
@@ -0,0 +1,8 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <endoffeed>
+ <name>default</name>
+ <generation>10</generation>
+ <increment>20</increment>
+ </endoffeed>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_doc6.xml b/document/src/tests/vespaxml/test_doc6.xml
new file mode 100644
index 00000000000..2f32701d88e
--- /dev/null
+++ b/document/src/tests/vespaxml/test_doc6.xml
@@ -0,0 +1,6 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <endoffeed>
+ <name>default</name>
+ </endoffeed>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_doc8.xml b/document/src/tests/vespaxml/test_doc8.xml
new file mode 100644
index 00000000000..d636e7baa96
--- /dev/null
+++ b/document/src/tests/vespaxml/test_doc8.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd>
+ <document type="news" version="13" id="http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_externalentity.xml b/document/src/tests/vespaxml/test_externalentity.xml
new file mode 100644
index 00000000000..1d87319b106
--- /dev/null
+++ b/document/src/tests/vespaxml/test_externalentity.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<!DOCTYPE vespafeed [<!ENTITY xxe SYSTEM "xxe.txt">]>
+<vespafeed>
+ <vespaadd idprefix="doc:crawler:">
+ <document documenttype="news" documentid="http://www.ntnu.no/">
+ <title>&xxe;</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_idprefix.xml b/document/src/tests/vespaxml/test_idprefix.xml
new file mode 100644
index 00000000000..e3a177d6606
--- /dev/null
+++ b/document/src/tests/vespaxml/test_idprefix.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+
+ <document type="news" id="http://music.yahoo.com/bobdylan/BestOf">
+ <title>Best of Bob Dylan</title>
+ </document>
+
+ <document type="news" id="http://music.yahoo.com/metallica/BestOf">
+ <title>Best of Metallica</title>
+ </document>
+
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_struct.xml b/document/src/tests/vespaxml/test_struct.xml
new file mode 100644
index 00000000000..c18c9263b4a
--- /dev/null
+++ b/document/src/tests/vespaxml/test_struct.xml
@@ -0,0 +1,9 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <document documenttype="news" documentid="doc:test:struct">
+ <mystruct>
+ <intval>36</intval>
+ <stringval>test</stringval>
+ </mystruct>
+ </document>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/test_update1.xml b/document/src/tests/vespaxml/test_update1.xml
new file mode 100644
index 00000000000..27c7d6214d3
--- /dev/null
+++ b/document/src/tests/vespaxml/test_update1.xml
@@ -0,0 +1,12 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<vespafeed>
+ <vespaadd idprefix="doc:crawler:">
+ <document type="news" id="http://www.ntnu.no/">
+ <title>TestTitle</title>
+ <last_downloaded>100</last_downloaded>
+ </document>
+ <update documenttype="news" documentid="http://www.ntnu.no/">
+ <divide field="last_downloaded" by="0" />
+ </update>
+ </vespaadd>
+</vespafeed>
diff --git a/document/src/tests/vespaxml/vespaxmldoctype.cfg b/document/src/tests/vespaxml/vespaxmldoctype.cfg
new file mode 100644
index 00000000000..3a4aa280e0d
--- /dev/null
+++ b/document/src/tests/vespaxml/vespaxmldoctype.cfg
@@ -0,0 +1,133 @@
+enablecompression false
+datatype[12]
+datatype[0].id 1002
+datatype[0].arraytype[1]
+datatype[0].arraytype[0].datatype 2
+datatype[0].weightedsettype[0]
+datatype[0].structtype[0]
+datatype[0].documenttype[0]
+datatype[1].id 1000
+datatype[1].arraytype[1]
+datatype[1].arraytype[0].datatype 0
+datatype[1].weightedsettype[0]
+datatype[1].structtype[0]
+datatype[1].documenttype[0]
+datatype[2].id 1004
+datatype[2].arraytype[1]
+datatype[2].arraytype[0].datatype 4
+datatype[2].weightedsettype[0]
+datatype[2].structtype[0]
+datatype[2].documenttype[0]
+datatype[3].id 1016
+datatype[3].arraytype[1]
+datatype[3].arraytype[0].datatype 16
+datatype[3].weightedsettype[0]
+datatype[3].structtype[0]
+datatype[3].documenttype[0]
+datatype[4].id 1001
+datatype[4].arraytype[1]
+datatype[4].arraytype[0].datatype 1
+datatype[4].weightedsettype[0]
+datatype[4].structtype[0]
+datatype[4].documenttype[0]
+datatype[5].id 2001
+datatype[5].arraytype[0]
+datatype[5].weightedsettype[1]
+datatype[5].weightedsettype[0].datatype 0
+datatype[5].weightedsettype[0].createifnonexistant false
+datatype[5].weightedsettype[0].removeifzero false
+datatype[5].structtype[0]
+datatype[5].documenttype[0]
+datatype[6].id 2002
+datatype[6].arraytype[0]
+datatype[6].weightedsettype[1]
+datatype[6].weightedsettype[0].datatype 2
+datatype[6].weightedsettype[0].createifnonexistant false
+datatype[6].weightedsettype[0].removeifzero false
+datatype[6].structtype[0]
+datatype[6].documenttype[0]
+datatype[7].id 3000
+datatype[7].arraytype[0]
+datatype[7].weightedsettype[0]
+datatype[7].structtype[1]
+datatype[7].structtype[0].name news.mystruct
+datatype[7].structtype[0].version 0
+datatype[7].structtype[0].field[2]
+datatype[7].structtype[0].field[0].name intval
+datatype[7].structtype[0].field[0].id[0]
+datatype[7].structtype[0].field[0].datatype 0
+datatype[7].structtype[0].field[1].name stringval
+datatype[7].structtype[0].field[1].id[0]
+datatype[7].structtype[0].field[1].datatype 2
+datatype[7].documenttype[0]
+datatype[8].id 103000
+datatype[8].arraytype[1]
+datatype[8].arraytype[0].datatype 3000
+datatype[8].weightedsettype[0]
+datatype[8].structtype[0]
+datatype[8].documenttype[0]
+datatype[9].id 5000
+datatype[9].arraytype[0]
+datatype[9].weightedsettype[0]
+datatype[9].structtype[1]
+datatype[9].structtype[0].name news.header
+datatype[9].structtype[0].version 0
+datatype[9].structtype[0].field[13]
+datatype[9].structtype[0].field[0].name url
+datatype[9].structtype[0].field[0].id[0]
+datatype[9].structtype[0].field[0].datatype 10
+datatype[9].structtype[0].field[1].name title
+datatype[9].structtype[0].field[1].id[0]
+datatype[9].structtype[0].field[1].datatype 2
+datatype[9].structtype[0].field[2].name last_downloaded
+datatype[9].structtype[0].field[2].id[0]
+datatype[9].structtype[0].field[2].datatype 0
+datatype[9].structtype[0].field[3].name value_long
+datatype[9].structtype[0].field[3].id[0]
+datatype[9].structtype[0].field[3].datatype 4
+datatype[9].structtype[0].field[4].name stringarr
+datatype[9].structtype[0].field[4].id[0]
+datatype[9].structtype[0].field[4].datatype 1002
+datatype[9].structtype[0].field[5].name intarr
+datatype[9].structtype[0].field[5].id[0]
+datatype[9].structtype[0].field[5].datatype 1000
+datatype[9].structtype[0].field[6].name longarr
+datatype[9].structtype[0].field[6].id[0]
+datatype[9].structtype[0].field[6].datatype 1004
+datatype[9].structtype[0].field[7].name bytearr
+datatype[9].structtype[0].field[7].id[0]
+datatype[9].structtype[0].field[7].datatype 1016
+datatype[9].structtype[0].field[8].name floatarr
+datatype[9].structtype[0].field[8].id[0]
+datatype[9].structtype[0].field[8].datatype 1001
+datatype[9].structtype[0].field[9].name weightedsetint
+datatype[9].structtype[0].field[9].id[0]
+datatype[9].structtype[0].field[9].datatype 2001
+datatype[9].structtype[0].field[10].name weightedsetstring
+datatype[9].structtype[0].field[10].id[0]
+datatype[9].structtype[0].field[10].datatype 2002
+datatype[9].structtype[0].field[11].name mystruct
+datatype[9].structtype[0].field[11].id[0]
+datatype[9].structtype[0].field[11].datatype 3000
+datatype[9].structtype[0].field[12].name mystructarr
+datatype[9].structtype[0].field[12].id[0]
+datatype[9].structtype[0].field[12].datatype 103000
+datatype[9].documenttype[0]
+datatype[10].id 5001
+datatype[10].arraytype[0]
+datatype[10].weightedsettype[0]
+datatype[10].structtype[1]
+datatype[10].structtype[0].name news.body
+datatype[10].structtype[0].version 0
+datatype[10].structtype[0].field[0]
+datatype[10].documenttype[0]
+datatype[11].id 5002
+datatype[11].arraytype[0]
+datatype[11].weightedsettype[0]
+datatype[11].structtype[0]
+datatype[11].documenttype[1]
+datatype[11].documenttype[0].name news
+datatype[11].documenttype[0].version 0
+datatype[11].documenttype[0].inherits[0]
+datatype[11].documenttype[0].headerstruct 5000
+datatype[11].documenttype[0].bodystruct 5001
diff --git a/document/src/tests/vespaxml/xxe.txt b/document/src/tests/vespaxml/xxe.txt
new file mode 100644
index 00000000000..c8c1b8233cc
--- /dev/null
+++ b/document/src/tests/vespaxml/xxe.txt
@@ -0,0 +1 @@
+scary xxe exploit \ No newline at end of file
diff --git a/document/src/tests/weightedsetfieldvaluetest.cpp b/document/src/tests/weightedsetfieldvaluetest.cpp
new file mode 100644
index 00000000000..bc1bdca6028
--- /dev/null
+++ b/document/src/tests/weightedsetfieldvaluetest.cpp
@@ -0,0 +1,324 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/document/serialization/vespadocumentdeserializer.h>
+#include <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/vespalib/objects/nbostream.h>
+
+using vespalib::nbostream;
+
+namespace document {
+
+struct WeightedSetFieldValueTest : public CppUnit::TestFixture {
+ void setUp() {}
+ void tearDown() {}
+
+ void testWeightedSet();
+ void testAddIgnoreZeroWeight();
+
+ CPPUNIT_TEST_SUITE(WeightedSetFieldValueTest);
+ CPPUNIT_TEST(testWeightedSet);
+ CPPUNIT_TEST(testAddIgnoreZeroWeight);
+ CPPUNIT_TEST_SUITE_END();
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WeightedSetFieldValueTest);
+
+namespace {
+template <typename T>
+void deserialize(const ByteBuffer &buffer, T &value) {
+ uint16_t version = Document::getNewestSerializationVersion();
+ nbostream stream(buffer.getBufferAtPos(), buffer.getRemaining());
+ DocumentTypeRepo repo;
+ VespaDocumentDeserializer deserializer(repo, stream, version);
+ deserializer.read(value);
+}
+
+template<typename Type1, typename Type2>
+void verifyFailedAssignment(Type1& lval, const Type2& rval)
+{
+ try{
+ lval = rval;
+ CPPUNIT_FAIL("Failed to check type equality in operator=");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot assign value of type", e.what());
+ }
+ try{
+ lval.assign(rval);
+ CPPUNIT_FAIL("Failed to check type equality in assign()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot assign value of type", e.what());
+ }
+}
+
+template<typename Type>
+void verifyFailedUpdate(Type& lval, const FieldValue& rval)
+{
+ try{
+ lval.add(rval);
+ CPPUNIT_FAIL("Failed to check type equality in add()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("These types are not compatible", e.what());
+ }
+ try{
+ lval.contains(rval);
+ CPPUNIT_FAIL("Failed to check type equality in contains()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("These types are not compatible", e.what());
+ }
+ try{
+ lval.remove(rval);
+ CPPUNIT_FAIL("Failed to check type equality in remove()");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("These types are not compatible", e.what());
+ }
+}
+} // namespace
+
+void WeightedSetFieldValueTest::testWeightedSet()
+{
+ WeightedSetDataType type(*DataType::INT, false, false);
+ WeightedSetFieldValue value(type);
+
+ // Initially empty
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value.size());
+ CPPUNIT_ASSERT(value.isEmpty());
+ CPPUNIT_ASSERT(!value.contains(IntFieldValue(1)));
+
+ CPPUNIT_ASSERT(value.add(IntFieldValue(1)));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(1), value.size());
+ CPPUNIT_ASSERT(!value.isEmpty());
+ CPPUNIT_ASSERT(value.contains(IntFieldValue(1)));
+
+ // Adding some more
+ CPPUNIT_ASSERT(value.add(IntFieldValue(2), 5));
+ CPPUNIT_ASSERT(value.add(IntFieldValue(3), 6));
+
+ // Not empty
+ CPPUNIT_ASSERT_EQUAL(size_t(3), value.size());
+ CPPUNIT_ASSERT(!value.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(1, value.get(IntFieldValue(1)));
+ CPPUNIT_ASSERT_EQUAL(5, value.get(IntFieldValue(2)));
+ CPPUNIT_ASSERT_EQUAL(6, value.get(IntFieldValue(3)));
+
+ // Serialize & equality
+ std::unique_ptr<ByteBuffer> buffer(value.serialize());
+ buffer->flip();
+ WeightedSetFieldValue value2(type);
+ CPPUNIT_ASSERT(value != value2);
+ deserialize(*buffer, value2);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+
+ // Various ways of removing
+ {
+ // By value
+ buffer->setPos(0);
+ deserialize(*buffer, value2);
+ CPPUNIT_ASSERT(value2.remove(IntFieldValue(1)));
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(1)));
+ CPPUNIT_ASSERT_EQUAL(size_t(2), value2.size());
+
+ // Clearing all
+ buffer->setPos(0);
+ deserialize(*buffer, value2);
+ value2.clear();
+ CPPUNIT_ASSERT(!value2.contains(IntFieldValue(1)));
+ CPPUNIT_ASSERT_EQUAL(size_t(0), value2.size());
+ CPPUNIT_ASSERT(value2.isEmpty());
+ }
+
+ // Updating
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ CPPUNIT_ASSERT(!value2.add(IntFieldValue(2), 10)); // false = overwritten
+ CPPUNIT_ASSERT(value2.add(IntFieldValue(17), 9)); // true = added new
+ CPPUNIT_ASSERT_EQUAL(10, value2.get(IntFieldValue(2)));
+ CPPUNIT_ASSERT(value != value2);
+ value2.assign(value);
+ CPPUNIT_ASSERT_EQUAL(value, value2);
+ WeightedSetFieldValue::UP valuePtr(value2.clone());
+ CPPUNIT_ASSERT_EQUAL(value, *valuePtr);
+
+ // Iterating
+ const WeightedSetFieldValue& constVal(value);
+ for(WeightedSetFieldValue::const_iterator it = constVal.begin();
+ it != constVal.end(); ++it)
+ {
+ const FieldValue& fval1(*it->first);
+ (void) fval1;
+ CPPUNIT_ASSERT_EQUAL((uint32_t) IntFieldValue::classId,
+ it->first->getClass().id());
+ const IntFieldValue& val = dynamic_cast<const IntFieldValue&>(*it->second);
+ (void) val;
+ }
+ value2 = value;
+ for(WeightedSetFieldValue::iterator it = value2.begin();
+ it != value2.end(); ++it)
+ {
+ IntFieldValue& val = dynamic_cast<IntFieldValue&>(*it->second);
+ val.setValue(7);
+ }
+ CPPUNIT_ASSERT(value != value2);
+ CPPUNIT_ASSERT_EQUAL(7, value2.get(IntFieldValue(2)));
+
+ // Comparison
+ value2 = value;
+ CPPUNIT_ASSERT_EQUAL(0, value.compare(value2));
+ value2.remove(IntFieldValue(1));
+ CPPUNIT_ASSERT(value.compare(value2) > 0);
+ CPPUNIT_ASSERT(value2.compare(value) < 0);
+ value2 = value;
+ value2.add(IntFieldValue(7));
+ CPPUNIT_ASSERT(value.compare(value2) < 0);
+ CPPUNIT_ASSERT(value2.compare(value) > 0);
+
+ // Output
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(
+ "WeightedSet<Int>(\n"
+ " 1 - weight 1,\n"
+ " 2 - weight 5,\n"
+ " 3 - weight 6\n"
+ ")"),
+ value.toString(false));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(
+ " WeightedSet<Int>(\n"
+ ".. 1 - weight 1,\n"
+ ".. 2 - weight 5,\n"
+ ".. 3 - weight 6\n"
+ "..)"),
+ " " + value.toString(true, ".."));
+ CPPUNIT_ASSERT_EQUAL(
+ std::string(
+ "<value>\n"
+ " <item weight=\"1\">1</item>\n"
+ " <item weight=\"5\">2</item>\n"
+ " <item weight=\"6\">3</item>\n"
+ "</value>"),
+ value.toXml(" "));
+
+ // Failure situations.
+
+ // Refuse to accept non-weightedset types
+ try{
+ ArrayDataType arrayType(*DataType::STRING);
+ WeightedSetFieldValue value6(arrayType);
+ CPPUNIT_FAIL("Didn't complain about non-weightedset type");
+ } catch (std::exception& e) {
+ CPPUNIT_ASSERT_CONTAIN("Cannot generate a weighted set value with "
+ "non-weighted set type", e.what());
+ }
+
+ // Verify that datatypes are verified
+ // Created almost equal types to try to get it to fail
+ WeightedSetDataType type1(*DataType::INT, false, false);
+ WeightedSetDataType type2(*DataType::LONG, false, false);
+ WeightedSetDataType type3(type1, false, false);
+ WeightedSetDataType type4(type2, false, false);
+ WeightedSetDataType type5(type2, false, true);
+ WeightedSetDataType type6(type2, true, false);
+
+ // Type differs in nested of nested type (verify recursivity)
+ {
+ WeightedSetFieldValue value3(type3);
+ WeightedSetFieldValue value4(type4);
+ verifyFailedAssignment(value3, value4);
+ }
+ // Type arguments differ
+ {
+ WeightedSetFieldValue value4(type4);
+ WeightedSetFieldValue value5(type5);
+ WeightedSetFieldValue value6(type6);
+ verifyFailedAssignment(value4, value5);
+ verifyFailedAssignment(value4, value6);
+ verifyFailedAssignment(value5, value4);
+ verifyFailedAssignment(value5, value6);
+ verifyFailedAssignment(value6, value4);
+ verifyFailedAssignment(value6, value5);
+ }
+ // Updates are checked too
+ {
+ WeightedSetFieldValue value3(type3);
+ WeightedSetFieldValue subValue(type2);
+ subValue.add(LongFieldValue(4));
+ verifyFailedUpdate(value3, subValue);
+ }
+
+ // Compare see difference even of close types.
+ {
+ WeightedSetFieldValue subValue2(type2);
+ subValue2.add(LongFieldValue(3));
+ WeightedSetFieldValue value3(type3);
+ WeightedSetFieldValue value4(type4);
+ value4.add(subValue2);
+ CPPUNIT_ASSERT(value3.compare(value4) != 0);
+ }
+
+ // Test createIfNonExisting and removeIfZero
+ {
+ WeightedSetDataType mytype1(*DataType::STRING, false, false);
+ WeightedSetDataType mytype2(*DataType::STRING, true, true);
+ CPPUNIT_ASSERT_EQUAL(*DataType::TAG, static_cast<DataType &>(mytype2));
+
+ WeightedSetFieldValue val1(mytype1);
+ val1.add("foo", 4);
+ try{
+ val1.increment("bar", 2);
+ CPPUNIT_FAIL("Expected exception incrementing with "
+ "createIfNonExistent set false");
+ } catch (std::exception& e) {}
+ try{
+ val1.decrement("bar", 2);
+ CPPUNIT_FAIL("Expected exception incrementing with "
+ "createIfNonExistent set false");
+ } catch (std::exception& e) {}
+ val1.increment("foo", 6);
+ CPPUNIT_ASSERT_EQUAL(10, val1.get("foo"));
+ val1.decrement("foo", 3);
+ CPPUNIT_ASSERT_EQUAL(7, val1.get("foo"));
+ val1.decrement("foo", 7);
+ CPPUNIT_ASSERT(val1.contains("foo"));
+
+ WeightedSetFieldValue val2(mytype2);
+ val2.add("foo", 4);
+ val2.increment("bar", 2);
+ CPPUNIT_ASSERT_EQUAL(2, val2.get("bar"));
+ val2.decrement("bar", 4);
+ CPPUNIT_ASSERT_EQUAL(-2, val2.get("bar"));
+ val2.increment("bar", 2);
+ CPPUNIT_ASSERT(!val2.contains("bar"));
+
+ val2.decrement("foo", 4);
+ CPPUNIT_ASSERT(!val2.contains("foo"));
+
+ val2.decrement("foo", 4);
+ CPPUNIT_ASSERT_EQUAL(-4, val2.get("foo"));
+
+ val2.add("foo", 0);
+ CPPUNIT_ASSERT(!val2.contains("foo"));
+ }
+}
+
+void
+WeightedSetFieldValueTest::testAddIgnoreZeroWeight()
+{
+ // Data type with auto-create and remove-if-zero set.
+ WeightedSetDataType wsetType(*DataType::STRING, true, true);
+ WeightedSetFieldValue ws(wsetType);
+
+ ws.addIgnoreZeroWeight(StringFieldValue("yarn"), 0);
+ CPPUNIT_ASSERT(ws.contains("yarn"));
+ CPPUNIT_ASSERT_EQUAL(0, ws.get("yarn"));
+
+ ws.addIgnoreZeroWeight(StringFieldValue("flarn"), 1);
+ CPPUNIT_ASSERT(ws.contains("flarn"));
+ CPPUNIT_ASSERT_EQUAL(1, ws.get("flarn"));
+}
+
+} // document
+
diff --git a/document/src/tests/weightedsetfieldvaluetest.h b/document/src/tests/weightedsetfieldvaluetest.h
new file mode 100644
index 00000000000..8cf52d9ea0e
--- /dev/null
+++ b/document/src/tests/weightedsetfieldvaluetest.h
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* $Id$*/
+
+#pragma once
+
+