aboutsummaryrefslogtreecommitdiffstats
path: root/searchlib/src/tests/features
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 /searchlib/src/tests/features
Publish
Diffstat (limited to 'searchlib/src/tests/features')
-rw-r--r--searchlib/src/tests/features/.gitignore11
-rw-r--r--searchlib/src/tests/features/CMakeLists.txt19
-rw-r--r--searchlib/src/tests/features/DESC1
-rw-r--r--searchlib/src/tests/features/FILES3
-rw-r--r--searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-double.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-float.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-int.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-long.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-wset.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-1.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-10.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-100.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-1000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-10000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-5.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-50.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100-500.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-100.txt6
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1-callgrind.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100-callgrind.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-5.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-50.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000-500.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-1000.txt6
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-100.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-5.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-50.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000-500.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-10000.txt6
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-1.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-10.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-100.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-1000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-10000.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-5.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-50.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20-500.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/c-20.txt6
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/phrase-02.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/phrase-10.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/phrase-50.txt7
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/plot.rb30
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/readme.txt22
-rw-r--r--searchlib/src/tests/features/benchmark/fieldmatch/run.rb17
-rw-r--r--searchlib/src/tests/features/benchmark/plotlib.rb36
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-1.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-10.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-100.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-200.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-400.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-5.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-50.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/c-800.txt4
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/plot.rb22
-rw-r--r--searchlib/src/tests/features/benchmark/rankingexpression/run.rb14
-rw-r--r--searchlib/src/tests/features/beta/.gitignore1
-rw-r--r--searchlib/src/tests/features/beta/CMakeLists.txt12
-rw-r--r--searchlib/src/tests/features/beta/beta_features.cpp726
-rw-r--r--searchlib/src/tests/features/element_completeness/.gitignore1
-rw-r--r--searchlib/src/tests/features/element_completeness/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/element_completeness/FILES1
-rw-r--r--searchlib/src/tests/features/element_completeness/element_completeness_test.cpp201
-rw-r--r--searchlib/src/tests/features/element_similarity_feature/.gitignore1
-rw-r--r--searchlib/src/tests/features/element_similarity_feature/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/element_similarity_feature/element_similarity_feature_test.cpp371
-rw-r--r--searchlib/src/tests/features/euclidean_distance/.gitignore1
-rw-r--r--searchlib/src/tests/features/euclidean_distance/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/euclidean_distance/FILES1
-rw-r--r--searchlib/src/tests/features/euclidean_distance/euclidean_distance_test.cpp115
-rw-r--r--searchlib/src/tests/features/featurebenchmark.cpp657
-rw-r--r--searchlib/src/tests/features/item_raw_score/.gitignore1
-rw-r--r--searchlib/src/tests/features/item_raw_score/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/item_raw_score/FILES1
-rw-r--r--searchlib/src/tests/features/item_raw_score/item_raw_score_test.cpp158
-rw-r--r--searchlib/src/tests/features/native_dot_product/.gitignore1
-rw-r--r--searchlib/src/tests/features/native_dot_product/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/native_dot_product/FILES1
-rw-r--r--searchlib/src/tests/features/native_dot_product/native_dot_product_test.cpp191
-rw-r--r--searchlib/src/tests/features/prod_features.cpp1937
-rw-r--r--searchlib/src/tests/features/prod_features.h175
-rw-r--r--searchlib/src/tests/features/prod_features_attributematch.cpp300
-rw-r--r--searchlib/src/tests/features/prod_features_fieldmatch.cpp1079
-rw-r--r--searchlib/src/tests/features/prod_features_fieldtermmatch.cpp113
-rw-r--r--searchlib/src/tests/features/prod_features_framework.cpp174
-rwxr-xr-xsearchlib/src/tests/features/prod_features_test.sh3
-rw-r--r--searchlib/src/tests/features/ranking_expression/.gitignore1
-rw-r--r--searchlib/src/tests/features/ranking_expression/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp90
-rw-r--r--searchlib/src/tests/features/raw_score/.gitignore1
-rw-r--r--searchlib/src/tests/features/raw_score/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/raw_score/FILES1
-rw-r--r--searchlib/src/tests/features/raw_score/raw_score_test.cpp151
-rw-r--r--searchlib/src/tests/features/subqueries/.gitignore1
-rw-r--r--searchlib/src/tests/features/subqueries/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/subqueries/subqueries_test.cpp162
-rw-r--r--searchlib/src/tests/features/tensor/.gitignore1
-rw-r--r--searchlib/src/tests/features/tensor/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/tensor/FILES1
-rw-r--r--searchlib/src/tests/features/tensor/tensor_test.cpp237
-rw-r--r--searchlib/src/tests/features/tensor_from_labels/.gitignore1
-rw-r--r--searchlib/src/tests/features/tensor_from_labels/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/tensor_from_labels/FILES1
-rw-r--r--searchlib/src/tests/features/tensor_from_labels/tensor_from_labels_test.cpp211
-rw-r--r--searchlib/src/tests/features/tensor_from_weighted_set/.gitignore1
-rw-r--r--searchlib/src/tests/features/tensor_from_weighted_set/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/tensor_from_weighted_set/FILES1
-rw-r--r--searchlib/src/tests/features/tensor_from_weighted_set/tensor_from_weighted_set_test.cpp198
-rw-r--r--searchlib/src/tests/features/text_similarity_feature/.gitignore1
-rw-r--r--searchlib/src/tests/features/text_similarity_feature/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/text_similarity_feature/FILES1
-rw-r--r--searchlib/src/tests/features/text_similarity_feature/text_similarity_feature_test.cpp245
-rw-r--r--searchlib/src/tests/features/util/.gitignore1
-rw-r--r--searchlib/src/tests/features/util/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/features/util/FILES1
-rw-r--r--searchlib/src/tests/features/util/util_test.cpp40
124 files changed, 8199 insertions, 0 deletions
diff --git a/searchlib/src/tests/features/.gitignore b/searchlib/src/tests/features/.gitignore
new file mode 100644
index 00000000000..1c71377a25e
--- /dev/null
+++ b/searchlib/src/tests/features/.gitignore
@@ -0,0 +1,11 @@
+.depend
+Makefile
+beta_features_test
+featurebenchmark
+nativerank_test
+prod_features_test
+vlog1.txt
+vlog2.txt
+vlog3.txt
+searchlib_prod_features_test_app
+searchlib_featurebenchmark_app
diff --git a/searchlib/src/tests/features/CMakeLists.txt b/searchlib/src/tests/features/CMakeLists.txt
new file mode 100644
index 00000000000..f1703b02c8b
--- /dev/null
+++ b/searchlib/src/tests/features/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(searchlib_prod_features_test_app
+ SOURCES
+ prod_features.cpp
+ prod_features_framework.cpp
+ prod_features_attributematch.cpp
+ prod_features_fieldmatch.cpp
+ prod_features_fieldtermmatch.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_prod_features_test_app COMMAND sh prod_features_test.sh)
+vespa_add_executable(searchlib_featurebenchmark_app
+ SOURCES
+ featurebenchmark.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_featurebenchmark_app COMMAND searchlib_featurebenchmark_app BENCHMARK)
diff --git a/searchlib/src/tests/features/DESC b/searchlib/src/tests/features/DESC
new file mode 100644
index 00000000000..333541aa0a0
--- /dev/null
+++ b/searchlib/src/tests/features/DESC
@@ -0,0 +1 @@
+features test. Take a look at features.cpp for details.
diff --git a/searchlib/src/tests/features/FILES b/searchlib/src/tests/features/FILES
new file mode 100644
index 00000000000..6e53d562fc0
--- /dev/null
+++ b/searchlib/src/tests/features/FILES
@@ -0,0 +1,3 @@
+beta_features.cpp
+prod_features.cpp
+nativerank.cpp
diff --git a/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-double.txt b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-double.txt
new file mode 100644
index 00000000000..a4319bdae53
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-double.txt
@@ -0,0 +1,7 @@
+case=dotProduct
+numruns=10000000
+numdocs=1000
+numvalues=1000
+collectiontype=array
+datatype=double
+dotProduct.vector=[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9]
diff --git a/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-float.txt b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-float.txt
new file mode 100644
index 00000000000..0371c72f13a
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-float.txt
@@ -0,0 +1,7 @@
+case=dotProduct
+numruns=10000000
+numdocs=1000
+numvalues=1000
+collectiontype=array
+datatype=float
+dotProduct.vector=[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9]
diff --git a/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-int.txt b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-int.txt
new file mode 100644
index 00000000000..0e27edf2e09
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-int.txt
@@ -0,0 +1,7 @@
+case=dotProduct
+numruns=10000000
+numdocs=1000
+numvalues=1000
+collectiontype=array
+datatype=int
+dotProduct.vector=[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9]
diff --git a/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-long.txt b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-long.txt
new file mode 100644
index 00000000000..ca1aa57e738
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-array-long.txt
@@ -0,0 +1,7 @@
+case=dotProduct
+numruns=10000000
+numdocs=1000
+numvalues=1000
+collectiontype=array
+datatype=long
+dotProduct.vector=[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9]
diff --git a/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-wset.txt b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-wset.txt
new file mode 100644
index 00000000000..38c323c667d
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/dotproduct/c-100000-1000-wset.txt
@@ -0,0 +1,7 @@
+case=dotProduct
+numruns=1000000
+numdocs=1000
+numvalues=1000
+collectiontype=wset
+datatype=int
+dotProduct.vector={0:2,1:2,2:2,3:2,4:2,5:2,6:2,7:2,8:2,9:2,10:2,11:2,12:2,13:2,14:2,15:2,16:2,17:2,18:2,19:2,20:2,21:2,22:2,23:2,24:2,25:2,26:2,27:2,28:2,29:2,30:2,31:2,32:2,33:2,34:2,35:2,36:2,37:2,38:2,39:2,40:2,41:2,42:2,43:2,44:2,45:2,46:2,47:2,48:2,49:2,50:2,51:2,52:2,53:2,54:2,55:2,56:2,57:2,58:2,59:2,60:2,61:2,62:2,63:2,64:2,65:2,66:2,67:2,68:2,69:2,70:2,71:2,72:2,73:2,74:2,75:2,76:2,77:2,78:2,79:2,80:2,81:2,82:2,83:2,84:2,85:2,86:2,87:2,88:2,89:2,90:2,91:2,92:2,93:2,94:2,95:2,96:2,97:2,98:2,99:2,100:2,101:2,102:2,103:2,104:2,105:2,106:2,107:2,108:2,109:2,110:2,111:2,112:2,113:2,114:2,115:2,116:2,117:2,118:2,119:2,120:2,121:2,122:2,123:2,124:2,125:2,126:2,127:2,128:2,129:2,130:2,131:2,132:2,133:2,134:2,135:2,136:2,137:2,138:2,139:2,140:2,141:2,142:2,143:2,144:2,145:2,146:2,147:2,148:2,149:2,150:2,151:2,152:2,153:2,154:2,155:2,156:2,157:2,158:2,159:2,160:2,161:2,162:2,163:2,164:2,165:2,166:2,167:2,168:2,169:2,170:2,171:2,172:2,173:2,174:2,175:2,176:2,177:2,178:2,179:2,180:2,181:2,182:2,183:2,184:2,185:2,186:2,187:2,188:2,189:2,190:2,191:2,192:2,193:2,194:2,195:2,196:2,197:2,198:2,199:2,200:2,201:2,202:2,203:2,204:2,205:2,206:2,207:2,208:2,209:2,210:2,211:2,212:2,213:2,214:2,215:2,216:2,217:2,218:2,219:2,220:2,221:2,222:2,223:2,224:2,225:2,226:2,227:2,228:2,229:2,230:2,231:2,232:2,233:2,234:2,235:2,236:2,237:2,238:2,239:2,240:2,241:2,242:2,243:2,244:2,245:2,246:2,247:2,248:2,249:2,250:2,251:2,252:2,253:2,254:2,255:2,256:2,257:2,258:2,259:2,260:2,261:2,262:2,263:2,264:2,265:2,266:2,267:2,268:2,269:2,270:2,271:2,272:2,273:2,274:2,275:2,276:2,277:2,278:2,279:2,280:2,281:2,282:2,283:2,284:2,285:2,286:2,287:2,288:2,289:2,290:2,291:2,292:2,293:2,294:2,295:2,296:2,297:2,298:2,299:2,300:2,301:2,302:2,303:2,304:2,305:2,306:2,307:2,308:2,309:2,310:2,311:2,312:2,313:2,314:2,315:2,316:2,317:2,318:2,319:2,320:2,321:2,322:2,323:2,324:2,325:2,326:2,327:2,328:2,329:2,330:2,331:2,332:2,333:2,334:2,335:2,336:2,337:2,338:2,339:2,340:2,341:2,342:2,343:2,344:2,345:2,346:2,347:2,348:2,349:2,350:2,351:2,352:2,353:2,354:2,355:2,356:2,357:2,358:2,359:2,360:2,361:2,362:2,363:2,364:2,365:2,366:2,367:2,368:2,369:2,370:2,371:2,372:2,373:2,374:2,375:2,376:2,377:2,378:2,379:2,380:2,381:2,382:2,383:2,384:2,385:2,386:2,387:2,388:2,389:2,390:2,391:2,392:2,393:2,394:2,395:2,396:2,397:2,398:2,399:2,400:2,401:2,402:2,403:2,404:2,405:2,406:2,407:2,408:2,409:2,410:2,411:2,412:2,413:2,414:2,415:2,416:2,417:2,418:2,419:2,420:2,421:2,422:2,423:2,424:2,425:2,426:2,427:2,428:2,429:2,430:2,431:2,432:2,433:2,434:2,435:2,436:2,437:2,438:2,439:2,440:2,441:2,442:2,443:2,444:2,445:2,446:2,447:2,448:2,449:2,450:2,451:2,452:2,453:2,454:2,455:2,456:2,457:2,458:2,459:2,460:2,461:2,462:2,463:2,464:2,465:2,466:2,467:2,468:2,469:2,470:2,471:2,472:2,473:2,474:2,475:2,476:2,477:2,478:2,479:2,480:2,481:2,482:2,483:2,484:2,485:2,486:2,487:2,488:2,489:2,490:2,491:2,492:2,493:2,494:2,495:2,496:2,497:2,498:2,499:2,500:2,501:2,502:2,503:2,504:2,505:2,506:2,507:2,508:2,509:2,510:2,511:2,512:2,513:2,514:2,515:2,516:2,517:2,518:2,519:2,520:2,521:2,522:2,523:2,524:2,525:2,526:2,527:2,528:2,529:2,530:2,531:2,532:2,533:2,534:2,535:2,536:2,537:2,538:2,539:2,540:2,541:2,542:2,543:2,544:2,545:2,546:2,547:2,548:2,549:2,550:2,551:2,552:2,553:2,554:2,555:2,556:2,557:2,558:2,559:2,560:2,561:2,562:2,563:2,564:2,565:2,566:2,567:2,568:2,569:2,570:2,571:2,572:2,573:2,574:2,575:2,576:2,577:2,578:2,579:2,580:2,581:2,582:2,583:2,584:2,585:2,586:2,587:2,588:2,589:2,590:2,591:2,592:2,593:2,594:2,595:2,596:2,597:2,598:2,599:2,600:2,601:2,602:2,603:2,604:2,605:2,606:2,607:2,608:2,609:2,610:2,611:2,612:2,613:2,614:2,615:2,616:2,617:2,618:2,619:2,620:2,621:2,622:2,623:2,624:2,625:2,626:2,627:2,628:2,629:2,630:2,631:2,632:2,633:2,634:2,635:2,636:2,637:2,638:2,639:2,640:2,641:2,642:2,643:2,644:2,645:2,646:2,647:2,648:2,649:2,650:2,651:2,652:2,653:2,654:2,655:2,656:2,657:2,658:2,659:2,660:2,661:2,662:2,663:2,664:2,665:2,666:2,667:2,668:2,669:2,670:2,671:2,672:2,673:2,674:2,675:2,676:2,677:2,678:2,679:2,680:2,681:2,682:2,683:2,684:2,685:2,686:2,687:2,688:2,689:2,690:2,691:2,692:2,693:2,694:2,695:2,696:2,697:2,698:2,699:2,700:2,701:2,702:2,703:2,704:2,705:2,706:2,707:2,708:2,709:2,710:2,711:2,712:2,713:2,714:2,715:2,716:2,717:2,718:2,719:2,720:2,721:2,722:2,723:2,724:2,725:2,726:2,727:2,728:2,729:2,730:2,731:2,732:2,733:2,734:2,735:2,736:2,737:2,738:2,739:2,740:2,741:2,742:2,743:2,744:2,745:2,746:2,747:2,748:2,749:2,750:2,751:2,752:2,753:2,754:2,755:2,756:2,757:2,758:2,759:2,760:2,761:2,762:2,763:2,764:2,765:2,766:2,767:2,768:2,769:2,770:2,771:2,772:2,773:2,774:2,775:2,776:2,777:2,778:2,779:2,780:2,781:2,782:2,783:2,784:2,785:2,786:2,787:2,788:2,789:2,790:2,791:2,792:2,793:2,794:2,795:2,796:2,797:2,798:2,799:2,800:2,801:2,802:2,803:2,804:2,805:2,806:2,807:2,808:2,809:2,810:2,811:2,812:2,813:2,814:2,815:2,816:2,817:2,818:2,819:2,820:2,821:2,822:2,823:2,824:2,825:2,826:2,827:2,828:2,829:2,830:2,831:2,832:2,833:2,834:2,835:2,836:2,837:2,838:2,839:2,840:2,841:2,842:2,843:2,844:2,845:2,846:2,847:2,848:2,849:2,850:2,851:2,852:2,853:2,854:2,855:2,856:2,857:2,858:2,859:2,860:2,861:2,862:2,863:2,864:2,865:2,866:2,867:2,868:2,869:2,870:2,871:2,872:2,873:2,874:2,875:2,876:2,877:2,878:2,879:2,880:2,881:2,882:2,883:2,884:2,885:2,886:2,887:2,888:2,889:2,890:2,891:2,892:2,893:2,894:2,895:2,896:2,897:2,898:2,899:2,900:2,901:2,902:2,903:2,904:2,905:2,906:2,907:2,908:2,909:2,910:2,911:2,912:2,913:2,914:2,915:2,916:2,917:2,918:2,919:2,920:2,921:2,922:2,923:2,924:2,925:2,926:2,927:2,928:2,929:2,930:2,931:2,932:2,933:2,934:2,935:2,936:2,937:2,938:2,939:2,940:2,941:2,942:2,943:2,944:2,945:2,946:2,947:2,948:2,949:2,950:2,951:2,952:2,953:2,954:2,955:2,956:2,957:2,958:2,959:2,960:2,961:2,962:2,963:2,964:2,965:2,966:2,967:2,968:2,969:2,970:2,971:2,972:2,973:2,974:2,975:2,976:2,977:2,978:2,979:2,980:2,981:2,982:2,983:2,984:2,985:2,986:2,987:2,988:2,989:2,990:2,991:2,992:2,993:2,994:2,995:2,996:2,997:2,998:2,999:2}
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-1.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-1.txt
new file mode 100644
index 00000000000..3b3e0915e9e
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-1.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=1
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-10.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-10.txt
new file mode 100644
index 00000000000..322784fc409
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-10.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=10
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-100.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-100.txt
new file mode 100644
index 00000000000..9a31201941c
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-100.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=100
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-1000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-1000.txt
new file mode 100644
index 00000000000..0a7b99c79fb
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-1000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-10000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-10000.txt
new file mode 100644
index 00000000000..1f859dc4ac6
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-10000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-5.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-5.txt
new file mode 100644
index 00000000000..1d9b6de23a4
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-5.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=5
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-50.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-50.txt
new file mode 100644
index 00000000000..c50f602a111
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-50.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=50
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100-500.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-500.txt
new file mode 100644
index 00000000000..163a9bfd96d
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100-500.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=500
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-100.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-100.txt
new file mode 100644
index 00000000000..b6a1094140b
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-100.txt
@@ -0,0 +1,6 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1-callgrind.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1-callgrind.txt
new file mode 100644
index 00000000000..d3fc48be0be
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1-callgrind.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=1
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1.txt
new file mode 100644
index 00000000000..b6d4d2b4bb3
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=1
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10.txt
new file mode 100644
index 00000000000..67d1db34e17
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=10
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100-callgrind.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100-callgrind.txt
new file mode 100644
index 00000000000..838ee6871f0
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100-callgrind.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=100
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100.txt
new file mode 100644
index 00000000000..3e02b0ee27f
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-100.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=100
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1000.txt
new file mode 100644
index 00000000000..407579b6bee
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-1000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10000.txt
new file mode 100644
index 00000000000..57aa1759b23
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-10000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-5.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-5.txt
new file mode 100644
index 00000000000..d91604f0bb5
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-5.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=5
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-50.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-50.txt
new file mode 100644
index 00000000000..7d388e25cfa
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-50.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=50
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-500.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-500.txt
new file mode 100644
index 00000000000..7cfc899b1f3
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000-500.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=500
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-1000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000.txt
new file mode 100644
index 00000000000..f06091fbcaa
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-1000.txt
@@ -0,0 +1,6 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1.txt
new file mode 100644
index 00000000000..b62b8b21e7c
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=1
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10.txt
new file mode 100644
index 00000000000..19f133833aa
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=10
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-100.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-100.txt
new file mode 100644
index 00000000000..7dbfc2731a1
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-100.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=100
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1000.txt
new file mode 100644
index 00000000000..e436ffb270c
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-1000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10000.txt
new file mode 100644
index 00000000000..ec2727a7035
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-10000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-5.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-5.txt
new file mode 100644
index 00000000000..cadd682a817
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-5.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=5
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-50.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-50.txt
new file mode 100644
index 00000000000..66c3203ad25
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-50.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=50
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-500.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-500.txt
new file mode 100644
index 00000000000..c82fba41604
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000-500.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
+fieldMatch(bar).maxAlternativeSegmentations=500
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-10000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000.txt
new file mode 100644
index 00000000000..bd2404eba81
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-10000.txt
@@ -0,0 +1,6 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c a x x b x x x a x b x x x x x a b x x c
+numruns=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-1.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-1.txt
new file mode 100644
index 00000000000..6266271fe4f
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-1.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=1
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-10.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-10.txt
new file mode 100644
index 00000000000..9f7593f8c76
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-10.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=10
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-100.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-100.txt
new file mode 100644
index 00000000000..20a26196c44
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-100.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=100
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-1000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-1000.txt
new file mode 100644
index 00000000000..126a7f4355d
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-1000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-10000.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-10000.txt
new file mode 100644
index 00000000000..456762710e1
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-10000.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-5.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-5.txt
new file mode 100644
index 00000000000..2839245ccdd
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-5.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=5
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-50.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-50.txt
new file mode 100644
index 00000000000..a94fb7cecd8
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-50.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=50
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20-500.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-500.txt
new file mode 100644
index 00000000000..a53dd4fd6a7
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20-500.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
+fieldMatch(bar).maxAlternativeSegmentations=500
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/c-20.txt b/searchlib/src/tests/features/benchmark/fieldmatch/c-20.txt
new file mode 100644
index 00000000000..82d455795d4
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/c-20.txt
@@ -0,0 +1,6 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a x x b x x x a x b x x x x x a b x x c
+numruns=10000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/phrase-02.txt b/searchlib/src/tests/features/benchmark/fieldmatch/phrase-02.txt
new file mode 100644
index 00000000000..b55e2d60429
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/phrase-02.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
+numruns=100000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/phrase-10.txt b/searchlib/src/tests/features/benchmark/fieldmatch/phrase-10.txt
new file mode 100644
index 00000000000..8f934a3e2a1
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/phrase-10.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x a b c x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
+numruns=100000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/phrase-50.txt b/searchlib/src/tests/features/benchmark/fieldmatch/phrase-50.txt
new file mode 100644
index 00000000000..e1b687802f9
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/phrase-50.txt
@@ -0,0 +1,7 @@
+case=fieldMatch
+feature=fieldMatch(bar)
+index=bar
+query=a b c
+field=a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x a b c x x x x x x x
+numruns=100000
+fieldMatch(bar).maxAlternativeSegmentations=1000
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/plot.rb b/searchlib/src/tests/features/benchmark/fieldmatch/plot.rb
new file mode 100644
index 00000000000..ffbbc25e354
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/plot.rb
@@ -0,0 +1,30 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+require '../plotlib'
+
+if ARGV.size == 0
+ puts "must specify folder"
+ exit
+end
+
+folder = ARGV[0]
+extra = ""
+extra = ARGV[1] if ARGV.size == 2
+field = [20, 100, 1000, 10000]
+segmentation = [1, 5, 10, 50, 100, 500, 1000, 10000]
+
+dat = folder + "/plot.dat"
+png = folder + "/plot.png"
+
+file = File.open(dat, "w")
+segmentation.each do |s|
+ file.write("#{s} ")
+ field.each do |f|
+ file.write(extract_data(folder + "/c-#{f}-#{s}.out") + " ")
+ end
+ file.write("\n")
+end
+file.close
+
+titles = ["fl-20", "fl-100", "fl-1000", "fl-10000"]
+
+plot_graph(dat, titles, png, "fieldMatch feature (#{extra})", "maxAlternativeSegmentations", "execution time per document (ms)", folder)
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/readme.txt b/searchlib/src/tests/features/benchmark/fieldmatch/readme.txt
new file mode 100644
index 00000000000..a96922e58fb
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/readme.txt
@@ -0,0 +1,22 @@
+** Running the benchmark **
+ruby run.rb folder
+folder is the place to store the output files.
+
+
+** Generating gnu plots **
+ruby plot.rb folder "description"
+folder contains the output files and description are used when setting the title of the graph.
+
+
+** Config file format **
+c-x-y.txt
+x is the length of the field and y is the value for maxAlternativeSegmentations.
+
+
+** Running callgrind **
+valgrind --tool=callgrind ../../featurebenchmark -c c-1000-1-callgrind.txt
+valgrind --tool=callgrind ../../featurebenchmark -c c-1000-100-callgrind.txt
+The numruns config value is reduced in these two config files.
+
+The output after running callgrind is two files: callgrind.out.x and callgrind.out.y.
+Use kcachegrind to look at these two files.
diff --git a/searchlib/src/tests/features/benchmark/fieldmatch/run.rb b/searchlib/src/tests/features/benchmark/fieldmatch/run.rb
new file mode 100644
index 00000000000..d0350c454e8
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/fieldmatch/run.rb
@@ -0,0 +1,17 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+if ARGV.size == 0
+ puts "must specify folder"
+ exit
+end
+
+folder = ARGV[0]
+cases = [20, 100, 1000, 10000]
+segmentations = [1, 5, 10, 50, 100, 500, 1000, 10000]
+cases.each do |c|
+ segmentations.each do |s|
+ file = "c-#{c}-#{s}"
+ cmd = "script -c \"../../featurebenchmark -c #{file}.txt\" " + folder + "/#{file}.out"
+ puts cmd
+ `#{cmd}`
+ end
+end
diff --git a/searchlib/src/tests/features/benchmark/plotlib.rb b/searchlib/src/tests/features/benchmark/plotlib.rb
new file mode 100644
index 00000000000..53a1ee984a9
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/plotlib.rb
@@ -0,0 +1,36 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+def plot_graph(dat, titles, png, title, xlabel, ylabel, folder)
+ plot_cmd = "";
+ plot_cmd += "set terminal png\n"
+ plot_cmd += "set output \"#{png}\"\n"
+ plot_cmd += "set title \"#{title}\"\n"
+ plot_cmd += "set xlabel \"#{xlabel}\"\n"
+ plot_cmd += "set ylabel \"#{ylabel}\"\n"
+ plot_cmd += "set logscale\n"
+
+ plots = []
+ c = 2
+ titles.each do |title|
+ plots.push("\"#{dat}\" using 1:#{c} title \"#{title}\" with linespoints")
+ c += 1
+ end
+ plot_cmd += "plot "
+ plot_cmd += plots.join(", ")
+
+ plot_cmd_file = File.open(folder + "/plot.cmd", "w")
+ plot_cmd_file.write(plot_cmd);
+ plot_cmd_file.close
+ cmd = "gnuplot " + folder + "/plot.cmd"
+ puts cmd
+ puts `#{cmd}`
+end
+
+def extract_data(file_name)
+ content = IO.readlines(file_name).join
+ r = /ETPD:\s*(\d+\.\d+)/
+ if content =~ r
+ return $1
+ end
+ return "0"
+end
+
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-1.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-1.txt
new file mode 100644
index 00000000000..f46508379af
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-1.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-10.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-10.txt
new file mode 100644
index 00000000000..cd9a34865cb
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-10.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1+1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-100.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-100.txt
new file mode 100644
index 00000000000..1d3007a14c5
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-100.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-200.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-200.txt
new file mode 100644
index 00000000000..0a9db3c3539
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-200.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-400.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-400.txt
new file mode 100644
index 00000000000..41600fb943d
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-400.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-5.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-5.txt
new file mode 100644
index 00000000000..b4704f8a822
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-5.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-50.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-50.txt
new file mode 100644
index 00000000000..74790ff0a21
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-50.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/c-800.txt b/searchlib/src/tests/features/benchmark/rankingexpression/c-800.txt
new file mode 100644
index 00000000000..57c250137fe
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/c-800.txt
@@ -0,0 +1,4 @@
+case=rankingExpression
+feature=rankingExpression
+numruns=1000000
+rankingExpression.rankingScript=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/plot.rb b/searchlib/src/tests/features/benchmark/rankingexpression/plot.rb
new file mode 100644
index 00000000000..ca586e1176e
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/plot.rb
@@ -0,0 +1,22 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+require '../plotlib'
+
+folder = ARGV[0]
+extra = ""
+extra = ARGV[1] if ARGV.size == 2
+trees = [1, 5, 10, 50, 100, 200, 400, 800]
+
+dat = folder + "/plot.dat"
+png = folder + "/plot.png"
+
+file = File.open(dat, "w")
+trees.each do |t|
+ file.write("#{t} ")
+ file.write(extract_data(folder + "/c-#{t}.out") + " ")
+ file.write("\n")
+end
+file.close
+
+titles = ["expression"]
+
+plot_graph(dat, titles, png, "rankingExpression feature (#{extra})", "number of trees", "execution time per document (ms)", folder)
diff --git a/searchlib/src/tests/features/benchmark/rankingexpression/run.rb b/searchlib/src/tests/features/benchmark/rankingexpression/run.rb
new file mode 100644
index 00000000000..2f707e35b51
--- /dev/null
+++ b/searchlib/src/tests/features/benchmark/rankingexpression/run.rb
@@ -0,0 +1,14 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+if ARGV.size == 0
+ puts "must specify folder"
+ exit
+end
+
+folder = ARGV[0]
+trees = [1, 5, 10, 50, 100, 200, 400, 800]
+trees.each do |t|
+ file = "c-#{t}"
+ cmd = "script -c \"../../featurebenchmark -c #{file}.txt\" " + folder + "/#{file}.out"
+ puts cmd
+ `#{cmd}`
+end
diff --git a/searchlib/src/tests/features/beta/.gitignore b/searchlib/src/tests/features/beta/.gitignore
new file mode 100644
index 00000000000..3a7ba416343
--- /dev/null
+++ b/searchlib/src/tests/features/beta/.gitignore
@@ -0,0 +1 @@
+searchlib_beta_features_test_app
diff --git a/searchlib/src/tests/features/beta/CMakeLists.txt b/searchlib/src/tests/features/beta/CMakeLists.txt
new file mode 100644
index 00000000000..ee7020f01fc
--- /dev/null
+++ b/searchlib/src/tests/features/beta/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_beta_features_test_app
+ SOURCES
+ beta_features.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(
+ NAME searchlib_beta_features_test_app
+ COMMAND searchlib_beta_features_test_app
+ ENVIRONMENT "VESPA_LOG_TARGET=file:vlog1.txt"
+)
diff --git a/searchlib/src/tests/features/beta/beta_features.cpp b/searchlib/src/tests/features/beta/beta_features.cpp
new file mode 100644
index 00000000000..e5642f475de
--- /dev/null
+++ b/searchlib/src/tests/features/beta/beta_features.cpp
@@ -0,0 +1,726 @@
+// 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("beta_features_test");
+
+#include <boost/tokenizer.hpp>
+#include <vespa/searchlib/attribute/attributeguard.h>
+#include <vespa/searchlib/attribute/attributemanager.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/attributevector.hpp>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/floatbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
+#include <vespa/searchlib/features/agefeature.h>
+#include <vespa/searchlib/features/attributefeature.h>
+#include <vespa/searchlib/features/attributematchfeature.h>
+#include <vespa/searchlib/features/fieldlengthfeature.h>
+#include <vespa/searchlib/features/fieldmatchfeature.h>
+#include <vespa/searchlib/features/fieldtermmatchfeature.h>
+#include <vespa/searchlib/features/firstphasefeature.h>
+#include <vespa/searchlib/features/flow_completeness_feature.h>
+#include <vespa/searchlib/features/jarowinklerdistancefeature.h>
+#include <vespa/searchlib/features/matchfeature.h>
+#include <vespa/searchlib/features/nowfeature.h>
+#include <vespa/searchlib/features/proximityfeature.h>
+#include <vespa/searchlib/features/queryfeature.h>
+#include <vespa/searchlib/features/querycompletenessfeature.h>
+#include <vespa/searchlib/features/randomfeature.h>
+#include <vespa/searchlib/features/rankingexpressionfeature.h>
+#include <vespa/searchlib/features/reverseproximityfeature.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/features/termeditdistancefeature.h>
+#include <vespa/searchlib/features/termfeature.h>
+#include <vespa/searchlib/features/utils.h>
+#include <vespa/searchlib/fef/featurenamebuilder.h>
+#include <vespa/searchlib/fef/indexproperties.h>
+#include <vespa/searchlib/fef/test/plugin/setup.h>
+#include <vespa/searchlib/util/rand48.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+//---------------------------------------------------------------------------------------------------------------------
+// TermPositionList
+//---------------------------------------------------------------------------------------------------------------------
+typedef std::pair<uint32_t, uint32_t> TermPosition;
+class TermPositionList : public std::vector<TermPosition> {
+public:
+ TermPositionList &add(uint32_t termId, uint32_t pos) {
+ push_back(TermPosition(termId, pos));
+ return *this;
+ }
+ TermPositionList &clear() {
+ std::vector<TermPosition>::clear();
+ return *this;
+ }
+};
+
+//---------------------------------------------------------------------------------------------------------------------
+// Test
+//---------------------------------------------------------------------------------------------------------------------
+class Test : public FtTestApp {
+public:
+ int Main();
+ void testJaroWinklerDistance();
+ void testProximity();
+ void testFlowCompleteness();
+ void testQueryCompleteness();
+ void testReverseProximity();
+ void testTermEditDistance();
+
+private:
+ void assertJaroWinklerDistance(const vespalib::string &query, const vespalib::string &field, feature_t expected);
+ void assertQueryCompleteness(FtFeatureTest & ft, uint32_t firstOcc, uint32_t hits, uint32_t miss);
+ void assertTermEditDistance(const vespalib::string &query, const vespalib::string &field,
+ uint32_t expectedDel, uint32_t expectedIns, uint32_t expectedSub);
+
+private:
+ search::fef::BlueprintFactory _factory;
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("beta_features_test");
+
+ // Configure factory with all known blueprints.
+ setup_fef_test_plugin(_factory);
+ setup_search_features(_factory);
+
+ // Test all features.
+ testJaroWinklerDistance(); TEST_FLUSH();
+ testProximity(); TEST_FLUSH();
+ testFlowCompleteness(); TEST_FLUSH();
+ testQueryCompleteness(); TEST_FLUSH();
+ testReverseProximity(); TEST_FLUSH();
+ testTermEditDistance(); TEST_FLUSH();
+
+ TEST_DONE();
+ return 0;
+}
+
+void
+Test::testJaroWinklerDistance()
+{
+ {
+ // Test blueprint.
+ JaroWinklerDistanceBlueprint pt;
+ {
+ EXPECT_TRUE(assertCreateInstance(pt, "jaroWinklerDistance"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_FAIL(pt, params.add("foo"));
+ FT_SETUP_FAIL(pt, params.add("0"));
+ params.clear();
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "afoo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wfoo");
+ FT_SETUP_FAIL(pt, ie, params);
+ FT_SETUP_OK (pt, ie, params.add("foo"), in.add("fieldLength(foo)"), out.add("out"));
+ FT_SETUP_FAIL(pt, ie, params.add("afoo"));
+ FT_SETUP_FAIL(pt, ie, params.add("wfoo"));
+ FT_SETUP_FAIL(pt, ie, params.add("1"));
+ }
+ {
+ FT_DUMP_EMPTY(_factory, "jaroWinklerDistance");
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "abar");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wbar");
+ FT_DUMP_EMPTY(_factory, "jaroWinklerDistance", ie); // must be a single value index field
+
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ StringList dump;
+ FT_DUMP(_factory, "jaroWinklerDistance", ie, dump/*.add("jaroWinklerDistance(bar).out")*/);
+ }
+ }
+ {
+ // These measures are taken from table 6 in the paper "Overview of Record Linkage and Current Research Directions"
+ // by William E. Winkler. It is available at: http://www.census.gov/srd/papers/pdf/rrs2006-02.pdf
+ //
+ // Note that the strings used as query and field here are transformed into query and field terms, and therefore
+ // they all need to be unique. The second occurence of a character in the below names are therefore
+ // capitalized. A comment is given whenever our result is different from what is presented in the paper (only 2
+ // of 17 is actually different).
+ assertJaroWinklerDistance("shackleford", "shackelford", 1 - 0.982f);
+ assertJaroWinklerDistance("dunNigham", "cunnigham", 1 - 0.852f); // 3x'n' in query, removed one
+ assertJaroWinklerDistance("nichlesoN", "nichulsoN", 1 - 0.956f);
+ assertJaroWinklerDistance("jones", "johnsoN", 1 - 0.832f);
+ assertJaroWinklerDistance("masSey", "masSie", 1 - 0.933f);
+ assertJaroWinklerDistance("abroms", "abrAms", 1 - 0.922f);
+ assertJaroWinklerDistance("hardin", "martinez", 1 - 0.722f); // no measure was given
+ assertJaroWinklerDistance("itman", "smith", 1 - 0.622f); // no measure was given
+ assertJaroWinklerDistance("jeraldinE", "geraldinE", 1 - 0.926f);
+ assertJaroWinklerDistance("marhtA", "marthA", 1 - 0.961f);
+ assertJaroWinklerDistance("micheLlE", "michael", 1 - 0.921f);
+ assertJaroWinklerDistance("julies", "juliUs", 1 - 0.933f);
+ assertJaroWinklerDistance("tanyA", "tonyA", 1 - 0.880f);
+ assertJaroWinklerDistance("dwayne", "duane", 1 - 0.765f); // was 0.840 in paper
+ assertJaroWinklerDistance("sean", "suSan", 1 - 0.672f); // was 0.805 in paper
+ assertJaroWinklerDistance("jon", "john", 1 - 0.933f);
+ assertJaroWinklerDistance("jon", "jan", 1 - 0.800f); // no measure was given
+ }
+}
+
+void
+Test::assertJaroWinklerDistance(const vespalib::string &query, const vespalib::string &field, feature_t expected)
+{
+ FtFeatureTest ft(_factory, "jaroWinklerDistance(foo)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FT_SETUP(ft, query, StringMap().add("foo", field), 1);
+
+ RankResult res;
+ ASSERT_TRUE(ft.execute(res.setEpsilon(0.001).addScore("jaroWinklerDistance(foo).out", expected)));
+}
+
+void
+Test::testProximity()
+{
+
+ { // Test blueprint.
+ ProximityBlueprint prototype;
+ {
+ EXPECT_TRUE(assertCreateInstance(prototype, "proximity"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(prototype, params);
+ FT_SETUP_FAIL(prototype, params.add("foo"));
+ FT_SETUP_FAIL(prototype, params.add("0"));
+ FT_SETUP_FAIL(prototype, params.add("1"));
+ FT_SETUP_FAIL(prototype, params.add("2"));
+ params.clear();
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FT_SETUP_FAIL(prototype, ie, params.add("foo"));
+ FT_SETUP_FAIL(prototype, ie, params.add("0"));
+ FT_SETUP_OK (prototype, ie, params.add("1"), in, out.add("out").add("posA").add("posB"));
+ FT_SETUP_FAIL(prototype, ie, params.add("2"));
+ }
+
+ {
+ FT_DUMP_EMPTY(_factory, "proximity");
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ FT_DUMP_EMPTY(_factory, "proximity", ie); // must be an index field
+
+ StringList dump;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+#ifdef VISIT_BETA_FEATURES
+ for (uint32_t a = 0; a < 5; ++a) {
+ for (uint32_t b = a + 1; b < 6; ++b) {
+ vespalib::string bn = vespalib::make_string("proximity(bar,%u,%u)", a, b);
+ dump.add(bn + ".out");
+ dump.add(bn + ".posA");
+ dump.add(bn + ".posB");
+ }
+ }
+#endif
+ FT_DUMP(_factory, "proximity", ie, dump);
+ }
+ }
+ {
+ // Test executor.
+ FtFeatureTest ft(_factory, "proximity(foo,0,1)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ASSERT_TRUE(ft.setup());
+
+ search::fef::test::RankResult exp;
+ exp.addScore("proximity(foo,0,1).out", util::FEATURE_MAX).
+ addScore("proximity(foo,0,1).posA", util::FEATURE_MAX).
+ addScore("proximity(foo,0,1).posB", util::FEATURE_MIN);
+ ASSERT_TRUE(ft.execute(exp, 1));
+ }
+ {
+ FtFeatureTest ft(_factory, "proximity(foo,0,1)");
+ ASSERT_TRUE(!ft.setup());
+
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ search::fef::test::MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 50));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 30));
+ search::fef::test::RankResult exp;
+ exp.addScore("proximity(foo,0,1).out", util::FEATURE_MAX).
+ addScore("proximity(foo,0,1).posA", util::FEATURE_MAX).
+ addScore("proximity(foo,0,1).posB", util::FEATURE_MIN);
+ ASSERT_TRUE(mdb->apply(1));
+ ASSERT_TRUE(ft.execute(exp, 1));
+
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, 20));
+ ASSERT_TRUE(mdb->apply(2));
+ ASSERT_TRUE(ft.execute(exp, 2));
+
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 10));
+ ASSERT_TRUE(mdb->apply(3));
+ exp .clear()
+ .addScore("proximity(foo,0,1).out", 10.0f)
+ .addScore("proximity(foo,0,1).posA", 10.0f)
+ .addScore("proximity(foo,0,1).posB", 20.0f);
+ ASSERT_TRUE(ft.execute(exp, 3));
+ }
+ {
+ for (int a = 0; a < 10; ++a) {
+ for (int b = 0; b < 10; ++b) {
+ FtFeatureTest ft(_factory, "proximity(foo,0,1)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ search::fef::test::MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 10));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, a));
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, b));
+ ASSERT_TRUE(mdb->apply(1));
+
+ search::fef::test::RankResult exp;
+ exp .addScore("proximity(foo,0,1).out", a < b ? b - a : util::FEATURE_MAX)
+ .addScore("proximity(foo,0,1).posA", a < b ? a : util::FEATURE_MAX)
+ .addScore("proximity(foo,0,1).posB", a < b ? b : util::FEATURE_MIN);
+ TEST_STATE(vespalib::make_string("a=%u, b=%u", a, b).c_str());
+ EXPECT_TRUE(ft.execute(exp));
+ }
+ }
+ }
+}
+
+void
+Test::testQueryCompleteness()
+{
+ { // Test blueprint.
+ QueryCompletenessBlueprint prototype;
+
+ EXPECT_TRUE(assertCreateInstance(prototype, "queryCompleteness"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(prototype, params);
+ FT_SETUP_FAIL(prototype, params.add("foo"));
+ FT_SETUP_FAIL(prototype, params.add("0"));
+ params.clear();
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FT_SETUP_OK (prototype, ie, params.add("foo"), in, out.add("hit").add("miss"));
+ FT_SETUP_OK (prototype, ie, params.add("0"), in, out);
+ FT_SETUP_OK (prototype, ie, params.add("1"), in, out);
+ FT_SETUP_FAIL(prototype, ie, params.add("2"));
+
+ FT_DUMP_EMPTY(_factory, "queryCompleteness");
+ FT_DUMP_EMPTY(_factory, "queryCompleteness", ie);
+ }
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "queryCompleteness(foo)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ // add 5 term nodes
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+ // from 0 to 5 hits (5 to 0 misses)
+ for (uint32_t i = 0; i < 6; ++i) {
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setFieldLength("foo", 10);
+ for (uint32_t j = 0; j < i; ++j) {
+ mdb->addOccurence("foo", j, j);
+ }
+ ASSERT_TRUE(mdb->apply(1));
+ RankResult exp;
+ exp.addScore("queryCompleteness(foo).hit", (feature_t)(i));
+ exp.addScore("queryCompleteness(foo).miss", (feature_t)(5 - i));
+ EXPECT_TRUE(ft.execute(exp));
+ }
+ }
+ { // Test executor.
+ FtFeatureTest ft(_factory, "queryCompleteness(foo,5,10)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ // before window
+ assertQueryCompleteness(ft, 4, 0, 1);
+ // inside window
+ assertQueryCompleteness(ft, 5, 1, 0);
+ // inside window
+ assertQueryCompleteness(ft, 9, 1, 0);
+ // after window
+ assertQueryCompleteness(ft, 10, 0, 1);
+ }
+}
+
+void
+Test::assertQueryCompleteness(FtFeatureTest & ft, uint32_t firstOcc, uint32_t hits, uint32_t miss)
+{
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setFieldLength("foo", 20);
+ mdb->addOccurence("foo", 0, firstOcc);
+ ASSERT_TRUE(mdb->apply(1));
+ RankResult exp;
+ exp.addScore("queryCompleteness(foo,5,10).hit", hits);
+ exp.addScore("queryCompleteness(foo,5,10).miss", miss);
+ EXPECT_TRUE(ft.execute(exp));
+}
+
+// BFI implementation: brute force and ignorance
+int cntFlow(int m1, int m2, int m3, int m4)
+{
+ int flow = 0;
+
+ for (int p1p = 0; p1p < 4; p1p++) {
+ if (((1 << p1p) & m1) == 0) continue;
+ for (int p2p = 0; p2p < 4; p2p++) {
+ if (((1 << p2p) & m2) == 0) continue;
+ int f2 = 1;
+ if (p2p != p1p) ++f2;
+ for (int p3p = 0; p3p < 4; p3p++) {
+ if (((1 << p3p) & m3) == 0) continue;
+ int f3 = f2;
+ if (p3p != p1p && p3p != p2p) ++f3;
+ for (int p4p = 0; p4p < 4; p4p++) {
+ if (((1 << p4p) & m4) == 0) continue;
+ int f4 = f3;
+ if (p4p != p1p && p4p != p2p && p4p != p3p) ++f4;
+ if (flow < f4) flow = f4;
+ }
+ }
+ }
+ }
+ return flow;
+}
+
+void
+Test::testFlowCompleteness()
+{
+ { // Test blueprint.
+ TEST_STATE("test flow completeness blueprint");
+ FlowCompletenessBlueprint prototype;
+
+ EXPECT_TRUE(assertCreateInstance(prototype, "flowCompleteness"));
+
+ StringList params, in, out;
+ TEST_DO(FT_SETUP_FAIL(prototype, params));
+ TEST_DO(FT_SETUP_FAIL(prototype, params.add("foo")));
+ TEST_DO(FT_SETUP_FAIL(prototype, params.add("0")));
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+
+ params.clear();
+ params.add("foo");
+
+ out.add("completeness").add("fieldCompleteness")
+ .add("queryCompleteness").add("elementWeight")
+ .add("weight").add("flow");
+
+ StringList expDump;
+ for (size_t i = 0; i < out.size(); ++i) {
+ vespalib::string fn = "flowCompleteness(foo).";
+ fn.append(out[i]);
+ expDump.push_back(fn);
+ }
+
+ TEST_DO(FT_SETUP_OK(prototype, ie, params, in, out));
+ TEST_DO(FT_SETUP_FAIL(prototype, ie, params.add("2")));
+ TEST_DO(FT_DUMP_EMPTY(_factory, "flowCompleteness"));
+#ifdef notyet
+ TEST_DO(FT_DUMP(_factory, "flowCompleteness", ie, expDump));
+#endif
+ }
+
+ { // Test executor.
+ TEST_STATE("test flow completeness executor");
+
+ FtFeatureTest ft(_factory, "flowCompleteness(foo)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ // add 5 term nodes
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+ // from 0 to 5 hits (5 to 0 misses)
+ for (uint32_t i = 0; i < 6; ++i) {
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setFieldLength("foo", 10);
+ for (uint32_t j = 0; j < i; ++j) {
+ mdb->addOccurence("foo", j, j);
+ }
+ ASSERT_TRUE(mdb->apply(1));
+ RankResult exp;
+ exp.setEpsilon(0.000001);
+ exp.addScore("flowCompleteness(foo)", i * 0.15);
+ exp.addScore("flowCompleteness(foo).completeness", i * 0.15); // == 0.1*0.5 + 0.2*(1-0.5)
+ exp.addScore("flowCompleteness(foo).fieldCompleteness", i * 0.1);
+ exp.addScore("flowCompleteness(foo).queryCompleteness", i * 0.2);
+ exp.addScore("flowCompleteness(foo).elementWeight", i > 0 ? 1 : 0);
+ exp.addScore("flowCompleteness(foo).weight", 100.0);
+ exp.addScore("flowCompleteness(foo).flow", i);
+ TEST_STATE("run execute");
+ EXPECT_TRUE(ft.execute(exp));
+ }
+ }
+
+
+ { // Test executor, pass 2
+ TEST_STATE("test flow completeness executor (pass 2)");
+
+ FtFeatureTest ft(_factory, "flowCompleteness(foo)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ // add 4 term nodes
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ // each term will have 1 to 3 positions it matches,
+ // with various points of overlap
+
+ for (uint32_t t0m = 1; t0m < 15 ; ++t0m) {
+
+ for (uint32_t t1m = 1; t1m < 15 ; ++t1m) {
+
+ for (uint32_t t2m = 1; t2m < 15 ; ++t2m) {
+
+ for (uint32_t t3m = 1; t3m < 15 ; ++t3m) {
+
+ int flow = cntFlow(t0m, t1m, t2m, t3m);
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setFieldLength("foo", 4);
+ for (int pos = 0; pos < 4; ++pos) {
+ if (((1 << pos) & t0m) != 0) mdb->addOccurence("foo", 0, pos);
+ if (((1 << pos) & t1m) != 0) mdb->addOccurence("foo", 1, pos);
+ if (((1 << pos) & t2m) != 0) mdb->addOccurence("foo", 2, pos);
+ if (((1 << pos) & t3m) != 0) mdb->addOccurence("foo", 3, pos);
+ }
+
+ ASSERT_TRUE(mdb->apply(1));
+ RankResult exp;
+ exp.setEpsilon(0.0001);
+ exp.addScore("flowCompleteness(foo)", flow * 0.25);
+ exp.addScore("flowCompleteness(foo).completeness", flow * 0.25);
+ exp.addScore("flowCompleteness(foo).fieldCompleteness", flow * 0.25);
+ exp.addScore("flowCompleteness(foo).queryCompleteness", flow * 0.25);
+ exp.addScore("flowCompleteness(foo).elementWeight", 1);
+ exp.addScore("flowCompleteness(foo).weight", 100.0);
+ exp.addScore("flowCompleteness(foo).flow", flow);
+ TEST_STATE(vespalib::make_string("execute t0m=%u t1m=%u t2m=%u t3m=%u flow=%u",
+ t0m, t1m, t2m, t3m, flow).c_str());
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void
+Test::testReverseProximity()
+{
+ { // Test blueprint.
+ ReverseProximityBlueprint prototype;
+ {
+ EXPECT_TRUE(assertCreateInstance(prototype, "reverseProximity"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(prototype, params);
+ FT_SETUP_FAIL(prototype, params.add("foo"));
+ FT_SETUP_FAIL(prototype, params.add("0"));
+ FT_SETUP_FAIL(prototype, params.add("1"));
+ FT_SETUP_FAIL(prototype, params.add("2"));
+ params.clear();
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FT_SETUP_FAIL(prototype, ie, params.add("foo"));
+ FT_SETUP_FAIL(prototype, ie, params.add("0"));
+ FT_SETUP_OK (prototype, ie, params.add("1"), in, out.add("out").add("posA").add("posB"));
+ FT_SETUP_FAIL(prototype, ie, params.add("2"));
+ }
+
+ {
+ FT_DUMP_EMPTY(_factory, "reverseProximity");
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ FT_DUMP_EMPTY(_factory, "reverseProximity", ie); // must be an index field
+
+ StringList dump;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+#ifdef VISIT_BETA_FEATURES
+ for (uint32_t a = 0; a < 5; ++a) {
+ for (uint32_t b = a + 1; b < 6; ++b) {
+ vespalib::string bn = vespalib::make_string("reverseProximity(bar,%u,%u)", a, b);
+ dump.add(bn + ".out");
+ dump.add(bn + ".posA");
+ dump.add(bn + ".posB");
+ }
+ }
+#endif
+ FT_DUMP(_factory, "reverseProximity", ie, dump);
+ }
+ }
+
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "reverseProximity(foo,0,1)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ASSERT_TRUE(ft.setup());
+ search::fef::test::RankResult exp;
+ exp.addScore("reverseProximity(foo,0,1).out", util::FEATURE_MAX).
+ addScore("reverseProximity(foo,0,1).posA", util::FEATURE_MIN).
+ addScore("reverseProximity(foo,0,1).posB", util::FEATURE_MAX);
+ ASSERT_TRUE(ft.execute(exp, 1));
+ }
+ {
+ FtFeatureTest ft(_factory, "reverseProximity(foo,0,1)"); ASSERT_TRUE(!ft.setup());
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields(); ASSERT_TRUE(ft.setup());
+
+ search::fef::test::MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 50));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 20));
+ search::fef::test::RankResult exp;
+ exp .addScore("reverseProximity(foo,0,1).out", util::FEATURE_MAX)
+ .addScore("reverseProximity(foo,0,1).posA", util::FEATURE_MIN)
+ .addScore("reverseProximity(foo,0,1).posB", util::FEATURE_MAX);
+ ASSERT_TRUE(mdb->apply(1));
+ ASSERT_TRUE(ft.execute(exp, 1));
+
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, 30));
+ ASSERT_TRUE(mdb->apply(2));
+ ASSERT_TRUE(ft.execute(exp, 2));
+
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, 10));
+ ASSERT_TRUE(mdb->apply(3));
+ exp .clear()
+ .addScore("reverseProximity(foo,0,1).out", 10.0f)
+ .addScore("reverseProximity(foo,0,1).posA", 20.0f)
+ .addScore("reverseProximity(foo,0,1).posB", 10.0f);
+ ASSERT_TRUE(ft.execute(exp, 3));
+ }
+ {
+ for (int a = 0; a < 10; ++a) {
+ for (int b = 0; b < 10; ++b) {
+ FtFeatureTest ft(_factory, "reverseProximity(foo,0,1)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ search::fef::test::MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 10));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, a));
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, b));
+ ASSERT_TRUE(mdb->apply(1));
+
+ search::fef::test::RankResult exp;
+ exp .addScore("reverseProximity(foo,0,1).out", a >= b ? a - b : util::FEATURE_MAX)
+ .addScore("reverseProximity(foo,0,1).posA", a >= b ? a : util::FEATURE_MIN)
+ .addScore("reverseProximity(foo,0,1).posB", a >= b ? b : util::FEATURE_MAX);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ }
+ }
+}
+
+void
+Test::testTermEditDistance()
+{
+ { // Test blueprint.
+ TermEditDistanceBlueprint prototype;
+ {
+ EXPECT_TRUE(assertCreateInstance(prototype, "termEditDistance"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(prototype, params);
+ FT_SETUP_FAIL(prototype, params.add("foo"));
+ FT_SETUP_FAIL(prototype, params.add("0"));
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "afoo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wfoo");
+ FT_SETUP_FAIL(prototype, ie, params.clear());
+ FT_SETUP_OK (prototype, ie, params.add("foo"), in.add("fieldLength(foo)"), out.add("out").add("del").add("ins").add("sub"));
+ FT_SETUP_FAIL(prototype, ie, params.add("afoo"));
+ FT_SETUP_FAIL(prototype, ie, params.add("wfoo"));
+ FT_SETUP_FAIL(prototype, ie, params.add("0"));
+ }
+
+ {
+ FT_DUMP_EMPTY(_factory, "termEditDistance");
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "abar");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wbar");
+ FT_DUMP_EMPTY(_factory, "termEditDistance", ie); // must be a single-value index field
+
+ StringList dump;
+#ifdef VISIT_BETA_FEATURES
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ vespalib::string bn = "termEditDistance(bar)";
+ dump.add(bn + ".out");
+ dump.add(bn + ".del");
+ dump.add(bn + ".ins");
+ dump.add(bn + ".sub");
+#endif
+ FT_DUMP(_factory, "termEditDistance", ie, dump);
+ }
+ }
+
+ { // Test executor.
+ assertTermEditDistance("abcde", "abcde", 0, 0, 0);
+ assertTermEditDistance("abcde", "abcd.", 0, 0, 1);
+ assertTermEditDistance("abcde", ".bcd.", 0, 0, 2);
+ assertTermEditDistance("abcde", ".bc..", 0, 0, 3);
+ assertTermEditDistance("abcde", "..c..", 0, 0, 4);
+ assertTermEditDistance("abcd" , "..c..", 0, 1, 3);
+ assertTermEditDistance("abc", "..c..", 0, 2, 2);
+ assertTermEditDistance("ab", "..b..", 0, 3, 1);
+ assertTermEditDistance("a", "..a..", 0, 4, 0);
+ }
+}
+
+// #pragma GCC diagnostic ignored "-Wstrict-aliasing"
+
+void
+Test::assertTermEditDistance(const vespalib::string &query, const vespalib::string &field,
+ uint32_t expectedDel, uint32_t expectedIns, uint32_t expectedSub)
+{
+ // Setup feature test.
+ vespalib::string feature = "termEditDistance(foo)";
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ StringMap foo;
+ foo.add("foo", field);
+ FT_SETUP(ft, query, foo, 1);
+
+ // Execute and compare results.
+ search::fef::test::RankResult exp;
+ exp .addScore(feature + ".out", (feature_t)(expectedDel*1 + expectedIns*1 + expectedSub*1))
+ .addScore(feature + ".del", (feature_t)expectedDel)
+ .addScore(feature + ".ins", (feature_t)expectedIns)
+ .addScore(feature + ".sub", (feature_t)expectedSub);
+ ASSERT_TRUE(ft.execute(exp));
+}
diff --git a/searchlib/src/tests/features/element_completeness/.gitignore b/searchlib/src/tests/features/element_completeness/.gitignore
new file mode 100644
index 00000000000..9d45fbda0ad
--- /dev/null
+++ b/searchlib/src/tests/features/element_completeness/.gitignore
@@ -0,0 +1 @@
+searchlib_element_completeness_test_app
diff --git a/searchlib/src/tests/features/element_completeness/CMakeLists.txt b/searchlib/src/tests/features/element_completeness/CMakeLists.txt
new file mode 100644
index 00000000000..aee13befe2d
--- /dev/null
+++ b/searchlib/src/tests/features/element_completeness/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_element_completeness_test_app
+ SOURCES
+ element_completeness_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_element_completeness_test_app COMMAND searchlib_element_completeness_test_app)
diff --git a/searchlib/src/tests/features/element_completeness/FILES b/searchlib/src/tests/features/element_completeness/FILES
new file mode 100644
index 00000000000..5b995b34729
--- /dev/null
+++ b/searchlib/src/tests/features/element_completeness/FILES
@@ -0,0 +1 @@
+element_completeness_test.cpp
diff --git a/searchlib/src/tests/features/element_completeness/element_completeness_test.cpp b/searchlib/src/tests/features/element_completeness/element_completeness_test.cpp
new file mode 100644
index 00000000000..24d1625520d
--- /dev/null
+++ b/searchlib/src/tests/features/element_completeness/element_completeness_test.cpp
@@ -0,0 +1,201 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/element_completeness_feature.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+std::vector<vespalib::string> featureNamesFoo() {
+ std::vector<vespalib::string> f;
+ f.push_back("elementCompleteness(foo).completeness");
+ f.push_back("elementCompleteness(foo).fieldCompleteness");
+ f.push_back("elementCompleteness(foo).queryCompleteness");
+ f.push_back("elementCompleteness(foo).elementWeight");
+ return f;
+}
+
+const size_t TOTAL = 0;
+const size_t FIELD = 1;
+const size_t QUERY = 2;
+const size_t WEIGHT = 3;
+
+FtIndex indexFoo() {
+ FtIndex idx;
+ idx.field("foo");
+ return idx;
+}
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "foo");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "bar");
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ std::vector<vespalib::string> expect;
+ size_t dumped;
+ virtual void visitDumpFeature(const vespalib::string &name) {
+ EXPECT_LESS(dumped, expect.size());
+ EXPECT_EQUAL(expect[dumped++], name);
+ }
+ FeatureDumpFixture() : IDumpFeatureVisitor(), expect(featureNamesFoo()), dumped(0) {}
+};
+
+struct RankFixture : BlueprintFactoryFixture {
+ Properties idxProps;
+ RankFixture() : BlueprintFactoryFixture(), idxProps() {}
+ void test(const vespalib::string &queryStr, const FtIndex &index,
+ feature_t field, feature_t query, int32_t weight = 1, feature_t factor = 0.5,
+ bool useStaleMatchData = false)
+ {
+ std::vector<vespalib::string> names = featureNamesFoo();
+ ASSERT_TRUE(names.size() == 4u);
+ RankResult expect;
+ expect.addScore(names[TOTAL], field*factor + query*(1-factor))
+ .addScore(names[FIELD], field).addScore(names[QUERY], query)
+ .addScore(names[WEIGHT], (double)weight);
+ FtFeatureTest ft(factory, names);
+ ft.getIndexEnv().getProperties().import(idxProps);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "bar");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "baz");
+ FtTestApp::FT_SETUP(ft, FtUtil::toQuery(queryStr), index, 1);
+ RankResult actual;
+ EXPECT_TRUE(ft.executeOnly(actual, useStaleMatchData ? 2 : 1));
+ for (size_t i = 0; i < names.size(); ++i) {
+ TEST_STATE(names[i].c_str());
+ EXPECT_EQUAL(expect.getScore(names[i]), actual.getScore(names[i]));
+ }
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("elementCompleteness");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<ElementCompletenessBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that appropriate features are dumped", ElementCompletenessBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+ EXPECT_EQUAL(f3.expect.size(), f3.dumped);
+}
+
+TEST_FF("require that setup can be done on index field", ElementCompletenessBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "foo")));
+}
+
+TEST_FF("require that setup can not be done on attribute field", ElementCompletenessBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(bar)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(!((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "bar")));
+}
+
+TEST_FF("require that default config parameters are correct", ElementCompletenessBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "foo")));
+ EXPECT_EQUAL(0u, f1.getParams().fieldId);
+ EXPECT_EQUAL(0.5, f1.getParams().fieldCompletenessImportance);
+}
+
+TEST_FF("require that blueprint can be configured", ElementCompletenessBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ f2.indexEnv.getProperties().add("elementCompleteness(foo).fieldCompletenessImportance", "0.75");
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "foo")));
+ EXPECT_EQUAL(0.75, f1.getParams().fieldCompletenessImportance);
+}
+
+TEST_F("require that no match gives zero outputs", RankFixture) {
+ TEST_DO(f.test("x", indexFoo().element("y"), 0.0, 0.0, 0));
+}
+
+TEST_F("require that perfect match gives max outputs", RankFixture) {
+ TEST_DO(f.test("x", indexFoo().element("x"), 1.0, 1.0));
+}
+
+TEST_F("require that matching half the field gives appropriate outputs", RankFixture) {
+ TEST_DO(f.test("x", indexFoo().element("x y"), 0.5, 1.0));
+ TEST_DO(f.test("x y", indexFoo().element("x y a b"), 0.5, 1.0));
+}
+
+TEST_F("require that matching half the query gives appropriate outputs", RankFixture) {
+ TEST_DO(f.test("x y", indexFoo().element("x"), 1.0, 0.5));
+ TEST_DO(f.test("x y a b", indexFoo().element("x y"), 1.0, 0.5));
+}
+
+TEST_F("require that query completeness is affected by query term weight", RankFixture) {
+ TEST_DO(f.test("x!300 y!100", indexFoo().element("y"), 1.0, 0.25));
+ TEST_DO(f.test("x!300 y!100", indexFoo().element("x"), 1.0, 0.75));
+}
+
+TEST_F("require that field completeness is not affected by duplicate field tokens", RankFixture) {
+ TEST_DO(f.test("x", indexFoo().element("x y y y"), 0.25, 1.00));
+ TEST_DO(f.test("x", indexFoo().element("x x y y"), 0.25, 1.00));
+ TEST_DO(f.test("x", indexFoo().element("x x x y"), 0.25, 1.00));
+ TEST_DO(f.test("x", indexFoo().element("x x x x"), 0.25, 1.00));
+}
+
+TEST_F("require that field completeness is affected by duplicate query terms", RankFixture) {
+ TEST_DO(f.test("x", indexFoo().element("x x x x"), 0.25, 1.00));
+ TEST_DO(f.test("x x", indexFoo().element("x x x x"), 0.50, 1.00));
+ TEST_DO(f.test("x x x", indexFoo().element("x x x x"), 0.75, 1.00));
+ TEST_DO(f.test("x x x x", indexFoo().element("x x x x"), 1.00, 1.00));
+}
+
+TEST_F("require that a single field token can match multiple query terms", RankFixture) {
+ TEST_DO(f.test("x", indexFoo().element("x"), 1.00, 1.00));
+ TEST_DO(f.test("x x", indexFoo().element("x"), 1.00, 1.00));
+ TEST_DO(f.test("x x x", indexFoo().element("x"), 1.00, 1.00));
+ TEST_DO(f.test("x x x x", indexFoo().element("x"), 1.00, 1.00));
+}
+
+TEST_F("require that field completeness importance can be adjusted", RankFixture) {
+ f.idxProps.clear().add("elementCompleteness(foo).fieldCompletenessImportance", "0.1");
+ TEST_DO(f.test("x y", indexFoo().element("x"), 1.0, 0.5, 1, 0.1));
+ f.idxProps.clear().add("elementCompleteness(foo).fieldCompletenessImportance", "0.4");
+ TEST_DO(f.test("x y", indexFoo().element("x"), 1.0, 0.5, 1, 0.4));
+ f.idxProps.clear().add("elementCompleteness(foo).fieldCompletenessImportance", "0.7");
+ TEST_DO(f.test("x y", indexFoo().element("x"), 1.0, 0.5, 1, 0.7));
+}
+
+TEST_F("require that order is not relevant", RankFixture) {
+ TEST_DO(f.test("x y a b", indexFoo().element("n x n y"), 0.5, 0.5));
+ TEST_DO(f.test("a b x y", indexFoo().element("y x n n"), 0.5, 0.5));
+ TEST_DO(f.test("a y x b", indexFoo().element("x n y n"), 0.5, 0.5));
+}
+
+TEST_F("require that element is selected based on completeness times element weight", RankFixture) {
+ f.idxProps.clear().add("elementCompleteness(foo).fieldCompletenessImportance", "0.0");
+ TEST_DO(f.test("x y a b", indexFoo().element("x", 39).element("y", 39).element("a b", 19).element("x y a b", 10), 1.0, 1.0, 10, 0.0));
+ TEST_DO(f.test("x y a b", indexFoo().element("x", 39).element("y", 39).element("a b", 21).element("x y a b", 10), 1.0, 0.5, 21, 0.0));
+ TEST_DO(f.test("x y a b", indexFoo().element("x", 39).element("y", 45).element("a b", 21).element("x y a b", 10), 1.0, 0.25, 45, 0.0));
+}
+
+TEST_F("require that stale match data is ignored", RankFixture) {
+ TEST_DO(f.test("x y a b", indexFoo().element("x y"), 0.0, 0.0, 0, 0.5, true));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/element_similarity_feature/.gitignore b/searchlib/src/tests/features/element_similarity_feature/.gitignore
new file mode 100644
index 00000000000..36e60cd547e
--- /dev/null
+++ b/searchlib/src/tests/features/element_similarity_feature/.gitignore
@@ -0,0 +1 @@
+searchlib_element_similarity_feature_test_app
diff --git a/searchlib/src/tests/features/element_similarity_feature/CMakeLists.txt b/searchlib/src/tests/features/element_similarity_feature/CMakeLists.txt
new file mode 100644
index 00000000000..08e3b04cd73
--- /dev/null
+++ b/searchlib/src/tests/features/element_similarity_feature/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_element_similarity_feature_test_app
+ SOURCES
+ element_similarity_feature_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_element_similarity_feature_test_app COMMAND searchlib_element_similarity_feature_test_app)
diff --git a/searchlib/src/tests/features/element_similarity_feature/element_similarity_feature_test.cpp b/searchlib/src/tests/features/element_similarity_feature/element_similarity_feature_test.cpp
new file mode 100644
index 00000000000..181f2fb71f3
--- /dev/null
+++ b/searchlib/src/tests/features/element_similarity_feature/element_similarity_feature_test.cpp
@@ -0,0 +1,371 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/element_similarity_feature.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <initializer_list>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+const vespalib::string DEFAULT = "elementSimilarity(foo)";
+const vespalib::string PROXIMITY = "elementSimilarity(foo).proximity";
+const vespalib::string ORDER = "elementSimilarity(foo).order";
+const vespalib::string QUERY = "elementSimilarity(foo).query_coverage";
+const vespalib::string FIELD = "elementSimilarity(foo).field_coverage";
+const vespalib::string WEIGHT = "elementSimilarity(foo).weight";
+
+FtIndex indexFoo() {
+ FtIndex idx;
+ idx.field("foo");
+ return idx;
+}
+
+//-----------------------------------------------------------------------------
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "foo");
+ builder.addField(FieldType::INDEX, CollectionType::ARRAY, "bar");
+ builder.addField(FieldType::INDEX, CollectionType::SINGLE, "baz");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "fox");
+ set("elementSimilarity(foo).output.proximity", "max(p)");
+ set("elementSimilarity(foo).output.order", "max(o)");
+ set("elementSimilarity(foo).output.query_coverage", "max(q)");
+ set("elementSimilarity(foo).output.field_coverage", "max(f)");
+ set("elementSimilarity(foo).output.weight", "max(w)");
+ set("elementSimilarity(bar).output.default", "avg(1)");
+ }
+ IndexFixture &set(const vespalib::string &key, const vespalib::string &value) {
+ Properties tmp;
+ tmp.add(key, value);
+ indexEnv.getProperties().import(tmp);
+ return *this;
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ std::vector<vespalib::string> actual;
+ FeatureDumpFixture() : IDumpFeatureVisitor(), actual() {}
+ virtual void visitDumpFeature(const vespalib::string &name) {
+ actual.push_back(name);
+ }
+};
+
+struct RankFixture : BlueprintFactoryFixture {
+ RankFixture() : BlueprintFactoryFixture() {}
+ double get_feature(const vespalib::string &query, const FtIndex &index, const vespalib::string &select,
+ const IndexFixture &idx_env = IndexFixture())
+ {
+ std::vector<vespalib::string> names({"elementSimilarity(foo).default", // use 'default' explicitly to verify default output name
+ "elementSimilarity(foo).proximity",
+ "elementSimilarity(foo).order",
+ "elementSimilarity(foo).query_coverage",
+ "elementSimilarity(foo).field_coverage",
+ "elementSimilarity(foo).weight"});
+ FtFeatureTest ft(factory, names);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "foo");
+ ft.getIndexEnv().getBuilder().getIndexEnv().getProperties().import(idx_env.indexEnv.getProperties());
+ FtTestApp::FT_SETUP(ft, FtUtil::toQuery(query), index, 1);
+ {
+ RankResult stale;
+ EXPECT_TRUE(ft.executeOnly(stale, 2));
+ EXPECT_EQUAL(0.0, stale.getScore(select));
+ }
+ RankResult actual;
+ EXPECT_TRUE(ft.executeOnly(actual, 1));
+ return actual.getScore(select);
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+double prox(uint32_t dist) {
+ return (dist > 8) ? 0 : (1.0 - (((dist-1)/8.0) * ((dist-1)/8.0)));
+}
+
+double sum(std::initializer_list<double> values) {
+ double my_sum = 0.0;
+ for (double value: values) {
+ my_sum += value;
+ }
+ return my_sum;
+}
+
+double comb(std::initializer_list<double> values) {
+ return (sum(values)/values.size());
+}
+
+double mix(double proximity, double order, double query, double field) {
+ return (0.35 * proximity) + (0.15 * order) + (0.30 * query) + (0.20 * field);
+}
+
+//-----------------------------------------------------------------------------
+
+template <typename A, typename B>
+bool cmp_lists_impl(const A &a, const B &b) {
+ std::vector<typename A::value_type> tmp_a(a.begin(), a.end());
+ std::vector<typename B::value_type> tmp_b(b.begin(), b.end());
+ std::sort(tmp_a.begin(), tmp_a.end());
+ std::sort(tmp_b.begin(), tmp_b.end());
+ if (!EXPECT_EQUAL(tmp_a.size(), tmp_b.size())) {
+ return false;
+ }
+ for (size_t i = 0; i < tmp_a.size(); ++i) {
+ if(!EXPECT_EQUAL(tmp_a[i], tmp_b[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename T>
+void dump_list(const vespalib::string &name, const T &list) {
+ fprintf(stderr, "list(name: '%s', size: %zu)\n", name.c_str(), list.size());
+ std::vector<typename T::value_type> tmp(list.begin(), list.end());
+ std::sort(tmp.begin(), tmp.end());
+ for (vespalib::string item: tmp) {
+ fprintf(stderr, " '%s'\n", item.c_str());
+ }
+}
+
+template <typename A, typename B>
+bool cmp_lists(const A &a, const B &b) {
+ if(!cmp_lists_impl(a, b)) {
+ dump_list("expected", a);
+ dump_list("actual", b);
+ return false;
+ }
+ return true;
+};
+
+//-----------------------------------------------------------------------------
+
+TEST_F("require that blueprint can be created from factory", BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("elementSimilarity");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<ElementSimilarityBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that appropriate features are dumped", ElementSimilarityBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+ EXPECT_TRUE(cmp_lists(std::vector<vespalib::string>({"elementSimilarity(foo)",
+ "elementSimilarity(foo).proximity",
+ "elementSimilarity(foo).order",
+ "elementSimilarity(foo).query_coverage",
+ "elementSimilarity(foo).field_coverage",
+ "elementSimilarity(foo).weight",
+ "elementSimilarity(bar)"}),
+ f3.actual));
+}
+
+bool try_setup(ElementSimilarityBlueprint &blueprint, const IndexFixture &index, const vespalib::string &field) {
+ DummyDependencyHandler deps(blueprint);
+ blueprint.setName(vespalib::make_string("%s(%s)", blueprint.getBaseName().c_str(), field.c_str()));
+ return ((Blueprint&)blueprint).setup(index.indexEnv, std::vector<vespalib::string>(1, field));
+}
+
+TEST_FF("require that setup can be done on weighted set index field", ElementSimilarityBlueprint, IndexFixture) {
+ EXPECT_TRUE(try_setup(f1, f2, "foo"));
+}
+
+TEST_FF("require that setup can be done on array index field", ElementSimilarityBlueprint, IndexFixture) {
+ EXPECT_TRUE(try_setup(f1, f2, "bar"));
+}
+
+TEST_FF("require that setup can be done on single value index field", ElementSimilarityBlueprint, IndexFixture) {
+ EXPECT_TRUE(try_setup(f1, f2, "baz"));
+}
+
+TEST_FF("require that setup can not be done on single value attribute field", ElementSimilarityBlueprint, IndexFixture) {
+ EXPECT_FALSE(try_setup(f1, f2, "fox"));
+}
+
+TEST_FF("require that setup will fail if output expression does not contain an aggregator", ElementSimilarityBlueprint, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "p");
+ EXPECT_FALSE(try_setup(f1, f2, "foo"));
+}
+
+TEST_FF("require that setup will fail if output expression contains an unknown aggregator", ElementSimilarityBlueprint, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "bogus(p)");
+ EXPECT_FALSE(try_setup(f1, f2, "foo"));
+}
+
+TEST_FF("require that setup will fail if output expression contains an unknown symbol", ElementSimilarityBlueprint, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "max(bogus)");
+ EXPECT_FALSE(try_setup(f1, f2, "foo"));
+}
+
+TEST_FF("require that setup will fail if output expression is malformed", ElementSimilarityBlueprint, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "max(w+)");
+ EXPECT_FALSE(try_setup(f1, f2, "foo"));
+}
+
+TEST_F("require that no match gives zero outputs", RankFixture) {
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), DEFAULT));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), PROXIMITY));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), ORDER));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), QUERY));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), FIELD));
+}
+
+TEST_F("require that minal perfect match gives max outputs", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), DEFAULT));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), QUERY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), FIELD));
+}
+
+TEST_F("require that larger perfect match gives max outputs", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), DEFAULT));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), QUERY));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), FIELD));
+}
+
+TEST_F("require that extra query terms reduces order but not proximity", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("x y", indexFoo().element("x"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x y y", indexFoo().element("x"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x y y y", indexFoo().element("x"), PROXIMITY));
+
+ EXPECT_EQUAL(0.0, f1.get_feature("x y", indexFoo().element("x"), ORDER));
+ EXPECT_EQUAL(0.0, f1.get_feature("x y y", indexFoo().element("x"), ORDER));
+ EXPECT_EQUAL(0.0, f1.get_feature("x y y y", indexFoo().element("x"), ORDER));
+}
+
+TEST_F("require that extra field terms reduces proximity but not order", RankFixture) {
+ EXPECT_EQUAL(prox(2), f1.get_feature("x", indexFoo().element("x y"), PROXIMITY));
+ EXPECT_EQUAL(prox(3), f1.get_feature("x", indexFoo().element("x y y"), PROXIMITY));
+ EXPECT_EQUAL(prox(4), f1.get_feature("x", indexFoo().element("x y y y"), PROXIMITY));
+
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x y"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x y y"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x y y y"), ORDER));
+}
+
+TEST_F("require that proximity acts as expected", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("a x b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(3), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("a x x b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(4), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("a x x x b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(2), prox(2)}), f1.get_feature("a b c d e", indexFoo().element("a x b x c x d x e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(1), prox(3)}), f1.get_feature("a b c d e", indexFoo().element("a x b x c d x x e"), PROXIMITY));
+}
+
+TEST_F("require that field order does not affect proximity score", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("d c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("d x c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(3), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("d x x c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(4), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("d x x x c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(2), prox(2)}), f1.get_feature("a b c d e", indexFoo().element("d x c x a x b x e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(1), prox(3)}), f1.get_feature("a b c d e", indexFoo().element("d x c x a b x x e"), PROXIMITY));
+}
+
+TEST_F("require that order score acts as expected", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), ORDER));
+ EXPECT_EQUAL(comb({1.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("a b c e d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b a c e d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b a e d c"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 0.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("e d c b a"), ORDER));
+}
+
+TEST_F("require that proximity does not affect order score", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), ORDER));
+ EXPECT_EQUAL(comb({1.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("a x b x c x e x d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b x a x c x e x d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b x a x e x d x c"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 0.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("e x d x c x b x a"), ORDER));
+}
+
+TEST_F("require that query coverage acts as expected", RankFixture) {
+ EXPECT_EQUAL(5.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), QUERY));
+ EXPECT_EQUAL(4.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c d"), QUERY));
+ EXPECT_EQUAL(3.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c"), QUERY));
+ EXPECT_EQUAL(2.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b"), QUERY));
+ EXPECT_EQUAL(4.0/7.0, f1.get_feature("a!200 b!200 c d e", indexFoo().element("a b"), QUERY));
+ EXPECT_EQUAL(2.0/7.0, f1.get_feature("a b c!500", indexFoo().element("a b"), QUERY));
+ EXPECT_EQUAL(5.0/7.0, f1.get_feature("a b c!500", indexFoo().element("c"), QUERY));
+}
+
+TEST_F("require that field coverage acts as expected", RankFixture) {
+ EXPECT_EQUAL(5.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), FIELD));
+ EXPECT_EQUAL(4.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a x c d e"), FIELD));
+ EXPECT_EQUAL(3.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b x x e"), FIELD));
+ EXPECT_EQUAL(2.0/5.0, f1.get_feature("a b c d e", indexFoo().element("x x x d e"), FIELD));
+}
+
+TEST_F("require that first unique match is used per query term", RankFixture) {
+ EXPECT_EQUAL(prox(3), f1.get_feature("a b", indexFoo().element("a a a b"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b", indexFoo().element("a a a b"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b", indexFoo().element("a a a b"), QUERY));
+ EXPECT_EQUAL(2.0/4.0, f1.get_feature("a b", indexFoo().element("a a a b"), FIELD));
+
+ EXPECT_EQUAL(comb({prox(1), prox(2)}), f1.get_feature("a b a", indexFoo().element("a a a b"), PROXIMITY));
+ EXPECT_EQUAL(0.5, f1.get_feature("a b a", indexFoo().element("a a a b"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b a", indexFoo().element("a a a b"), QUERY));
+ EXPECT_EQUAL(3.0/4.0, f1.get_feature("a b a", indexFoo().element("a a a b"), FIELD));
+}
+
+TEST_F("require that default score combines individual signals appropriately", RankFixture) {
+ EXPECT_EQUAL(comb({prox(1), prox(3), prox(2)}), f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), PROXIMITY));
+ EXPECT_EQUAL(comb({1.0, 0.0, 1.0}), f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), ORDER));
+ EXPECT_EQUAL(4.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), QUERY));
+ EXPECT_EQUAL(4.0/7.0, f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), FIELD));
+ EXPECT_EQUAL(mix(comb({prox(1), prox(3), prox(2)}), comb({1.0, 0.0, 1.0}), 4.0/5.0, 4.0/7.0),
+ f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), DEFAULT));
+ EXPECT_EQUAL(7.0 * mix(comb({prox(1), prox(3), prox(2)}), comb({1.0, 0.0, 1.0}), 4.0/5.0, 4.0/7.0),
+ f1.get_feature("a b c d e", indexFoo().element("a c x x b x d", 7), DEFAULT));
+}
+
+TEST_FF("require that max aggregation works", RankFixture, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "max(w)");
+ EXPECT_EQUAL(5.0, f1.get_feature("x", indexFoo().element("x y", 5), DEFAULT, f2));
+ EXPECT_EQUAL(5.0, f1.get_feature("x", indexFoo().element("x y", 5).element("x y", 3), DEFAULT, f2));
+ EXPECT_EQUAL(5.0, f1.get_feature("x", indexFoo().element("x y", 3).element("x y", 5), DEFAULT, f2));
+}
+
+TEST_FF("require that avg aggregation works", RankFixture, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "avg(w)");
+ EXPECT_EQUAL(5.0, f1.get_feature("x", indexFoo().element("x y", 5), DEFAULT, f2));
+ EXPECT_EQUAL(4.0, f1.get_feature("x", indexFoo().element("x y", 5).element("x y", 3), DEFAULT, f2));
+ EXPECT_EQUAL(4.0, f1.get_feature("x", indexFoo().element("x y", 3).element("x y", 5), DEFAULT, f2));
+}
+
+TEST_FF("require that sum aggregation works", RankFixture, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "sum(w)");
+ EXPECT_EQUAL(5.0, f1.get_feature("x", indexFoo().element("x y", 5), DEFAULT, f2));
+ EXPECT_EQUAL(8.0, f1.get_feature("x", indexFoo().element("x y", 5).element("x y", 3), DEFAULT, f2));
+ EXPECT_EQUAL(8.0, f1.get_feature("x", indexFoo().element("x y", 3).element("x y", 5), DEFAULT, f2));
+}
+
+TEST_FF("require that element demultiplexing works", RankFixture, IndexFixture) {
+ f2.set("elementSimilarity(foo).output.default", "sum(q)");
+ EXPECT_EQUAL(sum({0.25, 0.5, 0.5, 0.25, 0.5}),
+ f1.get_feature("x y z t", indexFoo()
+ .element("x")
+ .element("x y")
+ .element("x z")
+ .element("y")
+ .element("x z"), DEFAULT, f2));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/euclidean_distance/.gitignore b/searchlib/src/tests/features/euclidean_distance/.gitignore
new file mode 100644
index 00000000000..2d08dd27122
--- /dev/null
+++ b/searchlib/src/tests/features/euclidean_distance/.gitignore
@@ -0,0 +1 @@
+searchlib_euclidean_distance_test_app
diff --git a/searchlib/src/tests/features/euclidean_distance/CMakeLists.txt b/searchlib/src/tests/features/euclidean_distance/CMakeLists.txt
new file mode 100644
index 00000000000..d79aa9572bc
--- /dev/null
+++ b/searchlib/src/tests/features/euclidean_distance/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_euclidean_distance_test_app
+ SOURCES
+ euclidean_distance_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_euclidean_distance_test_app COMMAND searchlib_euclidean_distance_test_app)
diff --git a/searchlib/src/tests/features/euclidean_distance/FILES b/searchlib/src/tests/features/euclidean_distance/FILES
new file mode 100644
index 00000000000..4ed7d9969b3
--- /dev/null
+++ b/searchlib/src/tests/features/euclidean_distance/FILES
@@ -0,0 +1 @@
+euclidean_distance_test.cpp
diff --git a/searchlib/src/tests/features/euclidean_distance/euclidean_distance_test.cpp b/searchlib/src/tests/features/euclidean_distance/euclidean_distance_test.cpp
new file mode 100644
index 00000000000..b0d97902728
--- /dev/null
+++ b/searchlib/src/tests/features/euclidean_distance/euclidean_distance_test.cpp
@@ -0,0 +1,115 @@
+// 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/vespalib/testkit/test_kit.h>
+
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/floatbase.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <vespa/searchlib/features/euclidean_distance_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+using search::AttributeFactory;
+using search::IntegerAttribute;
+using search::FloatingPointAttribute;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+typedef search::AttributeVector::SP AttributePtr;
+typedef FtTestApp FTA;
+
+struct SetupFixture
+{
+ EuclideanDistanceBlueprint blueprint;
+ IndexEnvironment indexEnv;
+ SetupFixture()
+ : blueprint(),
+ indexEnv()
+ {
+ FieldInfo myField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "myAttribute", 1);
+ indexEnv.getFields().push_back(myField);
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", SetupFixture)
+{
+ EXPECT_TRUE(FTA::assertCreateInstance(f.blueprint, "euclideanDistance"));
+}
+
+TEST_F("require that setup succeeds with attribute source", SetupFixture)
+{
+ FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("myAttribute").add("myVector"),
+ StringList(), StringList().add("distance"));
+}
+
+struct ExecFixture
+{
+ BlueprintFactory factory;
+ FtFeatureTest test;
+ ExecFixture(const vespalib::string &feature)
+ : factory(),
+ test(factory, feature)
+ {
+ setup_search_features(factory);
+ setupAttributeVectors();
+ setupQueryEnvironment();
+ ASSERT_TRUE(test.setup());
+ }
+ void setupAttributeVectors() {
+ std::vector<AttributePtr> attrs;
+ attrs.push_back(AttributeFactory::createAttribute("aint", AVC(AVBT::INT32, AVCT::ARRAY)));
+ attrs.push_back(AttributeFactory::createAttribute("afloat", AVC(AVBT::FLOAT, AVCT::ARRAY)));
+
+ test.getIndexEnv().getFields().push_back(FieldInfo(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint", 0));
+ test.getIndexEnv().getFields().push_back(FieldInfo(FieldType::ATTRIBUTE, CollectionType::ARRAY, "afloat", 1));
+
+ for (const auto &attr : attrs) {
+ attr->addReservedDoc();
+ attr->addDocs(1);
+ test.getIndexEnv().getAttributeManager().add(attr);
+ }
+
+ IntegerAttribute *aint = static_cast<IntegerAttribute *>(attrs[0].get());
+ aint->append(1, 1, 0);
+ aint->append(1, -2, 0);
+ aint->append(1, 3, 0);
+
+ FloatingPointAttribute *afloat = static_cast<FloatingPointAttribute *>(attrs[1].get());
+ afloat->append(1, 1.3, 0);
+ afloat->append(1, 1.5, 0);
+ afloat->append(1, -1.7, 0);
+
+ for (const auto &attr : attrs) {
+ attr->commit();
+ }
+ }
+ void setupQueryEnvironment() {
+ test.getQueryEnv().getProperties().add("euclideanDistance.intquery", "[4 5 -6]");
+ test.getQueryEnv().getProperties().add("euclideanDistance.floatquery", "[4.1 15 0.001]");
+ }
+
+};
+
+TEST_F("require that distance is calculated for integer vectors",
+ ExecFixture("euclideanDistance(aint,intquery)"))
+{
+ EXPECT_TRUE(f.test.execute(11.789826, 0.000001));
+}
+
+TEST_F("require that distance is calculated for floating point vectors",
+ ExecFixture("euclideanDistance(afloat,floatquery)"))
+{
+ EXPECT_TRUE(f.test.execute(13.891846, 0.000001));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/featurebenchmark.cpp b/searchlib/src/tests/features/featurebenchmark.cpp
new file mode 100644
index 00000000000..14e43fa7d47
--- /dev/null
+++ b/searchlib/src/tests/features/featurebenchmark.cpp
@@ -0,0 +1,657 @@
+// 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("featurebenchmark");
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <boost/tokenizer.hpp>
+
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/attributevector.hpp>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
+
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/features/utils.h>
+#include <vespa/searchlib/fef/functiontablefactory.h>
+#include <vespa/searchlib/fef/test/plugin/setup.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+using search::AttributeVector;
+using search::AttributeFactory;
+using search::IntegerAttribute;
+using search::StringAttribute;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+
+typedef AttributeVector::SP AttributePtr;
+
+class Benchmark : public FtTestApp {
+public:
+ typedef std::vector<std::pair<vespalib::string, vespalib::string> > KeyValueVector;
+
+ class Config {
+ private:
+ typedef std::map<vespalib::string, vespalib::string> StringMap;
+ StringMap _config;
+
+ bool isKnown(const vespalib::string & key) const;
+
+ public:
+ Config() : _config() {}
+ Config(const vespalib::string & fileName) : _config() {
+ init(fileName);
+ }
+ void init(const vespalib::string & fileName);
+
+ void add(const vespalib::string & key, const vespalib::string & value) {
+ _config[key] = value;
+ }
+
+ void addIfNotFound(const vespalib::string & key, const vespalib::string & value) {
+ if (_config.count(key) == 0) {
+ add(key, value);
+ }
+ }
+
+ // known config values
+ vespalib::string getCase(const vespalib::string & fallback = "") const {
+ return getAsStr("case", fallback);
+ }
+ vespalib::string getFeature(const vespalib::string & fallback = "") const {
+ return getAsStr("feature", fallback);
+ }
+ vespalib::string getIndex(const vespalib::string & fallback = "") const {
+ return getAsStr("index", fallback);
+ }
+ vespalib::string getQuery(const vespalib::string & fallback = "") const {
+ return getAsStr("query", fallback);
+ }
+ vespalib::string getField(const vespalib::string & fallback = "") const {
+ return getAsStr("field", fallback);
+ }
+ uint32_t getNumRuns(uint32_t fallback = 1000) const {
+ return getAsUint32("numruns", fallback);
+ }
+
+ // access "unknown" config values
+ vespalib::string getAsStr(const vespalib::string & key, const vespalib::string & fallback = "") const {
+ StringMap::const_iterator itr = _config.find(key);
+ if (itr != _config.end()) {
+ return vespalib::string(itr->second);
+ }
+ return vespalib::string(fallback);
+ }
+ uint32_t getAsUint32(const vespalib::string & key, uint32_t fallback = 0) const {
+ return util::strToNum<uint32_t>(getAsStr(key, vespalib::make_string("%u", fallback)));
+ }
+ double getAsDouble(const vespalib::string & key, double fallback = 0) const {
+ return util::strToNum<double>(getAsStr(key, vespalib::make_string("%f", fallback)));
+ }
+
+ KeyValueVector getUnknown() const;
+
+ friend std::ostream & operator << (std::ostream & os, const Config & cfg);
+ };
+
+private:
+ search::fef::BlueprintFactory _factory;
+ FastOS_Time _timer;
+ double _sample;
+
+ void start() { _timer.SetNow(); }
+ void sample() { _sample = _timer.MilliSecsToNow(); }
+ void setupPropertyMap(Properties & props, const KeyValueVector & values);
+ void runFieldMatch(Config & cfg);
+ void runRankingExpression(Config & cfg);
+
+ AttributePtr createAttributeVector(AVBT dt, const vespalib::string & name, const vespalib::string & ctype, uint32_t numDocs,
+ AttributeVector::largeint_t value, uint32_t valueCount);
+ AttributePtr createAttributeVector(const vespalib::string & name, const vespalib::string & ctype, uint32_t numDocs,
+ AttributeVector::largeint_t value, uint32_t valueCount);
+ AttributePtr createStringAttributeVector(const vespalib::string & name, const vespalib::string & ctype, uint32_t numDocs,
+ const std::vector<vespalib::string> & values);
+ void runAttributeMatch(Config & cfg);
+ void runAttribute(Config & cfg);
+ void runDotProduct(Config & cfg);
+ void runNativeAttributeMatch(Config & cfg);
+ void runNativeFieldMatch(Config & cfg);
+ void runNativeProximity(Config & cfg);
+
+public:
+ Benchmark() : _factory(), _timer(), _sample() {}
+ int Main();
+
+};
+
+TEST_APPHOOK(Benchmark);
+
+
+bool
+Benchmark::Config::isKnown(const vespalib::string & key) const
+{
+ if (key == vespalib::string("case") ||
+ key == vespalib::string("feature") ||
+ key == vespalib::string("index") ||
+ key == vespalib::string("query") ||
+ key == vespalib::string("field") ||
+ key == vespalib::string("numruns"))
+ {
+ return true;
+ }
+ return false;
+}
+
+void
+Benchmark::Config::init(const vespalib::string & fileName)
+{
+ std::ifstream is(fileName.c_str());
+ if (is.fail()) {
+ throw std::runtime_error(fileName);
+ }
+
+ while (is.good()) {
+ std::string line;
+ std::getline(is, line);
+ if (!line.empty()) {
+ std::vector<vespalib::string> values = FtUtil::tokenize(line, "=");
+ LOG_ASSERT(values.size() == 2);
+ add(values[0], values[1]);
+ }
+ }
+}
+
+Benchmark::KeyValueVector
+Benchmark::Config::getUnknown() const
+{
+ KeyValueVector retval;
+ for (StringMap::const_iterator itr = _config.begin(); itr != _config.end(); ++itr) {
+ if (!isKnown(itr->first)) {
+ retval.push_back(std::make_pair(itr->first, itr->second));
+ }
+ }
+ return retval;
+}
+
+std::ostream & operator << (std::ostream & os, const Benchmark::Config & cfg)
+{
+ std::cout << "getCase: '" << cfg.getCase() << "'" << std::endl;
+ std::cout << "getFeature: '" << cfg.getFeature() << "'" << std::endl;
+ std::cout << "getIndex: '" << cfg.getIndex() << "'" << std::endl;
+ std::cout << "getQuery: '" << cfg.getQuery() << "'" << std::endl;
+ std::cout << "getField: '" << cfg.getField() << "'" << std::endl;
+ std::cout << "getNumRuns: '" << cfg.getNumRuns() << "'" << std::endl;
+
+ for (StringMap::const_iterator itr = cfg._config.begin(); itr != cfg._config.end(); ++itr) {
+ os << "'" << itr->first << "'='" << itr->second << "'" << std::endl;
+ }
+ return os;
+}
+
+
+void
+Benchmark::setupPropertyMap(Properties & props, const KeyValueVector & values)
+{
+ std::cout << "**** setup property map ****" << std::endl;
+ for (uint32_t i = 0; i < values.size(); ++i) {
+ std::cout << "'" << values[i].first << "'='" << values[i].second << "'" << std::endl;
+ props.add(values[i].first, values[i].second);
+ }
+ std::cout << "**** setup property map ****" << std::endl;
+}
+
+void
+Benchmark::runFieldMatch(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "fieldMatch(foo)");
+ cfg.addIfNotFound("index", "foo");
+ cfg.addIfNotFound("query", "a b c d");
+ cfg.addIfNotFound("field", "a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ vespalib::string index = cfg.getIndex();
+ vespalib::string query = cfg.getQuery();
+ vespalib::string field = cfg.getField();
+ uint32_t numRuns = cfg.getNumRuns();
+
+ FtFeatureTest ft(_factory, feature);
+
+ setupPropertyMap(ft.getIndexEnv().getProperties(), cfg.getUnknown());
+ setupFieldMatch(ft, index, query, field, NULL, 0, 0.0f, 0);
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ ft.executeOnly(0);
+ }
+ sample();
+}
+
+void
+Benchmark::runRankingExpression(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "rankingExpression");
+ cfg.addIfNotFound("rankingExpression.rankingScript", "1 + 1 + 1 + 1");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ uint32_t numRuns = cfg.getNumRuns();
+
+ FtFeatureTest ft(_factory, feature);
+ setupPropertyMap(ft.getIndexEnv().getProperties(), cfg.getUnknown());
+ ASSERT_TRUE(ft.setup());
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ ft.executeOnly(0);
+ }
+ sample();
+}
+
+AttributePtr
+Benchmark::createAttributeVector(const vespalib::string & name, const vespalib::string & ctype, uint32_t numDocs,
+ AttributeVector::largeint_t value, uint32_t valueCount)
+{
+ return createAttributeVector(AVBT::INT32, name, ctype, numDocs, value, valueCount);
+}
+
+AttributePtr
+Benchmark::createAttributeVector(AVBT dt, const vespalib::string & name, const vespalib::string & ctype, uint32_t numDocs,
+ AttributeVector::largeint_t value, uint32_t valueCount)
+{
+ AttributePtr a;
+ if (ctype == "single") {
+ a = AttributeFactory::createAttribute(name, AVC(dt, AVCT::SINGLE));
+ std::cout << "create single int32" << std::endl;
+ } else if (ctype == "array") {
+ a = AttributeFactory::createAttribute(name, AVC(dt, AVCT::ARRAY));
+ std::cout << "create array int32" << std::endl;
+ } else if (ctype == "wset") {
+ a = AttributeFactory::createAttribute(name, AVC(dt, AVCT::WSET));
+ std::cout << "create wset int32" << std::endl;
+ }
+
+ a->addDocs(numDocs);
+ IntegerAttribute * ia = static_cast<IntegerAttribute *>(a.get());
+ for (uint32_t i = 0; i < numDocs; ++i) {
+ if (ctype == "single") {
+ ia->update(i, value);
+ } else {
+ for (uint32_t j = 0; j < valueCount; ++j) {
+ if (ctype == "array") {
+ ia->append(i, value, 0);
+ } else {
+ ia->append(i, value + j, j);
+ }
+ }
+ }
+ }
+
+ a->commit();
+ return a;
+}
+
+AttributePtr
+Benchmark::createStringAttributeVector(const vespalib::string & name, const vespalib::string & ctype, uint32_t numDocs,
+ const std::vector<vespalib::string> & values)
+{
+ AttributePtr a;
+ if (ctype == "single") {
+ a = AttributeFactory::createAttribute(name, AVC(AVBT::STRING, AVCT::SINGLE));
+ std::cout << "create single string" << std::endl;
+ } else if (ctype == "array") {
+ a = AttributeFactory::createAttribute(name, AVC(AVBT::STRING, AVCT::ARRAY));
+ std::cout << "create array string" << std::endl;
+ } else if (ctype == "wset") {
+ a = AttributeFactory::createAttribute(name, AVC(AVBT::STRING, AVCT::WSET));
+ std::cout << "create wset string" << std::endl;
+ }
+
+ a->addDocs(numDocs);
+ StringAttribute * sa = static_cast<StringAttribute *>(a.get());
+ for (uint32_t i = 0; i < numDocs; ++i) {
+ if (ctype == "single") {
+ sa->update(i, values[0]);
+ } else {
+ for (uint32_t j = 0; j < values.size(); ++j) {
+ sa->append(i, values[j], j);
+ }
+ }
+ }
+
+ a->commit();
+ return a;
+}
+
+void
+Benchmark::runAttributeMatch(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "attributeMatch(foo)");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ uint32_t numRuns = 1000000;
+ uint32_t numDocs = 1000000;
+
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getAttributeManager().add(createAttributeVector("foo", "single", numDocs, 10, 10));
+ ft.getQueryEnv().getBuilder().addAttributeNode("foo");
+ setupPropertyMap(ft.getIndexEnv().getProperties(), cfg.getUnknown());
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setWeight("foo", 0, 0);
+ mdb->apply(0);
+ TermFieldMatchData *amd = mdb->getTermFieldMatchData(0, 0);
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ {
+ amd->reset(0); // preserve old behavior
+ TermFieldMatchDataPosition pos;
+ pos.setElementWeight(i % numDocs);
+ amd->appendPosition(pos);
+ }
+ ft.executeOnly(i % numDocs);
+ }
+ sample();
+}
+
+void
+Benchmark::runAttribute(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "attribute(foo,str4)");
+ cfg.addIfNotFound("numruns", "10000000");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ uint32_t numRuns = cfg.getNumRuns();
+ uint32_t numDocs = cfg.getAsUint32("numdocs", 1000);
+ StringList values;
+ values.add("str0").add("str1").add("str2").add("str3").add("str4")
+ .add("str5").add("str6").add("str7").add("str8").add("str9");
+
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "foo");
+ ft.getIndexEnv().getAttributeManager().add(createStringAttributeVector("foo", "wset", numDocs, values));
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ ft.executeOnly(i % numDocs);
+ }
+ sample();
+}
+
+void
+Benchmark::runDotProduct(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "dotProduct(wsstr,vector)");
+ cfg.addIfNotFound("numruns", "1000000");
+ cfg.addIfNotFound("numdocs", "1000");
+ cfg.addIfNotFound("numvalues", "10");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ vespalib::string collectionType = cfg.getAsStr("collectiontype", "wset");
+ vespalib::string dataType = cfg.getAsStr("datatype", "string");
+ uint32_t numRuns = cfg.getNumRuns();
+ uint32_t numDocs = cfg.getAsUint32("numdocs", 1000);
+ uint32_t numValues = cfg.getAsUint32("numvalues", 10);
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE,
+ collectionType == "wset" ? CollectionType::WEIGHTEDSET : CollectionType::ARRAY,
+ "wsstr");
+ if (dataType == "string") {
+ StringList values;
+ for (uint32_t i = 0; i < numValues; ++i) {
+ values.add(vespalib::make_string("str%u", i));
+ }
+
+ ft.getIndexEnv().getAttributeManager().add(createStringAttributeVector("wsstr", collectionType, numDocs, values));
+ } else if (dataType == "int") {
+ ft.getIndexEnv().getAttributeManager().add(createAttributeVector(AVBT::INT32, "wsstr", collectionType, numDocs, 0, numValues));
+ } else if (dataType == "long") {
+ ft.getIndexEnv().getAttributeManager().add(createAttributeVector(AVBT::INT64, "wsstr", collectionType, numDocs, 0, numValues));
+ } else if (dataType == "float") {
+ ft.getIndexEnv().getAttributeManager().add(createAttributeVector(AVBT::FLOAT, "wsstr", collectionType, numDocs, 0, numValues));
+ } else if (dataType == "double") {
+ ft.getIndexEnv().getAttributeManager().add(createAttributeVector(AVBT::DOUBLE, "wsstr", collectionType, numDocs, 0, numValues));
+ } else {
+ std::cerr << "Illegal data type '" << dataType << std::endl;
+ }
+ ft.getQueryEnv().getProperties().add("dotProduct.vector", cfg.getAsStr("dotProduct.vector", "(str0:1)"));
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ ft.executeOnly(i % numDocs);
+ }
+ sample();
+}
+
+void
+Benchmark::runNativeAttributeMatch(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "nativeAttributeMatch(foo)");
+ cfg.addIfNotFound("numruns", "10000000");
+ cfg.addIfNotFound("numdocs", "1000000");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ uint32_t numRuns = cfg.getNumRuns();
+ uint32_t numDocs = cfg.getAsUint32("numdocs");
+
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getTableManager().addFactory(ITableFactory::SP(new FunctionTableFactory(256))); // same as backend
+ ft.getQueryEnv().getBuilder().addAttributeNode("foo")->setWeight(search::query::Weight(100));
+ setupPropertyMap(ft.getIndexEnv().getProperties(), cfg.getUnknown());
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setWeight("foo", 0, 0);
+ mdb->apply(0);
+
+ TermFieldMatchData *amd = mdb->getTermFieldMatchData(0, 0);
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ uint32_t docId = i % numDocs;
+ {
+ amd->reset(docId);
+ TermFieldMatchDataPosition pos;
+ pos.setElementWeight(docId);
+ amd->appendPosition(pos);
+ }
+ ft.executeOnly(docId);
+ }
+ sample();
+}
+
+void
+Benchmark::runNativeFieldMatch(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "nativeFieldMatch(foo)");
+ cfg.addIfNotFound("numruns", "10000000");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ uint32_t numRuns = cfg.getNumRuns();
+
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getTableManager().addFactory(ITableFactory::SP(new FunctionTableFactory(256))); // same as backend
+ std::vector<vespalib::string> searchedFields;
+ searchedFields.push_back("foo");
+ ft.getQueryEnv().getBuilder().addIndexNode(searchedFields);
+ setupPropertyMap(ft.getIndexEnv().getProperties(), cfg.getUnknown());
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ // setup occurrence data
+ mdb->setFieldLength("foo", 100);
+ mdb->addOccurence("foo", 0, 2);
+ mdb->addOccurence("foo", 0, 8);
+ mdb->addOccurence("foo", 0, 32);
+ mdb->addOccurence("foo", 0, 64);
+ ASSERT_TRUE(mdb->apply(0));
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ ft.executeOnly(0);
+ }
+ sample();
+}
+
+void
+Benchmark::runNativeProximity(Config & cfg)
+{
+ cfg.addIfNotFound("feature", "nativeProximity(foo)");
+ cfg.addIfNotFound("numruns", "10000000");
+
+ std::cout << "**** config ****" << std::endl;
+ std::cout << cfg << std::endl;
+ std::cout << "**** config ****" << std::endl;
+
+ vespalib::string feature = cfg.getFeature();
+ uint32_t numRuns = cfg.getNumRuns();
+
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getTableManager().addFactory(ITableFactory::SP(new FunctionTableFactory(256))); // same as backend
+ std::vector<vespalib::string> searchedFields;
+ searchedFields.push_back("foo");
+ ft.getQueryEnv().getBuilder().addIndexNode(searchedFields); // termId 0
+ ft.getQueryEnv().getBuilder().addIndexNode(searchedFields); // termId 1
+ setupPropertyMap(ft.getIndexEnv().getProperties(), cfg.getUnknown());
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ // setup occurrence data
+ mdb->setFieldLength("foo", 100);
+ mdb->addOccurence("foo", 0, 2);
+ mdb->addOccurence("foo", 0, 16);
+ mdb->addOccurence("foo", 0, 32);
+ mdb->addOccurence("foo", 1, 6);
+ mdb->addOccurence("foo", 1, 12);
+ mdb->addOccurence("foo", 1, 30);
+ ASSERT_TRUE(mdb->apply(0));
+
+ start();
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+ for (uint32_t i = 0; i < numRuns; ++i) {
+ ft.executeOnly(0);
+ }
+ sample();
+}
+
+int
+Benchmark::Main()
+{
+ TEST_INIT("featurebenchmark");
+
+ // Configure factory with all known blueprints.
+ setup_fef_test_plugin(_factory);
+ setup_search_features(_factory);
+
+ int idx = 1;
+ char opt;
+ const char * arg;
+ bool optError = false;
+ vespalib::string file;
+ vespalib::string feature;
+ while ((opt = GetOpt("c:f:", arg, idx)) != -1) {
+ switch (opt) {
+ case 'c':
+ file.assign(arg);
+ break;
+ case 'f':
+ feature.assign(arg);
+ break;
+ default:
+ optError = true;
+ break;
+ }
+ }
+
+ if (_argc != idx || optError) {
+ //usage();
+ return -1;
+ }
+
+ Config cfg;
+ if (file.empty()) {
+ cfg.add("case", feature);
+ } else {
+ cfg.init(file);
+ }
+
+ if (cfg.getCase() == vespalib::string("fieldMatch")) {
+ runFieldMatch(cfg);
+ } else if (cfg.getCase() == vespalib::string("rankingExpression")) {
+ runRankingExpression(cfg);
+ } else if (cfg.getCase() == vespalib::string("attributeMatch")) {
+ runAttributeMatch(cfg);
+ } else if (cfg.getCase() == vespalib::string("attribute")) {
+ runAttribute(cfg);
+ } else if (cfg.getCase() == vespalib::string("dotProduct")) {
+ runDotProduct(cfg);
+ } else if (cfg.getCase() == vespalib::string("nativeAttributeMatch")) {
+ runNativeAttributeMatch(cfg);
+ } else if (cfg.getCase() == vespalib::string("nativeFieldMatch")) {
+ runNativeFieldMatch(cfg);
+ } else if (cfg.getCase() == vespalib::string("nativeProximity")) {
+ runNativeProximity(cfg);
+ } else {
+ std::cout << "feature case '" << cfg.getCase() << "' is not known" << std::endl;
+ }
+
+ std::cout << "TET: " << _sample << " (ms)" << std::endl;
+ std::cout << "ETPD: " << std::fixed << std::setprecision(10) << _sample / cfg.getNumRuns() << " (ms)" << std::endl;
+ std::cout << "**** '" << cfg.getFeature() << "' ****" << std::endl;
+
+ TEST_DONE();
+ return 0;
+}
+
diff --git a/searchlib/src/tests/features/item_raw_score/.gitignore b/searchlib/src/tests/features/item_raw_score/.gitignore
new file mode 100644
index 00000000000..29711c1533d
--- /dev/null
+++ b/searchlib/src/tests/features/item_raw_score/.gitignore
@@ -0,0 +1 @@
+searchlib_item_raw_score_test_app
diff --git a/searchlib/src/tests/features/item_raw_score/CMakeLists.txt b/searchlib/src/tests/features/item_raw_score/CMakeLists.txt
new file mode 100644
index 00000000000..24ef339133c
--- /dev/null
+++ b/searchlib/src/tests/features/item_raw_score/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_item_raw_score_test_app
+ SOURCES
+ item_raw_score_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_item_raw_score_test_app COMMAND searchlib_item_raw_score_test_app)
diff --git a/searchlib/src/tests/features/item_raw_score/FILES b/searchlib/src/tests/features/item_raw_score/FILES
new file mode 100644
index 00000000000..bce307ff6c1
--- /dev/null
+++ b/searchlib/src/tests/features/item_raw_score/FILES
@@ -0,0 +1 @@
+item_raw_score_test.cpp
diff --git a/searchlib/src/tests/features/item_raw_score/item_raw_score_test.cpp b/searchlib/src/tests/features/item_raw_score/item_raw_score_test.cpp
new file mode 100644
index 00000000000..20f9449062d
--- /dev/null
+++ b/searchlib/src/tests/features/item_raw_score/item_raw_score_test.cpp
@@ -0,0 +1,158 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/item_raw_score_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+const vespalib::string featureName("itemRawScore(label)");
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ virtual void visitDumpFeature(const vespalib::string &) {
+ TEST_ERROR("no features should be dumped");
+ }
+ FeatureDumpFixture() : IDumpFeatureVisitor() {}
+};
+
+struct Labels {
+ virtual void inject(Properties &p) const = 0;
+ virtual ~Labels() {}
+};
+struct NoLabel : public Labels {
+ virtual void inject(Properties &) const {}
+};
+struct SingleLabel : public Labels {
+ vespalib::string label;
+ uint32_t uid;
+ SingleLabel(const vespalib::string &l, uint32_t x) : label(l), uid(x) {}
+ virtual void inject(Properties &p) const {
+ vespalib::asciistream key;
+ key << "vespa.label." << label << ".id";
+ vespalib::asciistream value;
+ value << uid;
+ p.add(key.str(), value.str());
+ }
+};
+
+struct RankFixture : BlueprintFactoryFixture, IndexFixture {
+ QueryEnvironment queryEnv;
+ RankSetup rankSetup;
+ RankProgram::UP rankProgram;
+ MatchDataLayout mdl;
+ std::vector<TermFieldHandle> fooHandles;
+ std::vector<TermFieldHandle> barHandles;
+ RankFixture(size_t fooCnt, size_t barCnt, const Labels &labels)
+ : queryEnv(&indexEnv), rankSetup(factory, indexEnv),
+ rankProgram(), mdl(), fooHandles(), barHandles()
+ {
+ for (size_t i = 0; i < fooCnt; ++i) {
+ uint32_t fieldId = indexEnv.getFieldByName("foo")->id();
+ fooHandles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.setUniqueId(i + 1);
+ term.addField(fieldId).setHandle(fooHandles.back());
+ queryEnv.getTerms().push_back(term);
+ }
+ for (size_t i = 0; i < barCnt; ++i) {
+ uint32_t fieldId = indexEnv.getFieldByName("bar")->id();
+ barHandles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.setUniqueId(fooCnt + i + 1);
+ term.addField(fieldId).setHandle(barHandles.back());
+ queryEnv.getTerms().push_back(term);
+ }
+ labels.inject(queryEnv.getProperties());
+ rankSetup.setFirstPhaseRank(featureName);
+ rankSetup.setIgnoreDefaultRankFeatures(true);
+ ASSERT_TRUE(rankSetup.compile());
+ rankProgram = rankSetup.create_first_phase_program();
+ rankProgram->setup(mdl, queryEnv);
+ }
+ feature_t getScore(uint32_t docId) {
+ rankProgram->run(docId);
+ return *Utils::getScoreFeature(*rankProgram);
+ }
+ void setScore(TermFieldHandle handle, uint32_t docId, feature_t score) {
+ rankProgram->match_data().resolveTermField(handle)->setRawScore(docId, score);
+ }
+ void setFooScore(uint32_t i, uint32_t docId, feature_t score) {
+ ASSERT_LESS(i, fooHandles.size());
+ setScore(fooHandles[i], docId, score);
+ }
+ void setBarScore(uint32_t i, uint32_t docId, feature_t score) {
+ ASSERT_LESS(i, barHandles.size());
+ setScore(barHandles[i], docId, score);
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("itemRawScore");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<ItemRawScoreBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that no features are dumped", ItemRawScoreBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+}
+
+TEST_FF("require that setup can be done on random label", ItemRawScoreBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(random_label)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "random_label")));
+}
+
+TEST_FF("require that no label gives 0.0 item raw score", NoLabel(), RankFixture(2, 2, f1)) {
+ EXPECT_EQUAL(0.0, f2.getScore(10));
+}
+
+TEST_FF("require that unrelated label gives 0.0 item raw score", SingleLabel("unrelated", 1), RankFixture(2, 2, f1)) {
+ EXPECT_EQUAL(0.0, f2.getScore(10));
+}
+
+TEST_FF("require that item raw score can be obtained", SingleLabel("label", 1), RankFixture(2, 2, f1)) {
+ f2.setFooScore(0, 10, 5.0);
+ EXPECT_EQUAL(5.0, f2.getScore(10));
+}
+
+TEST_FF("require that other raw scores are ignored", SingleLabel("label", 2), RankFixture(2, 2, f1)) {
+ f2.setFooScore(0, 10, 1.0);
+ f2.setFooScore(1, 10, 2.0);
+ f2.setBarScore(0, 10, 5.0);
+ f2.setBarScore(1, 10, 6.0);
+ EXPECT_EQUAL(2.0, f2.getScore(10));
+}
+
+TEST_FF("require that stale raw score is ignored", SingleLabel("label", 2), RankFixture(2, 2, f1)) {
+ f2.setFooScore(0, 10, 1.0);
+ f2.setFooScore(1, 5, 2.0);
+ EXPECT_EQUAL(0.0, f2.getScore(10));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/native_dot_product/.gitignore b/searchlib/src/tests/features/native_dot_product/.gitignore
new file mode 100644
index 00000000000..d95f15f4492
--- /dev/null
+++ b/searchlib/src/tests/features/native_dot_product/.gitignore
@@ -0,0 +1 @@
+searchlib_native_dot_product_test_app
diff --git a/searchlib/src/tests/features/native_dot_product/CMakeLists.txt b/searchlib/src/tests/features/native_dot_product/CMakeLists.txt
new file mode 100644
index 00000000000..2dad758c82d
--- /dev/null
+++ b/searchlib/src/tests/features/native_dot_product/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_native_dot_product_test_app
+ SOURCES
+ native_dot_product_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_native_dot_product_test_app COMMAND searchlib_native_dot_product_test_app)
diff --git a/searchlib/src/tests/features/native_dot_product/FILES b/searchlib/src/tests/features/native_dot_product/FILES
new file mode 100644
index 00000000000..ab007656448
--- /dev/null
+++ b/searchlib/src/tests/features/native_dot_product/FILES
@@ -0,0 +1 @@
+native_dot_product_test.cpp
diff --git a/searchlib/src/tests/features/native_dot_product/native_dot_product_test.cpp b/searchlib/src/tests/features/native_dot_product/native_dot_product_test.cpp
new file mode 100644
index 00000000000..3e3702cceec
--- /dev/null
+++ b/searchlib/src/tests/features/native_dot_product/native_dot_product_test.cpp
@@ -0,0 +1,191 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/native_dot_product_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+#include <vespa/searchlib/query/weight.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+const std::string featureName("nativeDotProduct(foo)");
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "foo");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "bar");
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ virtual void visitDumpFeature(const vespalib::string &) {
+ TEST_ERROR("no features should be dumped");
+ }
+ FeatureDumpFixture() : IDumpFeatureVisitor() {}
+};
+
+std::vector<uint32_t> vec() {
+ std::vector<uint32_t> ret;
+ return ret;
+}
+
+std::vector<uint32_t> vec(uint32_t w1) {
+ std::vector<uint32_t> ret;
+ ret.push_back(w1);
+ return ret;
+}
+
+std::vector<uint32_t> vec(uint32_t w1, uint32_t w2) {
+ std::vector<uint32_t> ret;
+ ret.push_back(w1);
+ ret.push_back(w2);
+ return ret;
+}
+
+std::vector<uint32_t> vec(uint32_t w1, uint32_t w2, uint32_t w3) {
+ std::vector<uint32_t> ret;
+ ret.push_back(w1);
+ ret.push_back(w2);
+ ret.push_back(w3);
+ return ret;
+}
+
+struct RankFixture : BlueprintFactoryFixture, IndexFixture {
+ QueryEnvironment queryEnv;
+ RankSetup rankSetup;
+ RankProgram::UP rankProgram;
+ MatchDataLayout mdl;
+ std::vector<TermFieldHandle> fooHandles;
+ std::vector<TermFieldHandle> barHandles;
+ RankFixture(const std::vector<uint32_t> &fooWeights,
+ const std::vector<uint32_t> &barWeights)
+ : queryEnv(&indexEnv), rankSetup(factory, indexEnv),
+ rankProgram(), mdl(), fooHandles(), barHandles()
+ {
+ for (size_t i = 0; i < fooWeights.size(); ++i) {
+ uint32_t fieldId = indexEnv.getFieldByName("foo")->id();
+ fooHandles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.addField(fieldId).setHandle(fooHandles.back());
+ term.setWeight(search::query::Weight(fooWeights[i]));
+ queryEnv.getTerms().push_back(term);
+ }
+ for (size_t i = 0; i < barWeights.size(); ++i) {
+ uint32_t fieldId = indexEnv.getFieldByName("bar")->id();
+ barHandles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.addField(fieldId).setHandle(barHandles.back());
+ term.setWeight(search::query::Weight(barWeights[i]));
+ queryEnv.getTerms().push_back(term);
+ }
+ rankSetup.setFirstPhaseRank(featureName);
+ rankSetup.setIgnoreDefaultRankFeatures(true);
+ ASSERT_TRUE(rankSetup.compile());
+ rankProgram = rankSetup.create_first_phase_program();
+ rankProgram->setup(mdl, queryEnv);
+ }
+ feature_t getScore(uint32_t docId) {
+ rankProgram->run(docId);
+ return *Utils::getScoreFeature(*rankProgram);
+ }
+ void setFooWeight(uint32_t i, uint32_t docId, int32_t index_weight) {
+ ASSERT_LESS(i, fooHandles.size());
+ TermFieldMatchDataPosition pos;
+ pos.setElementWeight(index_weight);
+ rankProgram->match_data().resolveTermField(fooHandles[i])->reset(docId);
+ rankProgram->match_data().resolveTermField(fooHandles[i])->appendPosition(pos);
+ }
+ void setBarWeight(uint32_t i, uint32_t docId, int32_t index_weight) {
+ ASSERT_LESS(i, barHandles.size());
+ TermFieldMatchDataPosition pos;
+ pos.setElementWeight(index_weight);
+ rankProgram->match_data().resolveTermField(barHandles[i])->reset(docId);
+ rankProgram->match_data().resolveTermField(barHandles[i])->appendPosition(pos);
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("nativeDotProduct");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<NativeDotProductBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that no features are dumped", NativeDotProductBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+}
+
+TEST_FF("require that setup can be done on index field", NativeDotProductBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "foo")));
+}
+
+TEST_FF("require that setup can be done on attribute field", NativeDotProductBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(bar)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "bar")));
+}
+
+TEST_FF("require that setup fails for unknown field", NativeDotProductBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(unknown)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(!((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "unknown")));
+}
+
+TEST_F("require that not searching a field will give it 0.0 dot product", RankFixture(vec(), vec(1, 2, 3))) {
+ EXPECT_EQUAL(0.0, f1.getScore(10));
+}
+
+TEST_F("require that dot product works for single match", RankFixture(vec(5), vec())) {
+ f1.setFooWeight(0, 10, 7);
+ EXPECT_EQUAL(35, f1.getScore(10));
+}
+
+TEST_F("require that dot product works for multiple matches", RankFixture(vec(1, 3, 5), vec())) {
+ f1.setFooWeight(0, 10, 2);
+ f1.setFooWeight(1, 10, 4);
+ f1.setFooWeight(2, 10, 6);
+ EXPECT_EQUAL(44, f1.getScore(10));
+}
+
+TEST_F("require that stale data is ignored", RankFixture(vec(1, 3, 5), vec())) {
+ f1.setFooWeight(0, 10, 2);
+ f1.setFooWeight(1, 9, 4);
+ f1.setFooWeight(2, 10, 6);
+ EXPECT_EQUAL(32, f1.getScore(10));
+}
+
+TEST_F("require that data from other fields is ignored", RankFixture(vec(1, 3), vec(5, 7))) {
+ f1.setFooWeight(0, 10, 2);
+ f1.setFooWeight(1, 10, 4);
+ f1.setBarWeight(0, 10, 6);
+ f1.setBarWeight(1, 10, 8);
+ EXPECT_EQUAL(14, f1.getScore(10));
+}
+
+TEST_F("require that negative weights in the index works", RankFixture(vec(1, 3), vec())) {
+ f1.setFooWeight(0, 10, 2);
+ f1.setFooWeight(1, 10, -4);
+ EXPECT_EQUAL(-10, f1.getScore(10));
+}
+
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/prod_features.cpp b/searchlib/src/tests/features/prod_features.cpp
new file mode 100644
index 00000000000..b0bac4b576d
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features.cpp
@@ -0,0 +1,1937 @@
+// 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("prod_features_test");
+
+#include "prod_features.h"
+#include <boost/tokenizer.hpp>
+#include <vespa/searchlib/attribute/attributeguard.h>
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/attributevector.hpp>
+#include <vespa/searchlib/attribute/extendableattributes.h>
+#include <vespa/searchlib/attribute/floatbase.h>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
+#include <vespa/searchlib/features/agefeature.h>
+#include <vespa/searchlib/features/array_parser.hpp>
+#include <vespa/searchlib/features/attributefeature.h>
+#include <vespa/searchlib/features/attributematchfeature.h>
+#include <vespa/searchlib/features/closenessfeature.h>
+#include <vespa/searchlib/features/distancefeature.h>
+#include <vespa/searchlib/features/dotproductfeature.h>
+#include <vespa/searchlib/features/fieldlengthfeature.h>
+#include <vespa/searchlib/features/fieldmatchfeature.h>
+#include <vespa/searchlib/features/fieldtermmatchfeature.h>
+#include <vespa/searchlib/features/firstphasefeature.h>
+#include <vespa/searchlib/features/foreachfeature.h>
+#include <vespa/searchlib/features/freshnessfeature.h>
+#include <vespa/searchlib/features/matchesfeature.h>
+#include <vespa/searchlib/features/matchfeature.h>
+#include <vespa/searchlib/features/nowfeature.h>
+#include <vespa/searchlib/features/queryfeature.h>
+#include <vespa/searchlib/features/querytermcountfeature.h>
+#include <vespa/searchlib/features/randomfeature.h>
+#include <vespa/searchlib/features/rankingexpressionfeature.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/features/termfeature.h>
+#include <vespa/searchlib/features/utils.h>
+#include <vespa/searchlib/features/valuefeature.h>
+#include <vespa/searchlib/features/weighted_set_parser.hpp>
+#include <vespa/searchlib/fef/featurenamebuilder.h>
+#include <vespa/searchlib/fef/indexproperties.h>
+#include <vespa/searchlib/fef/queryproperties.h>
+#include <vespa/searchlib/fef/test/plugin/setup.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+#include <vespa/searchlib/util/rand48.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/geo/zcurve.h>
+#include <vespa/vespalib/util/string_hash.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+using search::AttributeVector;
+using search::AttributeFactory;
+using search::IntegerAttribute;
+using search::FloatingPointAttribute;
+using search::StringAttribute;
+using search::WeightedSetStringExtAttribute;
+using search::attribute::WeightedEnumContent;
+
+typedef AttributeVector::SP AttributePtr;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+
+const double EPS = 10e-6;
+
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("prod_features_test");
+
+ // Configure factory with all known blueprints.
+ setup_fef_test_plugin(_factory);
+ setup_search_features(_factory);
+
+ // Test all features.
+ TEST_DO(testFramework()); TEST_FLUSH();
+ TEST_DO(testFtLib()); TEST_FLUSH();
+ TEST_DO(testAge()); TEST_FLUSH();
+ TEST_DO(testAttribute()); TEST_FLUSH();
+ TEST_DO(testAttributeMatch()); TEST_FLUSH();
+ TEST_DO(testCloseness()); TEST_FLUSH();
+ TEST_DO(testDistance()); TEST_FLUSH();
+ TEST_DO(testDistanceToPath()); TEST_FLUSH();
+ TEST_DO(testDotProduct()); TEST_FLUSH();
+ TEST_DO(testFieldLength()); TEST_FLUSH();
+ TEST_DO(testFieldMatch()); TEST_FLUSH();
+ TEST_DO(testFieldTermMatch()); TEST_FLUSH();
+ TEST_DO(testFirstPhase()); TEST_FLUSH();
+ TEST_DO(testForeach()); TEST_FLUSH();
+ TEST_DO(testFreshness()); TEST_FLUSH();
+ TEST_DO(testMatch()); TEST_FLUSH();
+ TEST_DO(testMatches()); TEST_FLUSH();
+ TEST_DO(testNow()); TEST_FLUSH();
+ TEST_DO(testQuery()); TEST_FLUSH();
+ TEST_DO(testQueryTermCount()); TEST_FLUSH();
+ TEST_DO(testRandom()); TEST_FLUSH();
+ TEST_DO(testRankingExpression()); TEST_FLUSH();
+ TEST_DO(testTerm()); TEST_FLUSH();
+ TEST_DO(testTermDistance()); TEST_FLUSH();
+ TEST_DO(testUtils()); TEST_FLUSH();
+
+ TEST_DONE();
+ return 0;
+}
+
+
+void
+Test::testFtLib()
+{
+ { // toQuery
+ FtQuery q = FtUtil::toQuery("a b!50 0.5:c!200%0.5 d%0.3 e!300 0.3:f ");
+ ASSERT_TRUE(q.size() == 6);
+ EXPECT_EQUAL(q[0].term, vespalib::string("a"));
+ EXPECT_EQUAL(q[0].termWeight.percent(), 100);
+ EXPECT_APPROX(q[0].connexity, 0.1f, EPS);
+ EXPECT_APPROX(q[0].significance, 0.1f, EPS);
+ EXPECT_EQUAL(q[1].term, vespalib::string("b"));
+ EXPECT_EQUAL(q[1].termWeight.percent(), 50);
+ EXPECT_APPROX(q[1].connexity, 0.1f, EPS);
+ EXPECT_APPROX(q[1].significance, 0.1f, EPS);
+ EXPECT_EQUAL(q[2].term, vespalib::string("c"));
+ EXPECT_EQUAL(q[2].termWeight.percent(), 200);
+ EXPECT_APPROX(q[2].connexity, 0.5f, EPS);
+ EXPECT_APPROX(q[2].significance, 0.5f, EPS);
+ EXPECT_EQUAL(q[3].term, vespalib::string("d"));
+ EXPECT_EQUAL(q[3].termWeight.percent(), 100);
+ EXPECT_APPROX(q[3].connexity, 0.1f, EPS);
+ EXPECT_APPROX(q[3].significance, 0.3f, EPS);
+ EXPECT_EQUAL(q[4].term, vespalib::string("e"));
+ EXPECT_EQUAL(q[4].termWeight.percent(), 300);
+ EXPECT_APPROX(q[4].connexity, 0.1f, EPS);
+ EXPECT_APPROX(q[4].significance, 0.1f, EPS);
+ EXPECT_EQUAL(q[5].term, vespalib::string("f"));
+ EXPECT_EQUAL(q[5].termWeight.percent(), 100);
+ EXPECT_APPROX(q[5].connexity, 0.3f, EPS);
+ EXPECT_APPROX(q[5].significance, 0.1f, EPS);
+ }
+ { // toRankResult
+ RankResult rr = toRankResult("foo", "a:0.5 b:-0.5 c:2 d:3 ");
+ std::vector<vespalib::string> keys = rr.getKeys();
+ ASSERT_TRUE(keys.size() == 4);
+ EXPECT_EQUAL(keys[0], vespalib::string("foo.a"));
+ EXPECT_EQUAL(keys[1], vespalib::string("foo.b"));
+ EXPECT_EQUAL(keys[2], vespalib::string("foo.c"));
+ EXPECT_EQUAL(keys[3], vespalib::string("foo.d"));
+ EXPECT_APPROX(rr.getScore("foo.a"), 0.5f, EPS);
+ EXPECT_APPROX(rr.getScore("foo.b"), -0.5f, EPS);
+ EXPECT_APPROX(rr.getScore("foo.c"), 2.0f, EPS);
+ EXPECT_APPROX(rr.getScore("foo.d"), 3.0f, EPS);
+ }
+}
+
+
+void
+Test::testAge()
+{
+ { // Test blueprint
+ FtIndexEnvironment idx_env;
+ idx_env.getBuilder()
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "datetime")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "datetime2");
+
+ AgeBlueprint pt;
+ EXPECT_TRUE(assertCreateInstance(pt, "age"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, idx_env, params);
+ FT_SETUP_OK(pt, idx_env, params.add("datetime"), in.add("now"), out.add("out"));
+ FT_SETUP_FAIL(pt, idx_env, params.add("datetime2"));
+
+ FT_DUMP_EMPTY(_factory, "age");
+ }
+
+ { // Test executor
+ assertAge(0, "doctime", 60, 120);
+ assertAge(60, "doctime", 180, 120);
+ assertAge(15000000000, "doctime", 20000000000, 5000000000);
+ }
+}
+
+void
+Test::assertAge(feature_t expAge, const vespalib::string & attr, uint64_t now, uint64_t docTime)
+{
+ vespalib::string feature = "age(" + attr + ")";
+ FtFeatureTest ft(_factory, feature);
+ setupForAgeTest(ft, docTime);
+ ft.getQueryEnv().getProperties().add(queryproperties::now::SystemTime::NAME,
+ vespalib::make_string("%" PRIu64, now));
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore(feature, expAge)));
+}
+
+void
+Test::setupForAgeTest(FtFeatureTest & ft, uint64_t docTime)
+{
+ AttributePtr doctime = AttributeFactory::createAttribute("doctime", AVC(AVBT::INT64, AVCT::SINGLE));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "doctime");
+ doctime->addReservedDoc();
+ doctime->addDocs(1);
+ ft.getIndexEnv().getAttributeManager().add(doctime);
+ (static_cast<IntegerAttribute *>(doctime.get()))->update(1, docTime);
+ doctime->commit();
+}
+
+void
+Test::testAttribute()
+{
+ AttributeBlueprint prototype;
+ {
+ FtIndexEnvironment idx_env;
+ idx_env.getBuilder()
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+
+ EXPECT_TRUE(assertCreateInstance(prototype, "attribute"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(prototype, idx_env, params); // expects 1 - 2 params
+
+ FT_SETUP_OK(prototype, idx_env, params.add("bar"), in,
+ out.add("value").add("weight").add("contains").add("count"));
+ FT_SETUP_OK(prototype, idx_env, params.add("0"), in, out);
+
+ FT_DUMP_EMPTY(_factory, "attribute");
+ }
+ { // single attributes
+ RankResult exp;
+ exp.addScore("attribute(sint)", 10).
+ addScore("attribute(sint,0)", 10).
+ addScore("attribute(sfloat)", 60.5f).
+ addScore("attribute(sstr)", (feature_t)vespalib::hash_code("foo")).
+ addScore("attribute(sint).count", 1).
+ addScore("attribute(sfloat).count", 1).
+ addScore("attribute(sstr).count", 1).
+ addScore("attribute(udefint)", search::attribute::getUndefined<feature_t>()).
+ addScore("attribute(udeffloat)", search::attribute::getUndefined<feature_t>()).
+ addScore("attribute(udefstr)", (feature_t)vespalib::hash_code(""));
+
+ FtFeatureTest ft(_factory, exp.getKeys());
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint").
+ addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sfloat").
+ addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sstr").
+ addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "udefint").
+ addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "udeffloat").
+ addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "udefstr");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ { // array attributes
+ RankResult exp;
+ exp.addScore("attribute(aint)", 0).
+ addScore("attribute(aint,0)", 20).
+ addScore("attribute(aint,1)", 30).
+ addScore("attribute(aint,2)", 0).
+ addScore("attribute(afloat,0)", 70.5f).
+ addScore("attribute(afloat,1)", 80.5f).
+ addScore("attribute(astr,0)", (feature_t)vespalib::hash_code("bar")).
+ addScore("attribute(astr,1)", (feature_t)vespalib::hash_code("baz")).
+ addScore("attribute(aint).count", 2).
+ addScore("attribute(aint,0).count", 0).
+ addScore("attribute(afloat).count", 2).
+ addScore("attribute(afloat,0).count", 0).
+ addScore("attribute(astr).count", 2).
+ addScore("attribute(astr,0).count", 0);
+
+ FtFeatureTest ft(_factory, exp.getKeys());
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint").
+ addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "afloat").
+ addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "astr");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ { // weighted set attributes
+ RankResult exp;
+ exp.addScore("attribute(wsint).value", 0).
+ addScore("attribute(wsint).weight", 0).
+ addScore("attribute(wsint).contains", 0).
+ addScore("attribute(wsint,100).value", 0).
+ addScore("attribute(wsint,100).weight", 0).
+ addScore("attribute(wsint,100).contains", 0).
+ addScore("attribute(wsint,40).value", 40).
+ addScore("attribute(wsint,40).weight", 10).
+ addScore("attribute(wsint,40).contains", 1).
+ addScore("attribute(wsint,50).value", 50).
+ addScore("attribute(wsint,50).weight", 20).
+ addScore("attribute(wsint,50).contains", 1).
+ addScore("attribute(wsfloat).value", 0).
+ addScore("attribute(wsfloat).weight", 0).
+ addScore("attribute(wsfloat).contains", 0).
+ addScore("attribute(wsfloat,1000.5).value", 0).
+ addScore("attribute(wsfloat,1000.5).weight", 0).
+ addScore("attribute(wsfloat,1000.5).contains", 0).
+ addScore("attribute(wsfloat,90.5).value", 90.5f).
+ addScore("attribute(wsfloat,90.5).weight", -30).
+ addScore("attribute(wsfloat,90.5).contains", 1).
+ addScore("attribute(wsfloat,100.5).value", 100.5f).
+ addScore("attribute(wsfloat,100.5).weight", -40).
+ addScore("attribute(wsfloat,100.5).contains", 1).
+ addScore("attribute(wsstr).value", 0).
+ addScore("attribute(wsstr).weight", 0).
+ addScore("attribute(wsstr).contains", 0).
+ addScore("attribute(wsstr,foo).value", 0).
+ addScore("attribute(wsstr,foo).weight", 0).
+ addScore("attribute(wsstr,foo).contains", 0).
+ addScore("attribute(wsstr,qux).value", (feature_t)vespalib::hash_code("qux")).
+ addScore("attribute(wsstr,qux).weight", 11).
+ addScore("attribute(wsstr,qux).contains", 1).
+ addScore("attribute(wsstr,quux).value", (feature_t)vespalib::hash_code("quux")).
+ addScore("attribute(wsstr,quux).weight", 12).
+ addScore("attribute(wsstr,quux).contains", 1).
+ addScore("attribute(wsint).count", 2).
+ addScore("attribute(wsint,40).count", 0).
+ addScore("attribute(wsfloat).count", 2).
+ addScore("attribute(wsfloat,90.5).count", 0).
+ addScore("attribute(wsstr).count", 2).
+ addScore("attribute(wsstr,qux).count", 0);
+
+ FtFeatureTest ft(_factory, exp.getKeys());
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsint").
+ addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsfloat").
+ addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsstr");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ { // unique only attribute
+ RankResult exp;
+ exp.addScore("attribute(unique).value", 0).
+ addScore("attribute(unique).weight", 0).
+ addScore("attribute(unique).contains", 0).
+ addScore("attribute(unique).count", 0);
+
+ FtFeatureTest ft(_factory, exp.getKeys());
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.setup());
+ //ASSERT_TRUE(ft.execute(exp));
+ }
+}
+
+
+void
+Test::setupForAttributeTest(FtFeatureTest &ft, bool setup_env)
+{
+ // setup an original attribute manager with attributes
+ std::vector<AttributePtr> avs;
+ avs.push_back(AttributeFactory::createAttribute("sint", AVC(AVBT::INT32, AVCT::SINGLE))); // 0
+ avs.push_back(AttributeFactory::createAttribute("aint", AVC(AVBT::INT32, AVCT::ARRAY))); // 1
+ avs.push_back(AttributeFactory::createAttribute("wsint", AVC(AVBT::INT32, AVCT::WSET))); // 2
+ avs.push_back(AttributeFactory::createAttribute("sfloat", AVC(AVBT::FLOAT, AVCT::SINGLE))); // 3
+ avs.push_back(AttributeFactory::createAttribute("afloat", AVC(AVBT::FLOAT, AVCT::ARRAY))); // 4
+ avs.push_back(AttributeFactory::createAttribute("wsfloat",AVC(AVBT::FLOAT, AVCT::WSET))); // 5
+ avs.push_back(AttributeFactory::createAttribute("sstr", AVC(AVBT::STRING, AVCT::SINGLE))); // 6
+ avs.push_back(AttributeFactory::createAttribute("astr", AVC(AVBT::STRING, AVCT::ARRAY))); // 7
+ avs.push_back(AttributeFactory::createAttribute("wsstr", AVC(AVBT::STRING, AVCT::WSET))); // 8
+ avs.push_back(AttributeFactory::createAttribute("udefint", AVC(AVBT::INT32, AVCT::SINGLE))); // 9
+ avs.push_back(AttributeFactory::createAttribute("udeffloat", AVC(AVBT::FLOAT, AVCT::SINGLE))); // 10
+ avs.push_back(AttributeFactory::createAttribute("udefstr", AVC(AVBT::STRING, AVCT::SINGLE))); // 11
+
+ // simulate a unique only attribute as specified in sd
+ AVC cfg(AVBT::INT32, AVCT::SINGLE);
+ cfg.setFastSearch(true);
+ avs.push_back(AttributeFactory::createAttribute("unique", cfg)); // 9
+
+ if (setup_env) {
+ // register attributes in index environment
+ ft.getIndexEnv().getBuilder()
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint")
+ .addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint")
+ .addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsint")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sfloat")
+ .addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "afloat")
+ .addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsfloat")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sstr")
+ .addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "astr")
+ .addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsstr")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "udefint")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "udeffloat")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "udefstr")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "unique");
+ }
+
+ for (uint32_t i = 0; i < avs.size(); ++i) {
+ avs[i]->addReservedDoc();
+ avs[i]->addDocs(1);
+ ft.getIndexEnv().getAttributeManager().add(avs[i]);
+ }
+
+ // integer attributes
+ (static_cast<IntegerAttribute *>(avs[0].get()))->update(1, 10);
+ (static_cast<IntegerAttribute *>(avs[1].get()))->append(1, 20, 0);
+ (static_cast<IntegerAttribute *>(avs[1].get()))->append(1, 30, 0);
+ (static_cast<IntegerAttribute *>(avs[2].get()))->append(1, 40, 10);
+ (static_cast<IntegerAttribute *>(avs[2].get()))->append(1, 50, 20);
+ (static_cast<IntegerAttribute *>(avs[9].get()))->update(1, search::attribute::getUndefined<int32_t>());
+ // feature_t attributes
+ (static_cast<FloatingPointAttribute *>(avs[3].get()))->update(1, 60.5f);
+ (static_cast<FloatingPointAttribute *>(avs[4].get()))->append(1, 70.5f, 0);
+ (static_cast<FloatingPointAttribute *>(avs[4].get()))->append(1, 80.5f, 0);
+ (static_cast<FloatingPointAttribute *>(avs[5].get()))->append(1, 90.5f, -30);
+ (static_cast<FloatingPointAttribute *>(avs[5].get()))->append(1, 100.5f, -40);
+ (static_cast<FloatingPointAttribute *>(avs[10].get()))->update(1, search::attribute::getUndefined<float>());
+ // string attributes
+ (static_cast<StringAttribute *>(avs[6].get()))->update(1, "foo");
+ (static_cast<StringAttribute *>(avs[7].get()))->append(1, "bar", 0);
+ (static_cast<StringAttribute *>(avs[7].get()))->append(1, "baz", 0);
+ (static_cast<StringAttribute *>(avs[8].get()))->append(1, "qux", 11);
+ (static_cast<StringAttribute *>(avs[8].get()))->append(1, "quux", 12);
+ (static_cast<StringAttribute *>(avs[11].get()))->update(1, "");
+
+ for (uint32_t i = 0; i < avs.size() - 1; ++i) { // do not commit the noupdate attribute
+ avs[i]->commit();
+ }
+
+ // save 'sint' and load it into 'unique' (only way to set a noupdate attribute)
+ ASSERT_TRUE(avs[0]->saveAs(avs[9]->getBaseFileName()));
+ ASSERT_TRUE(avs[9]->load());
+}
+
+void
+Test::testCloseness()
+{
+ { // Test blueprint.
+ ClosenessBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "closeness"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_OK(pt, params.add("name"), in.add("distance(name)"), out.add("out").add("logscale"));
+
+ FT_DUMP_EMPTY(_factory, "closeness");
+ }
+
+ { // Test executor.
+ assertCloseness(1, "pos", 0);
+ assertCloseness(0.8, "pos", 1802661);
+ assertCloseness(0, "pos", 9013306);
+ // use non-existing attribute -> default distance
+ assertCloseness(0, "no", 0);
+
+ // use non-default maxDistance
+ assertCloseness(1, "pos", 0, 100);
+ assertCloseness(0.5, "pos", 50, 100);
+ assertCloseness(0, "pos", 100, 100);
+ assertCloseness(0, "pos", 101, 100);
+
+ // test logscale using halfResponse (define that x = 10 should give 0.5 -> s = -10^2/(2*10 - 100) = 1.25 (scale distance))
+ assertCloseness(1, "pos", 0, 100, 10);
+ assertCloseness(0.5, "pos", 10, 100, 10);
+ assertCloseness(0, "pos", 100, 100, 10);
+ assertCloseness(0, "pos", 101, 100, 10);
+ }
+}
+
+void
+Test::assertCloseness(feature_t exp, const vespalib::string & attr, double distance, double maxDistance, double halfResponse)
+{
+ vespalib::string feature = "closeness(" + attr + ")";
+ FtFeatureTest ft(_factory, feature);
+ std::vector<std::pair<int32_t, int32_t> > positions;
+ int32_t x = 0;
+ positions.push_back(std::make_pair(x, x));
+ setupForDistanceTest(ft, "pos", positions, false);
+ ft.getQueryEnv().getLocation().setXPosition((int)distance);
+ ft.getQueryEnv().getLocation().setValid(true);
+ if (maxDistance > 0) {
+ ft.getIndexEnv().getProperties().add(feature + ".maxDistance",
+ vespalib::make_string("%u", (unsigned int)maxDistance));
+ }
+ if (halfResponse > 0) {
+ ft.getIndexEnv().getProperties().add(feature + ".halfResponse",
+ vespalib::make_string("%f", halfResponse));
+ feature.append(".logscale");
+ }
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore(feature, exp)));
+}
+
+void
+Test::testFieldLength()
+{
+ FieldLengthBlueprint pt;
+
+ { // Test blueprint.
+ EXPECT_TRUE(assertCreateInstance(pt, "fieldLength"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FtIndexEnvironment ie;
+ ie.getBuilder()
+ .addField(FieldType::INDEX, CollectionType::SINGLE, "foo")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar")
+ .addField(FieldType::INDEX, CollectionType::ARRAY, "afoo")
+ .addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wfoo");
+ FT_SETUP_FAIL(pt, params.add("qux")); // does not exists
+ FT_SETUP_FAIL(pt, params.clear().add("bar")); // not an index
+ FT_SETUP_FAIL(pt, params.clear().add("afoo")); // wrong collection type
+ FT_SETUP_FAIL(pt, params.clear().add("wfoo")); // wrong collection type
+ FT_SETUP_OK(pt, ie, params.clear().add("foo"), in, out.add("out"));
+
+ FT_DUMP_EMPTY(_factory, "fieldLength");
+ FT_DUMP_EMPTY(_factory, "fieldLength", ie);
+ }
+
+ { // Test executor.
+ for (uint32_t i = 0; i < 10; ++i) {
+ StringList features;
+ features.add("fieldLength(foo)").add("fieldLength(baz)");
+ FtFeatureTest ft(_factory, features);
+ ASSERT_TRUE(!ft.setup());
+
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo").
+ addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar").addField(FieldType::INDEX, CollectionType::SINGLE, "baz");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ search::fef::test::MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, i));
+ ASSERT_TRUE(mdb->setFieldLength("foo", i + 10));
+ ASSERT_TRUE(mdb->addOccurence("baz", 0, i));
+ ASSERT_TRUE(mdb->setFieldLength("baz", i + 20));
+ ASSERT_TRUE(mdb->apply(1));
+ ASSERT_TRUE(ft.execute(RankResult()
+ .addScore("fieldLength(foo)", (feature_t)i + 10)
+ .addScore("fieldLength(baz)", (feature_t)i + 20)));
+ }
+ }
+}
+
+
+void
+Test::assertFieldMatch(const vespalib::string & spec,
+ const vespalib::string & query,
+ const vespalib::string & field,
+ const fieldmatch::Params * params,
+ uint32_t totalTermWeight,
+ feature_t totalSignificance)
+{
+ LOG(info, "assertFieldMatch('%s', '%s', '%s', (%u))", spec.c_str(), query.c_str(), field.c_str(), totalTermWeight);
+
+ // Setup feature test.
+ vespalib::string feature = "fieldMatch(foo)";
+ FtFeatureTest ft(_factory, feature);
+
+ setupFieldMatch(ft, "foo", query, field, params, totalTermWeight, totalSignificance, 1);
+
+ // Execute and compare results.
+ RankResult rr = toRankResult(feature, spec);
+ rr.setEpsilon(1e-4); // same as java tests
+ ASSERT_TRUE(ft.execute(rr));
+}
+
+void
+Test::assertFieldMatch(const vespalib::string & spec,
+ const vespalib::string & query,
+ const vespalib::string & field,
+ uint32_t totalTermWeight)
+{
+ assertFieldMatch(spec, query, field, NULL, totalTermWeight);
+}
+
+void
+Test::assertFieldMatchTS(const vespalib::string & spec,
+ const vespalib::string & query,
+ const vespalib::string & field,
+ feature_t totalSignificance)
+{
+ assertFieldMatch(spec, query, field, NULL, 0, totalSignificance);
+}
+
+
+void
+Test::testFirstPhase()
+{
+ { // Test blueprint.
+ FirstPhaseBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "firstPhase"));
+
+ FtIndexEnvironment ie;
+ ie.getProperties().add(indexproperties::rank::FirstPhase::NAME, "random"); // override nativeRank dependency
+
+ StringList params, in, out;
+ FT_SETUP_OK(pt, ie, params, in.add("random"), out.add("score"));
+ FT_SETUP_FAIL(pt, params.add("foo"));
+ params.clear();
+
+ FT_DUMP(_factory, "firstPhase", ie, StringList().add("firstPhase"));
+ }
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "firstPhase");
+ ft.getIndexEnv().getProperties().add(indexproperties::rank::FirstPhase::NAME, "value(10)");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(10.0f));
+ }
+}
+
+void
+Test::testForeach()
+{
+ { // Test blueprint.
+ ForeachBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "foreach"));
+
+ StringList params, in, out;
+ out.add("value");
+ FT_SETUP_FAIL(pt, params);
+ // illegal dimension
+ FT_SETUP_FAIL(pt, params.add("squares").add("N").add("foo").add("true").add("sum"));
+ // illegal condition
+ FT_SETUP_FAIL(pt, params.clear().add("fields").add("N").add("foo").add("false").add("sum"));
+ // illegal operation
+ FT_SETUP_FAIL(pt, params.clear().add("fields").add("N").add("foo").add("true").add("dotproduct"));
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "baz");
+
+ // various dimensions
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo(N)").add("true").add("sum"),
+ in.clear().add("foo(0)").add("foo(1)").add("foo(2)").add("foo(3)").add("foo(4)").
+ add("foo(5)").add("foo(6)").add("foo(7)").add("foo(8)").add("foo(9)").
+ add("foo(10)").add("foo(11)").add("foo(12)").add("foo(13)").add("foo(14)").add("foo(15)"), out);
+ ie.getProperties().add("foreach.maxTerms", "1");
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("sum"),
+ in.clear().add("foo"), out);
+ FT_SETUP_OK(pt, ie, params.clear().add("fields").add("N").add("foo(N)").add("true").add("sum"),
+ in.clear().add("foo(foo)").add("foo(bar)"), out);
+ FT_SETUP_OK(pt, ie, params.clear().add("attributes").add("N").add("foo(N)").add("true").add("sum"),
+ in.clear().add("foo(baz)"), out);
+
+ // various conditions
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("sum"), in.clear().add("foo"), out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("<4").add("sum"), in, out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add(">4").add("sum"), in, out);
+ // various operations
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("sum"), in, out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("product"), in, out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("average"), in, out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("max"), in, out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("min"), in, out);
+ FT_SETUP_OK(pt, ie, params.clear().add("terms").add("N").add("foo").add("true").add("count"), in, out);
+
+ FT_DUMP_EMPTY(_factory, "foreach");
+ }
+ { // Test executor
+ // single loop
+ assertForeachOperation( 16.5, "true", "sum");
+ assertForeachOperation(-2106, "true", "product");
+ assertForeachOperation( 3.3, "true", "average");
+ assertForeachOperation( 8, "true", "max");
+ assertForeachOperation( -4.5, "true", "min");
+ assertForeachOperation( 5, "true", "count");
+
+ assertForeachOperation(3, "\">4\"", "count");
+ assertForeachOperation(2, "\">4.5\"", "count");
+ assertForeachOperation(2, "\"<4\"", "count");
+ assertForeachOperation(2, "\"<4.5\"", "count");
+ assertForeachOperation(4, "\">0\"", "count");
+ assertForeachOperation(1, "\"<0\"", "count");
+ assertForeachOperation(4, "\">-4.5\"", "count");
+ assertForeachOperation(1, "\"<-4.4\"", "count");
+
+ { // average without any values
+ FtFeatureTest ft(_factory, "foreach(fields,N,value(N),true,average)");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(0));
+ }
+
+ { // double loop
+ vespalib::string feature =
+ "foreach(fields,N,foreach(attributes,M,rankingExpression(\"value(N)+value(M)\"),true,product),true,sum)";
+ LOG(info, "double loop feature: '%s'", feature.c_str());
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getProperties().add("foreach.maxTerms", "1");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "1");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "2");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "3");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "4");
+ // ((1 + 3) * (1 + 4)) + ((2 + 3) * (2 + 4)) = 4 * 5 + 5 * 6 = 20 + 30 = 50
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(50));
+ ASSERT_TRUE(ft.execute(50)); // check that reset works
+ }
+ }
+}
+
+void
+Test::assertForeachOperation(feature_t exp, const vespalib::string & cond, const vespalib::string & op)
+{
+ vespalib::string feature = "foreach(fields,N,value(N)," + cond + "," + op + ")";
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "4.5");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "2");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "8");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "6.5");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "-4.5");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(exp));
+ ASSERT_TRUE(ft.execute(exp)); // check that reset works
+}
+
+
+void
+Test::testFreshness()
+{
+ { // Test blueprint.
+ FtIndexEnvironment idx_env;
+ idx_env.getBuilder()
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "name");
+
+ FreshnessBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "freshness"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, idx_env, params);
+ FT_SETUP_OK(pt, idx_env, params.add("name"), in.add("age(name)"), out.add("out").add("logscale"));
+
+ FT_DUMP_EMPTY(_factory, "freshness");
+ }
+
+ { // Test executor.
+ assertFreshness(1, "doctime", 0);
+ assertFreshness(0.5, "doctime", 3*15*24*60*60);
+ assertFreshness(0, "doctime", 3*30*24*60*60);
+ // use non-default maxAge
+ assertFreshness(1, "doctime", 0, 120);
+ assertFreshness(0.75, "doctime", 30, 120);
+ assertFreshness(0.5, "doctime", 60, 120);
+ assertFreshness(0, "doctime", 120, 120);
+ assertFreshness(0, "doctime", 121, 120);
+
+ // test logscale
+ assertFreshness(1, "doctime", 0, 0, 0, true);
+ assertFreshness(0.5, "doctime", 7*24*60*60, 0, 0, true);
+ assertFreshness(0, "doctime", 3*30*24*60*60, 0, 0, true);
+ // use non-default maxAge & halfResponse
+ assertFreshness(1, "doctime", 0, 120, 30, true);
+ assertFreshness(0.5, "doctime", 30, 120, 30, true); // half response after 30 secs
+ assertFreshness(0, "doctime", 120, 120, 30, true);
+ assertFreshness(0, "doctime", 121, 120, 30, true);
+ // test invalid half response
+ assertFreshness(0.5, "doctime", 1, 120, 0.5, true); // half response is set to 1
+ assertFreshness(0.5, "doctime", 59, 120, 70, true); // half response is set to 120/2 - 1
+ }
+}
+
+void
+Test::assertFreshness(feature_t expFreshness, const vespalib::string & attr, uint32_t age, uint32_t maxAge, double halfResponse, bool logScale)
+{
+ vespalib::string feature = "freshness(" + attr + ")";
+ FtFeatureTest ft(_factory, feature);
+ setupForAgeTest(ft, 60); // time = 60
+ if (maxAge > 0) {
+ ft.getIndexEnv().getProperties().add("freshness(" + attr + ").maxAge",
+ vespalib::make_string("%u", maxAge));
+ }
+ if (halfResponse > 0) {
+ ft.getIndexEnv().getProperties().add("freshness(" + attr + ").halfResponse",
+ vespalib::make_string("%f", halfResponse));
+ }
+ if (logScale) {
+ feature.append(".logscale");
+ }
+ ft.getQueryEnv().getProperties().add(queryproperties::now::SystemTime::NAME,
+ vespalib::make_string("%u", age + 60)); // now = age + 60
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore(feature, expFreshness).setEpsilon(EPS)));
+}
+
+void
+Test::testDistance()
+{
+ { // Test blueprint.
+ DistanceBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "distance"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_OK(pt, params.add("pos"), in, out.add("out"));
+
+ FT_DUMP_EMPTY(_factory, "distance");
+ }
+
+ { // Test executor.
+
+ { // test 2D single location (zcurve)
+ assert2DZDistance(static_cast<feature_t>(sqrt(650.0f)), "5:-5", 10, 20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(250.0f)), "5:-5", 10, -20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(450.0f)), "5:-5", -10, -20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(850.0f)), "5:-5", -10, 20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(250.0f)), "5:-5", 15, -20, 0x80000000); // 2^31
+ }
+
+ { // test 2D multi location (zcurve)
+ vespalib::string positions = "5:-5,35:0,5:40,35:-40";
+ assert2DZDistance(static_cast<feature_t>(sqrt(425.0f)), positions, 10, 20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(250.0f)), positions, 10, -20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(450.0f)), positions, -10, -20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(625.0f)), positions, -10, 20);
+ assert2DZDistance(static_cast<feature_t>(sqrt(250.0f)), positions, 15, -20, 0x80000000); // 2^31
+ assert2DZDistance(static_cast<feature_t>(sqrt(425.0f)), positions, 45, -20, 0x80000000); // 2^31
+ }
+
+ { // test default distance
+ { // non-existing attribute
+ FtFeatureTest ft(_factory, "distance(pos)");
+ ft.getQueryEnv().getLocation().setValid(true);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
+ }
+ { // wrong attribute type (float)
+ FtFeatureTest ft(_factory, "distance(pos)");
+ AttributePtr pos = AttributeFactory::createAttribute("pos", AVC(AVBT::FLOAT, AVCT::SINGLE));
+ pos->commit();
+ ft.getIndexEnv().getAttributeManager().add(pos);
+ ft.getQueryEnv().getLocation().setValid(true);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
+ }
+ { // wrong attribute type (string)
+ FtFeatureTest ft(_factory, "distance(pos)");
+ AttributePtr pos = AttributeFactory::createAttribute("pos", AVC(AVBT::STRING, AVCT::SINGLE));
+ pos->commit();
+ ft.getIndexEnv().getAttributeManager().add(pos);
+ ft.getQueryEnv().getLocation().setValid(true);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
+ }
+ { // wrong attribute collection type (weighted set)
+ FtFeatureTest ft(_factory, "distance(pos)");
+ AttributePtr pos = AttributeFactory::createAttribute("pos", AVC(AVBT::INT64, AVCT::WSET));
+ pos->commit();
+ ft.getIndexEnv().getAttributeManager().add(pos);
+ ft.getQueryEnv().getLocation().setValid(true);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("distance(pos)", 6400000000.0)));
+ }
+ }
+ }
+}
+
+void
+Test::setupForDistanceTest(FtFeatureTest &ft, const vespalib::string & attrName,
+ const std::vector<std::pair<int32_t, int32_t> > & positions, bool zcurve)
+{
+ AttributePtr pos = AttributeFactory::createAttribute(attrName, AVC(AVBT::INT64, AVCT::ARRAY));
+
+ pos->addReservedDoc();
+ pos->addDocs(1);
+ ft.getIndexEnv().getAttributeManager().add(pos);
+
+ IntegerAttribute * ia = static_cast<IntegerAttribute *>(pos.get());
+ for (uint32_t i = 0; i < positions.size(); ++i) {
+ if (zcurve) {
+ ia->append(1, vespalib::geo::ZCurve::encode(positions[i].first, positions[i].second), 0);
+ } else {
+ ia->append(1, positions[i].first, 0);
+ }
+ }
+
+ pos->commit();
+}
+
+void
+Test::assert2DZDistance(feature_t exp, const vespalib::string & positions,
+ int32_t xquery, int32_t yquery, uint32_t xAspect)
+{
+ LOG(info, "assert2DZDistance(%g, %s, %d, %d, %u)", exp, positions.c_str(), xquery, yquery, xAspect);
+ FtFeatureTest ft(_factory, "distance(pos)");
+ std::vector<vespalib::string> ta = FtUtil::tokenize(positions, ",");
+ std::vector<std::pair<int32_t, int32_t> > pos;
+ for (uint32_t i = 0; i < ta.size(); ++i) {
+ std::vector<vespalib::string> tb = FtUtil::tokenize(ta[i], ":");
+ int32_t x = util::strToNum<int32_t>(tb[0]);
+ int32_t y = util::strToNum<int32_t>(tb[1]);
+ pos.push_back(std::make_pair(x, y));
+ }
+ setupForDistanceTest(ft, "pos", pos, true);
+ ft.getQueryEnv().getLocation().setXPosition(xquery);
+ ft.getQueryEnv().getLocation().setYPosition(yquery);
+ ft.getQueryEnv().getLocation().setXAspect(xAspect);
+ ft.getQueryEnv().getLocation().setValid(true);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().setEpsilon(1e-4).
+ addScore("distance(pos)", exp)));
+}
+
+void
+Test::testDistanceToPath()
+{
+ {
+ // Test blueprint.
+ DistanceToPathBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "distanceToPath"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_OK(pt, params.add("pos"), in, out.add("distance").add("traveled").add("product"));
+ FT_SETUP_FAIL(pt, params.add("foo"));
+
+ FT_DUMP_EMPTY(_factory, "distanceToPath");
+ }
+
+ {
+ // Test executor.
+ std::vector<std::pair<int32_t, int32_t> > pos;
+ pos.push_back(std::make_pair(0, 0));
+
+ // invalid path
+ assertDistanceToPath(pos, "a");
+ assertDistanceToPath(pos, "(");
+ assertDistanceToPath(pos, "(a");
+ assertDistanceToPath(pos, "(a)");
+ assertDistanceToPath(pos, "(-1)");
+ assertDistanceToPath(pos, "(-1,1)");
+ assertDistanceToPath(pos, "(-1,1,1)");
+ assertDistanceToPath(pos, "(-1 1 1 1)");
+
+ // path on either side of document
+ assertDistanceToPath(pos, "(-1,1,1,1)", 1, 0.5, 2);
+ assertDistanceToPath(pos, "(-1,-1,1,-1)", 1, 0.5, -2);
+
+ // zero length path
+ assertDistanceToPath(pos, "(0,0,0,0)", 0, 0);
+ assertDistanceToPath(pos, "(0,0,0,0,0,0)", 0, 0);
+ assertDistanceToPath(pos, "(0,1,0,1)", 1, 0);
+ assertDistanceToPath(pos, "(0,1,0,1,0,1)", 1, 0);
+
+ // path crosses document
+ assertDistanceToPath(pos, "(-1,1,1,-1)", 0, 0.5);
+ assertDistanceToPath(pos, "(-2,2,2,-2)", 0, 0.5);
+ assertDistanceToPath(pos, "(-1,1,3,-3)", 0, 0.25);
+
+ // intersection outside segments
+ assertDistanceToPath(pos, "(1,0,2,0)", 1, 0); // before
+ assertDistanceToPath(pos, "(0,1,0,2)", 1, 0);
+ assertDistanceToPath(pos, "(-2,0,-1,0)", 1, 1); // after
+ assertDistanceToPath(pos, "(0,-2,0,-1)", 1, 1);
+
+ // various paths
+ assertDistanceToPath(pos, "(-3,1,2,1,2,-2,-2,-2)", 1, 0.25, 5);
+ assertDistanceToPath(pos, "(-3,2,2,2,2,-1,0,-1)", 1, 1, 2);
+
+ // multiple document locations
+ pos.push_back(std::make_pair(0, 1));
+ assertDistanceToPath(pos, "(-1,1,1,1)", 0, 0.5);
+ assertDistanceToPath(pos, "(-2,-1,-1,1)", 1, 1, 2);
+ assertDistanceToPath(pos, "(-1,0.25,1,0.25)", 0.25, 0.5, 0.5);
+
+ {
+ // Test defaults.
+ RankResult res;
+ res.addScore("distanceToPath(pos).distance", DistanceExecutor::DEFAULT_DISTANCE);
+ res.addScore("distanceToPath(pos).traveled", 1);
+ {
+ // Non-existing attribute.
+ FtFeatureTest ft(_factory, "distanceToPath(pos)");
+ ft.getQueryEnv().getProperties().add("distanceToPath(pos).path", "0 0 1 1");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(res));
+ }
+ {
+ // Wrong attribute type (float).
+ FtFeatureTest ft(_factory, "distanceToPath(pos)");
+ AttributePtr att = AttributeFactory::createAttribute("pos", AVC(AVBT::FLOAT, AVCT::SINGLE));
+ att->commit();
+ ft.getIndexEnv().getAttributeManager().add(att);
+ ft.getQueryEnv().getProperties().add("distanceToPath(pos).path", "0 0 1 1");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(res));
+ }
+ {
+ // Wrong attribute type (string).
+ FtFeatureTest ft(_factory, "distanceToPath(pos)");
+ AttributePtr att = AttributeFactory::createAttribute("pos", AVC(AVBT::STRING, AVCT::SINGLE));
+ att->commit();
+ ft.getIndexEnv().getAttributeManager().add(att);
+ ft.getQueryEnv().getProperties().add("distanceToPath(pos).path", "0 0 1 1");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(res));
+ }
+ {
+ // Wrong attribute collection type (weighted set).
+ FtFeatureTest ft(_factory, "distanceToPath(pos)");
+ AttributePtr att = AttributeFactory::createAttribute("pos", AVC(AVBT::INT64, AVCT::WSET));
+ att->commit();
+ ft.getIndexEnv().getAttributeManager().add(att);
+ ft.getQueryEnv().getProperties().add("distanceToPath(pos).path", "0 0 1 1");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(res));
+ }
+ }
+ }
+}
+
+void
+Test::assertDistanceToPath(const std::vector<std::pair<int32_t, int32_t> > pos,
+ const vespalib::string &path, feature_t distance, feature_t traveled, feature_t product)
+{
+ LOG(info, "Testing distance to path '%s' with %zd document locations.", path.c_str(), pos.size());
+
+ FtFeatureTest ft(_factory, "distanceToPath(pos)");
+ setupForDistanceTest(ft, "pos", pos, true);
+
+ ft.getQueryEnv().getProperties().add("distanceToPath(pos).path", path);
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult()
+ .addScore("distanceToPath(pos).distance", distance)
+ .addScore("distanceToPath(pos).traveled", traveled)
+ .addScore("distanceToPath(pos).product", product)));
+}
+
+void
+Test::setupForDocumentTest(FtFeatureTest &ft, const vespalib::string & attrName, const vespalib::string & docType)
+{
+ AttributePtr type = AttributeFactory::createAttribute(attrName, AVC(AVBT::STRING, AVCT::SINGLE));
+
+ type->addReservedDoc();
+ type->addDocs(1);
+ ft.getIndexEnv().getAttributeManager().add(type);
+
+ (static_cast<StringAttribute *>(type.get()))->update(1, docType);
+ type->commit();
+}
+
+void
+Test::testDotProduct()
+{
+ { // Test blueprint.
+ FtIndexEnvironment idx_env;
+ idx_env.getBuilder()
+ .addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "attribute");
+
+ DotProductBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "dotProduct"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, idx_env, params);
+ FT_SETUP_OK(pt, idx_env, params.add("attribute").add("vector"), in, out.add("scalar"));
+
+ FT_DUMP_EMPTY(_factory, "dotProduct");
+ }
+
+ { // Test vector parser
+ { // string enum vector
+ FtFeatureTest ft(_factory, "value(0)");
+ setupForDotProductTest(ft);
+ search::AttributeGuard::UP ag(ft.getIndexEnv().getAttributeManager().getAttribute("wsstr"));
+ const search::attribute::IAttributeVector * sv = ag->operator->();
+ EXPECT_TRUE(sv->hasEnum());
+ search::attribute::EnumHandle e;
+ {
+ dotproduct::wset::EnumVector out(sv);
+ WeightedSetParser::parse("", out);
+ EXPECT_EQUAL(out.getVector().size(), 0u);
+ WeightedSetParser::parse("()", out);
+ EXPECT_EQUAL(out.getVector().size(), 0u);
+ WeightedSetParser::parse("(a;1)", out);
+ EXPECT_EQUAL(out.getVector().size(), 0u);
+ WeightedSetParser::parse("(a:1)", out);
+ EXPECT_EQUAL(out.getVector().size(), 1u);
+ EXPECT_TRUE(sv->findEnum("a", e));
+ EXPECT_EQUAL(out.getVector()[0].first, e);
+ EXPECT_EQUAL(out.getVector()[0].second, 1.0);
+ }
+ std::vector<vespalib::string> v = {"(b:2.5,c:-3.5)", "{b:2.5,c:-3.5}"};
+ for(const vespalib::string & s : v) {
+ dotproduct::wset::EnumVector out(sv);
+ WeightedSetParser::parse(s, out);
+ EXPECT_EQUAL(out.getVector().size(), 2u);
+ EXPECT_TRUE(sv->findEnum("b", e));
+ EXPECT_EQUAL(out.getVector()[0].first, e);
+ EXPECT_EQUAL(out.getVector()[0].second, 2.5);
+ EXPECT_TRUE(sv->findEnum("c", e));
+ EXPECT_EQUAL(out.getVector()[1].first, e);
+ EXPECT_EQUAL(out.getVector()[1].second, -3.5);
+ }
+ { // test funky syntax
+ dotproduct::wset::EnumVector out(sv);
+ WeightedSetParser::parse("( a: 1, b:2 ,c: , :3)", out);
+ EXPECT_EQUAL(out.getVector().size(), 3u);
+ EXPECT_TRUE(sv->findEnum("a", e));
+ EXPECT_EQUAL(out.getVector()[0].first, e);
+ EXPECT_EQUAL(out.getVector()[0].second, 1);
+ EXPECT_TRUE(sv->findEnum("b", e));
+ EXPECT_EQUAL(out.getVector()[1].first, e);
+ EXPECT_EQUAL(out.getVector()[1].second, 2);
+ EXPECT_TRUE(sv->findEnum("c", e));
+ EXPECT_EQUAL(out.getVector()[2].first, e);
+ EXPECT_EQUAL(out.getVector()[2].second, 0);
+ }
+ { // strings not in attribute vector
+ dotproduct::wset::EnumVector out(sv);
+ WeightedSetParser::parse("(not:1)", out);
+ EXPECT_EQUAL(out.getVector().size(), 0u);
+ }
+ }
+ { // string vector
+ dotproduct::wset::StringVector out;
+ WeightedSetParser::parse("(b:2.5,c:-3.5)", out);
+ EXPECT_EQUAL(out.getVector().size(), 2u);
+ EXPECT_EQUAL(out.getVector()[0].first, "b");
+ EXPECT_EQUAL(out.getVector()[0].second, 2.5);
+ EXPECT_EQUAL(out.getVector()[1].first, "c");
+ EXPECT_EQUAL(out.getVector()[1].second, -3.5);
+ }
+ { // integer vector
+ dotproduct::wset::IntegerVector out;
+ WeightedSetParser::parse("(20:2.5,30:-3.5)", out);
+ EXPECT_EQUAL(out.getVector().size(), 2u);
+ EXPECT_EQUAL(out.getVector()[0].first, 20);
+ EXPECT_EQUAL(out.getVector()[0].second, 2.5);
+ EXPECT_EQUAL(out.getVector()[1].first, 30);
+ EXPECT_EQUAL(out.getVector()[1].second, -3.5);
+ }
+ }
+ { // Array parser
+ std::vector<vespalib::string> v = {"(0:2,7:-3,1:-3)", "{0:2,7:-3,1:-3}", "[2 -3 0 0 0 0 0 -3]"};
+ for(const vespalib::string & s : v) {
+ std::vector<int32_t> out;
+ ArrayParser::parse(s, out);
+ EXPECT_EQUAL(8u, out.size());
+ EXPECT_EQUAL(2, out[0]);
+ EXPECT_EQUAL(-3, out[1]);
+ EXPECT_EQUAL(0, out[2]);
+ EXPECT_EQUAL(0, out[3]);
+ EXPECT_EQUAL(0, out[4]);
+ EXPECT_EQUAL(0, out[5]);
+ EXPECT_EQUAL(0, out[6]);
+ EXPECT_EQUAL(-3, out[7]);
+ }
+ }
+ {
+ vespalib::string s = "[[1:3]]";
+ std::vector<int32_t> out;
+ ArrayParser::parse(s, out);
+ EXPECT_EQUAL(0u, out.size());
+ }
+
+ { // Test executor.
+ { // string enum attribute
+ // docId = 1
+ assertDotProduct(0, "()");
+ assertDotProduct(0, "(f:5)");
+ assertDotProduct(0, "(f:5,g:5)");
+ assertDotProduct(-5, "(a:-5)");
+ assertDotProduct(25, "(e:5)");
+ assertDotProduct(-5.5, "(a:-5.5)");
+ assertDotProduct(27.5, "(e:5.5)");
+ assertDotProduct(55, "(a:1,b:2,c:3,d:4,e:5)");
+ assertDotProduct(20, "(b:10,b:15)");
+ // docId = 2
+ assertDotProduct(0, "()", 2);
+ assertDotProduct(0, "(a:1,b:2,c:3,d:4,e:5)", 2);
+ }
+ { // string attribute
+ assertDotProduct(0, "(f:5,g:5)", 1, "wsextstr");
+ assertDotProduct(550, "(a:1,b:2,c:3,d:4,e:5)", 1, "wsextstr");
+ }
+ { // integer attribute
+ assertDotProduct(0, "()", 1, "wsint");
+ assertDotProduct(0, "(6:5,7:5)", 1, "wsint");
+ assertDotProduct(55, "(1:1,2:2,3:3,4:4,5:5)", 1, "wsint");
+ }
+ std::vector<const char *> attributes = {"arrint", "arrfloat", "arrint_fast", "arrfloat_fast"};
+ for (const char * name : attributes) {
+ assertDotProduct(0, "()", 1, name);
+ assertDotProduct(0, "(6:5,7:5)", 1, name);
+ assertDotProduct(55, "(0:1,1:2,2:3,3:4,4:5)", 1, name);
+ assertDotProduct(55, "[1 2 3 4 5]", 1, name);
+ assertDotProduct(41, "{3:4,4:5}", 1, name);
+ }
+ { // float array attribute
+ assertDotProduct(55, "[1.0 2.0 3.0 4.0 5.0]", 1, "arrfloat");
+ assertDotProduct(41, "{3:4,4:5.0}", 1, "arrfloat");
+ }
+ { // Sparse float array attribute.
+ assertDotProduct(17, "(0:1,3:4,50:97)", 1, "arrfloat");
+ }
+
+ assertDotProduct(0, "(0:1,3:4,50:97)", 1, "sint"); // attribute of the wrong type
+ assertDotProduct(17, "(0:1,3:4,50:97)", 1, "sint", "arrfloat"); // attribute override
+ assertDotProduct(0, "(0:1,3:4,50:97)", 1, "sint", "arrfloat_non_existing"); // incorrect attribute override
+ }
+ { // Test that correct executor is created
+ FtFeatureTest ft(_factory, "value(0)");
+ setupForDotProductTest(ft);
+ ft.getQueryEnv().getProperties().add("dotProduct.vector", "(a:1)");
+ ParameterList params;
+ params.push_back(Parameter(ParameterType::ATTRIBUTE, "wsstr"));
+ params.push_back(Parameter(ParameterType::STRING, "vector"));
+ DotProductBlueprint bp;
+ DummyDependencyHandler deps(bp);
+ EXPECT_TRUE(bp.setup(ft.getIndexEnv(), params));
+ FeatureExecutor::LP exc = bp.createExecutor(ft.getQueryEnv());
+ // check that we have the optimized enum version
+ dotproduct::wset::DotProductExecutor<dotproduct::wset::EnumVector, WeightedEnumContent> * myExc =
+ dynamic_cast<dotproduct::wset::DotProductExecutor<dotproduct::wset::EnumVector, WeightedEnumContent> *>(exc.get());
+ EXPECT_TRUE(myExc != nullptr);
+ EXPECT_EQUAL(1u, deps.output.size());
+ }
+}
+
+void
+Test::assertDotProduct(feature_t exp, const vespalib::string & vector, uint32_t docId,
+ const vespalib::string & attribute, const vespalib::string & attributeOverride)
+{
+ RankResult rr;
+ rr.addScore("dotProduct(" + attribute + ",vector)", exp);
+ FtFeatureTest ft(_factory, rr.getKeys());
+ setupForDotProductTest(ft);
+ ft.getQueryEnv().getProperties().add("dotProduct.vector", vector);
+ if ( ! attributeOverride.empty() ) {
+ ft.getQueryEnv().getProperties().add("dotProduct." + attribute + ".override.name", attributeOverride);
+ }
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(rr, docId));
+}
+
+void
+Test::setupForDotProductTest(FtFeatureTest & ft)
+{
+ struct Config {
+ const char * name;
+ AVBT dataType;
+ AVCT collectionType;
+ bool fastSearch;
+ };
+ std::vector<Config> cfgList = { {"wsint", AVBT::INT32, AVCT::WSET, false},
+ {"arrint", AVBT::INT32, AVCT::ARRAY, false},
+ {"arrfloat", AVBT::FLOAT, AVCT::ARRAY, false},
+ {"arrint_fast", AVBT::INT32, AVCT::ARRAY, true},
+ {"arrfloat_fast", AVBT::FLOAT, AVCT::ARRAY, true}
+ };
+ AttributePtr a = AttributeFactory::createAttribute("wsstr", AVC(AVBT::STRING, AVCT::WSET));
+ AttributePtr c = AttributeFactory::createAttribute("sint", AVC(AVBT::INT32, AVCT::SINGLE));
+ AttributePtr d(new search::WeightedSetStringExtAttribute("wsextstr"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsstr");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsextstr");
+ for (const Config & cfg : cfgList) {
+ AttributePtr baf = AttributeFactory::createAttribute(cfg.name, AVC(cfg.dataType,
+ cfg.collectionType,
+ cfg.fastSearch));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE,
+ cfg.collectionType==AVCT::ARRAY
+ ? CollectionType::ARRAY
+ : CollectionType::WEIGHTEDSET,
+ cfg.name);
+ baf->addReservedDoc();
+ baf->addDocs(2);
+ ft.getIndexEnv().getAttributeManager().add(baf);
+ for (size_t i(1); i < 6; i++) {
+ IntegerAttribute * ia = dynamic_cast<IntegerAttribute *>(baf.get());
+ if (ia) {
+ ia->append(1, i, i);
+ } else {
+ FloatingPointAttribute * fa = dynamic_cast<FloatingPointAttribute *>(baf.get());
+ fa->append(1, i, i);
+ }
+ }
+ baf->commit();
+ }
+
+ a->addReservedDoc();
+ c->addReservedDoc();
+ a->addDocs(2);
+ c->addDocs(2);
+ ft.getIndexEnv().getAttributeManager().add(a);
+ ft.getIndexEnv().getAttributeManager().add(c);
+ ft.getIndexEnv().getAttributeManager().add(d);
+
+ StringAttribute * sa = static_cast<StringAttribute *>(a.get());
+ sa->append(1, "a", 1);
+ sa->append(1, "b", 2);
+ sa->append(1, "c", 3);
+ sa->append(1, "d", 4);
+ sa->append(1, "e", 5);
+
+ WeightedSetStringExtAttribute * ea = static_cast<WeightedSetStringExtAttribute *>(d.get());
+ EXPECT_TRUE(!ea->hasEnum());
+ uint32_t docId;
+ ea->addDoc(docId); // reserved doc
+ ea->addDoc(docId);
+ ea->add("a", 10);
+ ea->add("b", 20);
+ ea->add("c", 30);
+ ea->add("d", 40);
+ ea->add("e", 50);
+ ea->addDoc(docId);
+
+ a->commit();
+ c->commit();
+}
+
+void
+Test::testNow()
+{
+ {
+ // Test blueprint.
+ NowBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "now"));
+
+ StringList params, in, out;
+ FT_SETUP_OK (pt, params, in, out.add("out"));
+ FT_SETUP_FAIL(pt, params.add("foo"));
+
+ FT_DUMP(_factory, "now", StringList().add("now"));
+ }
+
+ {
+ // Test executor.
+ FtFeatureTest ft(_factory, "now");
+ ASSERT_TRUE(ft.setup());
+
+ RankResult res;
+ res.addScore("now", 0.0f);
+ for (uint32_t i = 1; i <= 10; ++i) {
+ feature_t last = res.getScore("now");
+ res.clear();
+ ASSERT_TRUE(ft.executeOnly(res, i));
+ ASSERT_TRUE(last <= res.getScore("now"));
+ }
+ }
+
+ {
+ // Test executor with ms resolution
+ FtFeatureTest ft(_factory, "now");
+ ft.getQueryEnv().getProperties().add("vespa.now", "15000000000");
+ ASSERT_TRUE(ft.setup());
+
+ RankResult res;
+ ASSERT_TRUE(ft.executeOnly(res, 0));
+ feature_t now = 15000000000;
+ ASSERT_EQUAL(now, res.getScore("now"));
+ }
+}
+
+
+void
+Test::testMatch()
+{
+ { // Test blueprint.
+ MatchBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "match"));
+
+ FtFeatureTest ft(_factory, "");
+ setupForAttributeTest(ft);
+
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "bar");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "baz");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsint");
+
+ FtIndexEnvironment idx_env;
+ idx_env.getBuilder()
+ .addField(FieldType::INDEX, CollectionType::SINGLE, "foo")
+ .addField(FieldType::INDEX, CollectionType::ARRAY, "bar")
+ .addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "baz")
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint")
+ .addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint")
+ .addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsint");
+
+ StringList params, in, out;
+ FT_SETUP_OK(pt, params, in, out.add("score").add("totalWeight"));
+ FT_SETUP_OK(pt, idx_env, params, in
+ .add("fieldMatch(foo)")
+ .add("elementCompleteness(bar)")
+ .add("elementCompleteness(baz)")
+ .add("attributeMatch(sint)")
+ .add("attributeMatch(aint)")
+ .add("attributeMatch(wsint)"), out
+ .add("weight.foo")
+ .add("weight.bar")
+ .add("weight.baz")
+ .add("weight.sint")
+ .add("weight.aint")
+ .add("weight.wsint"));
+ FT_SETUP_FAIL(pt, idx_env, params.add("1")); // expects 0 parameters
+
+ FT_DUMP_EMPTY(_factory, "match");
+ }
+
+ { // Test executor
+ FtFeatureTest ft(_factory, "match");
+
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "bar");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "baz");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsint");
+
+ ft.getIndexEnv().getProperties().add("vespa.fieldweight.foo", "100"); // assign weight to all fields, simulate sd behaviour
+ ft.getIndexEnv().getProperties().add("vespa.fieldweight.bar", "200");
+ ft.getIndexEnv().getProperties().add("vespa.fieldweight.sint", "300");
+ ft.getIndexEnv().getProperties().add("vespa.fieldweight.aint", "400");
+
+ // search in field 'foo'
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")); // term id 0
+
+ // search in field 'sint'
+ ft.getQueryEnv().getBuilder().addAttributeNode("sint"); // term id 1
+ setupForAttributeTest(ft, false);
+
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ // add hit for field 'foo' for search term 0
+ ASSERT_TRUE(mdb->setFieldLength("foo", 1));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 0));
+ ASSERT_TRUE(mdb->setWeight("sint", 1, 0));
+ ASSERT_TRUE(mdb->apply(1));
+
+ RankResult rr = toRankResult("match", "score:1 totalWeight:400 weight.foo:100 weight.bar:200 weight.baz:100 weight.sint:300 weight.aint:400 weight.wsint:100");
+ rr.setEpsilon(1e-4); // same as java tests
+ ASSERT_TRUE(ft.execute(rr));
+ }
+
+ { // Test executor
+ FtFeatureTest ft(_factory, "match");
+
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ // search in field 'foo'
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")); // term id 0
+ ASSERT_TRUE(ft.setup());
+
+ // must create this so that term match data is configured with the term data object
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ // no hits on docId 1
+ RankResult rr = toRankResult("match", "score:0 totalWeight:0 weight.foo:100");
+ ASSERT_TRUE(ft.execute(rr, 1));
+ }
+}
+
+void
+Test::testMatches()
+{
+ { // Test blueprint.
+ MatchesBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "matches"));
+
+ FtFeatureTest ft(_factory, "");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, ft.getIndexEnv(), params); // expects 1-2 parameters
+ FT_SETUP_FAIL(pt, ft.getIndexEnv(), params.add("baz")); // cannot find the field
+ FT_SETUP_OK(pt, ft.getIndexEnv(), params.clear().add("foo"), in, out.add("out"));
+ FT_SETUP_OK(pt, ft.getIndexEnv(), params.add("1"), in, out);
+ FT_SETUP_OK(pt, ft.getIndexEnv(), params.clear().add("bar"), in, out);
+ FT_SETUP_OK(pt, ft.getIndexEnv(), params.add("1"), in, out);
+
+ FT_DUMP_EMPTY(_factory, "matches");
+ }
+ { // Test executor for index fields
+ EXPECT_TRUE(assertMatches(0, "x", "a"));
+ EXPECT_TRUE(assertMatches(1, "a", "a"));
+ EXPECT_TRUE(assertMatches(1, "a b", "a b"));
+ // change docId to indicate no matches in the field
+ EXPECT_TRUE(assertMatches(0, "a", "a", "matches(foo)", 2));
+ // specify termIdx as second parameter
+ EXPECT_TRUE(assertMatches(0, "x", "a", "matches(foo,0)"));
+ EXPECT_TRUE(assertMatches(1, "a", "a", "matches(foo,0)"));
+ EXPECT_TRUE(assertMatches(0, "a", "a", "matches(foo,1)"));
+ EXPECT_TRUE(assertMatches(0, "x b", "a b", "matches(foo,0)"));
+ EXPECT_TRUE(assertMatches(1, "x b", "a b", "matches(foo,1)"));
+ }
+ { // Test executor for attribute fields
+ FtFeatureTest ft(_factory, StringList().add("matches(foo)").
+ add("matches(baz)").
+ add("matches(foo,0)").
+ add("matches(foo,1)").
+ add("matches(foo,2)").
+ add("matches(foo,3)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "baz");
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("foo") != NULL); // query term 0, hit in foo
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("bar") != NULL); // query term 1, hit in bar
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("foo") != NULL); // query term 2, hit in foo
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setWeight("foo", 0, 0);
+ mdb->setWeight("bar", 1, 0);
+ mdb->apply(1);
+ EXPECT_TRUE(ft.execute(RankResult().addScore("matches(foo)", 1)));
+ EXPECT_TRUE(ft.execute(RankResult().addScore("matches(baz)", 0)));
+ EXPECT_TRUE(ft.execute(RankResult().addScore("matches(foo,0)", 1)));
+ EXPECT_TRUE(ft.execute(RankResult().addScore("matches(foo,1)", 0)));
+ EXPECT_TRUE(ft.execute(RankResult().addScore("matches(foo,2)", 0)));
+ EXPECT_TRUE(ft.execute(RankResult().addScore("matches(foo,3)", 0)));
+ }
+}
+
+bool
+Test::assertMatches(uint32_t output,
+ const vespalib::string & query,
+ const vespalib::string & field,
+ const vespalib::string & feature,
+ uint32_t docId)
+{
+ LOG(info, "assertMatches(%u, '%s', '%s', '%s')", output, query.c_str(), field.c_str(), feature.c_str());
+
+ // Setup feature test.
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ std::map<vespalib::string, std::vector<vespalib::string> > index;
+ index["foo"] = FtUtil::tokenize(field);
+ FT_SETUP(ft, FtUtil::toQuery(query), index, 1);
+
+ ASSERT_TRUE(ft.execute(output, EPS, docId));
+ // Execute and compare results.
+ if (!EXPECT_TRUE(ft.execute(output, EPS, docId))) return false;
+ return true;
+}
+
+
+void
+Test::testQuery()
+{
+ { // Test blueprint.
+ QueryBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "query"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_OK(pt, params.add("foo"), in, out.add("out"));
+
+ FT_DUMP_EMPTY(_factory, "query");
+ }
+
+ { // Test executor.
+ RankResult exp;
+ exp.addScore("query(def1)", 1.0).
+ addScore("query(def2)", 2.0).
+ addScore("query(def3)", 0.0).
+ addScore("query(val1)", 1.1).
+ addScore("query(val2)", 2.2).
+ addScore("query(hash1)", vespalib::hash_code("foo")).
+ addScore("query(hash2)", vespalib::hash_code("2")).
+ addScore("query(hash3)", vespalib::hash_code("foo")).
+ addScore("query(hash4)", vespalib::hash_code("'foo"));
+ FtFeatureTest ft(_factory, exp.getKeys());
+ ft.getIndexEnv().getProperties()
+ .add("query(def1)", "1.0")
+ .add("$def2", "2.0");
+ ft.getQueryEnv().getProperties()
+ .add("val1", "1.1")
+ .add("$val2", "2.2")
+ .add("hash1", "foo")
+ .add("hash2", "'2")
+ .add("hash3", "'foo")
+ .add("hash4", "''foo");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(exp));
+ }
+}
+
+void
+Test::testQueryTermCount()
+{
+ { // Test blueprint.
+ QueryTermCountBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "queryTermCount"));
+
+ StringList params, in, out;
+ FT_SETUP_OK(pt, params, in, out.add("out"));
+ FT_SETUP_FAIL(pt, params.add("foo"));
+
+ StringList dump;
+ FT_DUMP(_factory, "queryTermCount", dump.add("queryTermCount"));
+ }
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "queryTermCount");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("queryTermCount", 0)));
+ }
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "queryTermCount");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("queryTermCount", 1)));
+ }
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "queryTermCount");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(RankResult().addScore("queryTermCount", 2)));
+ }
+}
+
+void
+Test::testRandom()
+{
+ { // Test blueprint.
+ RandomBlueprint pt;
+
+ EXPECT_TRUE(assertCreateInstance(pt, "random"));
+
+ StringList params, in, out;
+ FT_SETUP_OK (pt, params, in, out.add("out").add("match"));
+ FT_SETUP_OK (pt, params.add("1"), in, out);
+ FT_SETUP_FAIL(pt, params.add("2"));
+
+ FT_DUMP_EMPTY(_factory, "random");
+ }
+
+ { // Test executor (seed specified through config)
+ FtFeatureTest ft(_factory, "random");
+ ft.getIndexEnv().getProperties().add("random.seed", "100");
+ ASSERT_TRUE(ft.setup());
+ search::Rand48 rnd;
+ rnd.srand48(100);
+ for (uint32_t i = 0; i < 5; ++i) {
+ feature_t exp = rnd.lrand48() / (feature_t)0x80000000u;
+ ASSERT_TRUE(ft.execute(exp, EPS, i + 1));
+ }
+ }
+ { // Test executor (current time used as seed)
+ FtFeatureTest ft(_factory, "random");
+ ASSERT_TRUE(ft.setup());
+ RankResult rr;
+ rr.addScore("random", 1.0f);
+ for (uint32_t i = 0; i < 5; ++i) {
+ feature_t last = rr.getScore("random");
+ rr.clear();
+ ASSERT_TRUE(ft.executeOnly(rr, i + 1));
+ ASSERT_TRUE(last != rr.getScore("random"));
+ }
+ }
+ { // Test executor (random.match)
+ FtFeatureTest ft(_factory, "random.match");
+ ft.getQueryEnv().getProperties().add("random.match.seed", "100");
+ ASSERT_TRUE(ft.setup());
+ search::Rand48 rnd;
+ for (uint32_t i = 1; i <= 5; ++i) {
+ rnd.srand48(100 + i); // seed + lid
+ feature_t exp = rnd.lrand48() / (feature_t)0x80000000u;
+ ASSERT_TRUE(ft.execute(exp, EPS, i));
+ }
+ }
+}
+
+
+void
+Test::testRankingExpression()
+{
+ { // Test blueprint.
+ RankingExpressionBlueprint prototype;
+
+ EXPECT_TRUE(assertCreateInstance(prototype, "rankingExpression"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(prototype, params); // requires config to run without params
+ FT_SETUP_OK (prototype, params.add("foo.out"), in.add("foo.out"), out.add("out"));
+ FT_SETUP_FAIL(prototype, params.add("bar.out"));
+ FT_SETUP_OK (prototype, params.clear().add("log((1 + 2)- 3 * 4 / 5 )"), in.clear(), out);
+ FT_SETUP_OK (prototype,
+ params.clear().add("if(if(f1.out<1,0,1)<if(f2.out<2,0,1),f3.out,3)"),
+ in.clear().add("f1.out").add("f2.out").add("f3.out"), out);
+
+ FT_DUMP_EMPTY(_factory, "rankingExpression");
+ }
+
+ { // Test executor.
+ {
+ FtFeatureTest ft(_factory, getExpression("if(1<2,3,4)"));
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(3.0f));
+ }
+ {
+ FtFeatureTest ft(_factory, getExpression("sqrt(100)"));
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(10.0f));
+ }
+ {
+ FtFeatureTest ft(_factory, getExpression("mysum(value(4),value(4))"));
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(8.0f));
+ }
+ {
+ FtFeatureTest ft(_factory, getExpression("if(mysum(value(4),value(4))>3+4,1,0)"));
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(1.0f));
+ }
+ {
+ FtFeatureTest ft(_factory, "rankingExpression");
+ ft.getIndexEnv().getProperties().add("rankingExpression.rankingScript", "if(1<2,3,4)");
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(3.0f));
+ }
+ {
+ FtFeatureTest ft(_factory, "rankingExpression(foo)");
+ ft.getIndexEnv().getProperties().add("rankingExpression(foo).rankingScript", "if(1<2,3,4)");
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(3.0f));
+ }
+ {
+ FtFeatureTest ft(_factory, "rankingExpression");
+ ft.getIndexEnv().getProperties()
+ .add("rankingExpression.rankingScript", "if(")
+ .add("rankingExpression.rankingScript", "1<")
+ .add("rankingExpression.rankingScript", "2,")
+ .add("rankingExpression.rankingScript", "3,")
+ .add("rankingExpression.rankingScript", "4)");
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(3.0f));
+ }
+ {
+ // test interpreted expression
+ vespalib::string my_expr("3.0 + value(4.0) + sum(tensorFromWeightedSet(query(my_tensor)))");
+ FtFeatureTest ft(_factory, getExpression(my_expr));
+ ft.getQueryEnv().getProperties().add("my_tensor", "{a:1,b:2,c:3}");
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(13.0));
+ }
+ }
+}
+
+vespalib::string
+Test::getExpression(const vespalib::string &parameter) const
+{
+ typedef search::fef::FeatureNameBuilder FNB;
+ return FNB().baseName("rankingExpression").parameter(parameter).buildName();
+}
+
+void
+Test::testTerm()
+{
+ {
+ // Test blueprint.
+ TermBlueprint pt;
+ {
+ EXPECT_TRUE(assertCreateInstance(pt, "term"));
+
+ StringList params, in, out;
+ FT_SETUP_OK (pt, params.add("0"), in, out.add("connectedness").add("significance").add("weight"));
+ FT_SETUP_FAIL(pt, params.add("1"));
+ }
+ {
+ StringList dump;
+ for (uint32_t term = 0; term < 3; ++term) {
+ vespalib::string bn = vespalib::make_string("term(%u)", term);
+ dump.add(bn + ".connectedness").add(bn + ".significance").add(bn + ".weight");
+ }
+ FtIndexEnvironment ie;
+ ie.getProperties().add("term.numTerms", "3");
+ FT_DUMP(_factory, "term", ie, dump); // check override
+
+ for (uint32_t term = 3; term < 5; ++term) {
+ vespalib::string bn = vespalib::make_string("term(%u)", term);
+ dump.add(bn + ".connectedness").add(bn + ".significance").add(bn + ".weight");
+ }
+ FT_DUMP(_factory, "term", dump); // check default
+ }
+ }
+
+ {
+ // Test executor.
+ FtFeatureTest ft(_factory, "term(0)");
+ ASSERT_TRUE(ft.setup());
+
+ RankResult exp;
+ exp .addScore("term(0).connectedness", 0)
+ .addScore("term(0).significance", 0)
+ .addScore("term(0).weight", 0);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ {
+ // Test executor.
+ FtFeatureTest ft(_factory, StringList().add("term(1)").add("term(2)"));
+ ft.getIndexEnv().getBuilder()
+ .addField(FieldType::INDEX, CollectionType::SINGLE, "idx1") // field 0
+ .addField(FieldType::INDEX, CollectionType::SINGLE, "idx2") // field 1
+ .addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "attr"); // field 2
+ ft.getQueryEnv().getBuilder().addAllFields().setUniqueId(0);
+ ft.getQueryEnv().getBuilder().addAllFields().setUniqueId(1).setWeight(search::query::Weight(200)).lookupField(0)->setDocFreq(0.5);
+ ft.getQueryEnv().getBuilder().addAttributeNode("attr")->setUniqueId(2).setWeight(search::query::Weight(400)).lookupField(2)->setDocFreq(0.25);
+ // setup connectedness between term 1 and term 0
+ ft.getQueryEnv().getProperties().add("vespa.term.1.connexity", "0");
+ ft.getQueryEnv().getProperties().add("vespa.term.1.connexity", "0.7");
+ ASSERT_TRUE(ft.setup());
+
+ RankResult exp;
+ exp.addScore("term(1).significance", util::getSignificance(0.50)).
+ addScore("term(1).weight", 200.0f).
+ addScore("term(1).connectedness", 0.7f).
+ addScore("term(2).significance", util::getSignificance(0.25)).
+ addScore("term(2).weight", 400.0f).
+ addScore("term(2).connectedness", 0.1f). // default connectedness
+ setEpsilon(10e-6);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ {
+ // Test executor.
+ FtFeatureTest ft(_factory, "term(0)");
+ ft.getQueryEnv().getBuilder().addAllFields().setUniqueId(0);
+ // setup significance for term 0
+ ft.getQueryEnv().getProperties().add("vespa.term.0.significance", "0.3");
+ ASSERT_TRUE(ft.setup());
+
+ ASSERT_TRUE(ft.execute(RankResult().addScore("term(0).significance", 0.3f).setEpsilon(10e-6)));
+ }
+}
+
+void
+Test::testTermDistance()
+{
+ { // test blueprint
+ TermDistanceBlueprint pt;
+ {
+ EXPECT_TRUE(assertCreateInstance(pt, "termDistance"));
+
+ StringList params, in, out;
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_FAIL(pt, ie, params.add("baz").add("0").add("0"));
+ FT_SETUP_FAIL(pt, ie, params.clear().add("bar").add("0").add("0"));
+
+ FT_SETUP_OK(pt, ie, params.clear().add("foo").add("0").add("0"),
+ in, out.add("forward").add("forwardTermPosition")
+ .add("reverse").add("reverseTermPosition"));
+ }
+ {
+ FT_DUMP_EMPTY(_factory, "termDistance");
+ }
+ }
+
+ { // test executor
+ typedef TermDistanceCalculator::Result Result;
+ const uint32_t UV = TermDistanceCalculator::UNDEFINED_VALUE;
+
+ EXPECT_TRUE(assertTermDistance(Result(), "a b", "x x"));
+ EXPECT_TRUE(assertTermDistance(Result(), "a b", "a x"));
+ EXPECT_TRUE(assertTermDistance(Result(), "a b", "x b"));
+ EXPECT_TRUE(assertTermDistance(Result(), "a", "a b"));
+ EXPECT_TRUE(assertTermDistance(Result(), "a", "a a"));
+ EXPECT_TRUE(assertTermDistance(Result(1,0,UV,UV), "a b", "a b"));
+ EXPECT_TRUE(assertTermDistance(Result(2,0,UV,UV), "a b", "a x b"));
+ EXPECT_TRUE(assertTermDistance(Result(UV,UV,1,0), "a b", "b a"));
+ EXPECT_TRUE(assertTermDistance(Result(UV,UV,2,0), "a b", "b x a"));
+ EXPECT_TRUE(assertTermDistance(Result(2,18,1,20), "a b", "a x x x x x b x x x x a x x x b x x a x b a"));
+ EXPECT_TRUE(assertTermDistance(Result(1,0,2,1), "a b", "a b x a x x b x x x a x x x x b x x x x x a"));
+ EXPECT_TRUE(assertTermDistance(Result(1,0,1,1), "a b", "a b a b a")); // first best is kept
+ EXPECT_TRUE(assertTermDistance(Result(1,0,1,0), "a a", "a a"));
+ EXPECT_TRUE(assertTermDistance(Result(2,0,2,0), "a a", "a x a"));
+ }
+}
+
+bool
+Test::assertTermDistance(const TermDistanceCalculator::Result & exp,
+ const vespalib::string & query,
+ const vespalib::string & field,
+ uint32_t docId)
+{
+ LOG(info, "assertTermDistance('%s', '%s')", query.c_str(), field.c_str());
+
+ vespalib::string feature = "termDistance(foo,0,1)";
+ FtFeatureTest ft(_factory, feature);
+
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ StringVectorMap index;
+ index["foo"] = FtUtil::tokenize(field);
+ FT_SETUP(ft, FtUtil::toQuery(query), index, 1);
+
+ RankResult rr;
+ rr.addScore(feature + ".forward", exp.forwardDist);
+ rr.addScore(feature + ".forwardTermPosition", exp.forwardTermPos);
+ rr.addScore(feature + ".reverse", exp.reverseDist);
+ rr.addScore(feature + ".reverseTermPosition", exp.reverseTermPos);
+ if (!EXPECT_TRUE(ft.execute(rr, docId))) {
+ return false;
+ }
+ return true;
+}
+
+void
+Test::testUtils()
+{
+ { // getSignificance
+ EXPECT_APPROX(util::getSignificance(0.0), 1, EPS);
+ EXPECT_APPROX(util::getSignificance(0.0 + 1.0e-7), 1, EPS);
+ EXPECT_APPROX(util::getSignificance(1.0), 0.5, EPS);
+ EXPECT_APPROX(util::getSignificance(1.0 + 1.0e-7), 0.5, EPS);
+ feature_t last = 1;
+ for (uint32_t i = 2; i <= 100; i = i + 1) {
+ feature_t s = util::getSignificance(i * 1.0e-6);
+ EXPECT_GREATER(s, 0);
+ EXPECT_LESS(s, 1);
+ EXPECT_LESS(s, last);
+ last = s;
+ }
+ for (uint32_t i = 999900; i <= 1000000; i = i + 1) {
+ feature_t s = util::getSignificance(i * 1.0e-6);
+ EXPECT_GREATER(s, 0);
+ EXPECT_LESS(s, 1);
+ EXPECT_LESS(s, last);
+ last = s;
+ }
+ }
+}
+
diff --git a/searchlib/src/tests/features/prod_features.h b/searchlib/src/tests/features/prod_features.h
new file mode 100644
index 00000000000..dd15981af1f
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features.h
@@ -0,0 +1,175 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/searchlib/features/distancetopathfeature.h>
+#include <vespa/searchlib/features/termdistancefeature.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+
+class Test : public FtTestApp
+{
+public:
+ int Main();
+ void testFramework();
+ void testFtLib();
+ void testAge();
+ void testAttribute();
+ void testAttributeMatch();
+ void testCloseness();
+ void testDistance();
+ void testDistanceToPath();
+ void testDotProduct();
+ void testFieldLength();
+ void testFieldMatch();
+ void testFieldTermMatch();
+ void testFirstPhase();
+ void testForeach();
+ void testFreshness();
+ void testMatch();
+ void testMatches();
+ void testNow();
+ void testQuery();
+ void testQueryTermCount();
+ void testRandom();
+ void testRankingExpression();
+ void testTerm();
+ void testTermDistance();
+ void testUtils();
+
+private:
+ void
+ testFieldMatchBluePrint();
+
+ void
+ testFieldMatchExecutor();
+
+ void
+ testFieldMatchExecutorOutOfOrder();
+
+ void
+ testFieldMatchExecutorSegments();
+
+ void
+ testFieldMatchExecutorGaps();
+
+ void
+ testFieldMatchExecutorHead();
+
+ void
+ testFieldMatchExecutorTail();
+
+ void
+ testFieldMatchExecutorLongestSequence();
+
+ void
+ testFieldMatchExecutorMatches();
+
+ void
+ testFieldMatchExecutorCompleteness();
+
+ void
+ testFieldMatchExecutorOrderness();
+
+ void
+ testFieldMatchExecutorRelatedness();
+
+ void
+ testFieldMatchExecutorLongestSequenceRatio();
+
+ void
+ testFieldMatchExecutorEarliness();
+
+ void
+ testFieldMatchExecutorWeight();
+
+ void
+ testFieldMatchExecutorSignificance();
+
+ void
+ testFieldMatchExecutorImportance();
+
+ void
+ testFieldMatchExecutorOccurrence();
+
+ void
+ testFieldMatchExecutorAbsoluteOccurrence();
+
+ void
+ testFieldMatchExecutorWeightedOccurrence();
+
+ void
+ testFieldMatchExecutorWeightedAbsoluteOccurrence();
+
+ void
+ testFieldMatchExecutorSignificantOccurrence();
+
+ void
+ testFieldMatchExecutorUnweightedProximity();
+
+ void
+ testFieldMatchExecutorReverseProximity();
+
+ void
+ testFieldMatchExecutorAbsoluteProximity();
+
+ void
+ testFieldMatchExecutorMultiSegmentProximity();
+
+ void
+ testFieldMatchExecutorSegmentDistance();
+
+ void
+ testFieldMatchExecutorSegmentProximity();
+
+ void
+ testFieldMatchExecutorSegmentStarts();
+
+ void
+ testFieldMatchExecutorMoreThanASegmentLengthOfUnmatchedQuery();
+
+ void
+ testFieldMatchExecutorQueryRepeats();
+
+ void
+ testFieldMatchExecutorZeroCases();
+
+ void
+ testFieldMatchExecutorExceedingIterationLimit();
+
+ void
+ testFieldMatchExecutorRemaining();
+
+
+ void assertAge(feature_t expAge, const vespalib::string & attr, uint64_t now, uint64_t docTime);
+ void setupForAgeTest(FtFeatureTest & ft, uint64_t docTime);
+ void setupForAttributeTest(FtFeatureTest &ft, bool setup_env = true);
+ void assertCloseness(feature_t exp, const vespalib::string & attr, double distance, double maxDistance = 0, double halfResponse = 0);
+ void setupForDistanceTest(FtFeatureTest & ft, const vespalib::string & attrName,
+ const std::vector<std::pair<int32_t, int32_t> > & positions, bool zcurve);
+ void assert2DZDistance(feature_t exp, const vespalib::string & positions,
+ int32_t xquery, int32_t yquery, uint32_t xAspect = 0);
+ void assertDistanceToPath(const std::vector<std::pair<int32_t, int32_t> > pos, const vespalib::string &path,
+ feature_t distance = search::features::DistanceToPathExecutor::DEFAULT_DISTANCE,
+ feature_t traveled = 1, feature_t product = 0);
+ void setupForDocumentTest(FtFeatureTest &ft, const vespalib::string & attrName, const vespalib::string & docType);
+ void assertDotProduct(feature_t exp, const vespalib::string & vector, uint32_t docId = 1,
+ const vespalib::string & attribute = "wsstr", const vespalib::string & attributeOverride="");
+ void setupForDotProductTest(FtFeatureTest & ft);
+ void assertFieldMatch(const vespalib::string & spec, const vespalib::string & query, const vespalib::string & field,
+ const search::features::fieldmatch::Params * params = NULL, uint32_t totalTermWeight = 0, feature_t totalSignificance = 0.0f);
+ void assertFieldMatch(const vespalib::string & spec, const vespalib::string & query, const vespalib::string & field,
+ uint32_t totalTermWeight);
+ void assertFieldMatchTS(const vespalib::string & spec, const vespalib::string & query, const vespalib::string & field,
+ feature_t totalSignificance);
+ vespalib::string getExpression(const vespalib::string &parameter) const;
+ void assertForeachOperation(feature_t exp, const vespalib::string & cond, const vespalib::string & op);
+ void assertFreshness(feature_t expFreshness, const vespalib::string & attr, uint32_t age, uint32_t maxAge = 0, double halfResponse = 0, bool logScale = false);
+ bool assertTermDistance(const search::features::TermDistanceCalculator::Result & exp, const vespalib::string & query,
+ const vespalib::string & field, uint32_t docId = 1);
+ bool assertMatches(uint32_t output, const vespalib::string & query, const vespalib::string & field,
+ const vespalib::string & feature = "matches(foo)", uint32_t docId = 1);
+
+private:
+ search::fef::BlueprintFactory _factory;
+};
+
diff --git a/searchlib/src/tests/features/prod_features_attributematch.cpp b/searchlib/src/tests/features/prod_features_attributematch.cpp
new file mode 100644
index 00000000000..06b2b859709
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features_attributematch.cpp
@@ -0,0 +1,300 @@
+// 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(".prod_features_attributematch");
+
+#include "prod_features.h"
+#include <vespa/searchlib/features/attributematchfeature.h>
+#include <vespa/searchlib/attribute/attributefactory.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+using search::AttributeVector;
+using search::AttributeFactory;
+
+typedef AttributeVector::SP AttributePtr;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+
+
+void
+Test::testAttributeMatch()
+{
+ AttributeMatchBlueprint pt;
+ {
+ EXPECT_TRUE(assertCreateInstance(pt, "attributeMatch"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params); // expects 1 param
+ FT_SETUP_FAIL(pt, params.add("foo")); // field must exists
+
+ FtIndexEnvironment idx_env;
+ idx_env.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FT_SETUP_FAIL(pt, idx_env, params); // field must be an attribute
+ idx_env.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint");
+
+ FT_SETUP_OK(pt, idx_env, params.clear().add("sint"), in, out
+ .add("completeness")
+ .add("queryCompleteness")
+ .add("fieldCompleteness")
+ .add("normalizedWeight")
+ .add("normalizedWeightedWeight")
+ .add("weight")
+ .add("significance")
+ .add("importance")
+ .add("matches")
+ .add("totalWeight")
+ .add("averageWeight"));
+
+ FT_DUMP_EMPTY(_factory, "attributeMatch");
+
+ FT_DUMP(_factory, "attributeMatch", idx_env, out.clear()
+ .add("attributeMatch(sint)")
+ .add("attributeMatch(sint).completeness")
+ .add("attributeMatch(sint).queryCompleteness")
+ .add("attributeMatch(sint).fieldCompleteness")
+ .add("attributeMatch(sint).normalizedWeight")
+ .add("attributeMatch(sint).normalizedWeightedWeight")
+ .add("attributeMatch(sint).weight")
+ .add("attributeMatch(sint).significance")
+ .add("attributeMatch(sint).importance")
+ .add("attributeMatch(sint).matches")
+ .add("attributeMatch(sint).totalWeight")
+ .add("attributeMatch(sint).averageWeight"));
+ }
+
+ { // single attributes
+ FtFeatureTest ft(_factory, StringList().
+ add("attributeMatch(sint)").add("attributeMatch(sfloat)").add("attributeMatch(sstr)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sint"); // 2 matches
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sfloat"); // 1 matches
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "sstr"); // 0 matches
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("sint") != NULL); // query term 0, hit in sint
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("sint") != NULL); // query term 1, ..
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("sint") != NULL); // query term 2, ..
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("sint") != NULL); // query term 3, ..
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("sfloat") != NULL); // query term 4, hit in sfloat
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")) != NULL);
+ ft.getQueryEnv().getTerms()[0].setWeight(search::query::Weight(20));
+ ft.getQueryEnv().getTerms()[0].setUniqueId(0);
+ ft.getQueryEnv().getTerms()[1].setWeight(search::query::Weight(20));
+ ft.getQueryEnv().getTerms()[1].setUniqueId(1);
+ ft.getQueryEnv().getTerms()[2].setWeight(search::query::Weight(10));
+ ft.getQueryEnv().getTerms()[2].setUniqueId(1);
+ ft.getQueryEnv().getTerms()[3].setWeight(search::query::Weight(10));
+ ft.getQueryEnv().getTerms()[3].setUniqueId(1);
+ ft.getQueryEnv().getTerms()[4].setWeight(search::query::Weight(20));
+ ft.getQueryEnv().getTerms()[4].setUniqueId(1);
+ ft.getQueryEnv().getTerms()[5].setWeight(search::query::Weight(20));
+ ft.getQueryEnv().getTerms()[5].setUniqueId(1);
+ ft.getQueryEnv().getProperties().add("vespa.term.0.significance", "0.5"); // change significance for term 0
+ ft.getQueryEnv().getProperties().add("vespa.term.1.significance", "0.1"); // change significance for all other terms
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setWeight("sint", 0, 0);
+ mdb->setWeight("sint", 1, 0);
+ mdb->setWeight("sfloat", 4, 0);
+ mdb->apply(1);
+ RankResult exp;
+ exp.addScore("attributeMatch(sint)", 0.5f). // same as completeness
+ addScore("attributeMatch(sint).matches", 2).
+ addScore("attributeMatch(sint).totalWeight", 0).
+ addScore("attributeMatch(sint).averageWeight", 0).
+ addScore("attributeMatch(sint).completeness", 0.5f).
+ addScore("attributeMatch(sint).queryCompleteness", 0.5f).
+ addScore("attributeMatch(sint).fieldCompleteness", 1).
+ addScore("attributeMatch(sint).normalizedWeight", 0).
+ addScore("attributeMatch(sint).normalizedWeightedWeight", 0).
+ addScore("attributeMatch(sint).weight", 0.4).
+ addScore("attributeMatch(sint).significance", 0.6).
+ addScore("attributeMatch(sint).importance", 0.5).
+ addScore("attributeMatch(sfloat)", 1). // same as completeness
+ addScore("attributeMatch(sfloat).matches", 1).
+ addScore("attributeMatch(sfloat).totalWeight", 0).
+ addScore("attributeMatch(sfloat).averageWeight", 0).
+ addScore("attributeMatch(sfloat).completeness", 1).
+ addScore("attributeMatch(sfloat).queryCompleteness", 1).
+ addScore("attributeMatch(sfloat).fieldCompleteness", 1).
+ addScore("attributeMatch(sfloat).normalizedWeight", 0).
+ addScore("attributeMatch(sfloat).normalizedWeightedWeight", 0).
+ addScore("attributeMatch(sfloat).weight", 0.2).
+ addScore("attributeMatch(sfloat).significance", 0.1).
+ addScore("attributeMatch(sfloat).importance", 0.15).
+ addScore("attributeMatch(sstr)", 0). // same as completeness
+ addScore("attributeMatch(sstr).matches", 0).
+ addScore("attributeMatch(sstr).totalWeight", 0).
+ addScore("attributeMatch(sstr).averageWeight", 0).
+ addScore("attributeMatch(sstr).completeness", 0).
+ addScore("attributeMatch(sstr).queryCompleteness", 0).
+ addScore("attributeMatch(sstr).fieldCompleteness", 0).
+ addScore("attributeMatch(sstr).normalizedWeight", 0).
+ addScore("attributeMatch(sstr).normalizedWeightedWeight", 0).
+ addScore("attributeMatch(sstr).weight", 0).
+ addScore("attributeMatch(sstr).significance", 0).
+ addScore("attributeMatch(sstr).importance", 0).
+ setEpsilon(10e-6);
+ ASSERT_TRUE(ft.execute(exp));
+ ASSERT_TRUE(ft.execute(exp));
+ }
+
+ { // array attributes
+
+ FtFeatureTest ft(_factory, StringList().add("attributeMatch(aint)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint"); // 1 matches
+ ft.getIndexEnv().getProperties().add("attributeMatch(aint).fieldCompletenessImportance", "0.5");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("aint") != NULL); // 0
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setWeight("aint", 0, 0);
+ mdb->apply(1);
+ RankResult exp;
+ exp.addScore("attributeMatch(aint)", 0.75f) // same as completeness
+ .addScore("attributeMatch(aint).matches", 1)
+ .addScore("attributeMatch(aint).totalWeight", 0)
+ .addScore("attributeMatch(aint).averageWeight", 0)
+ .addScore("attributeMatch(aint).completeness", 0.75f)
+ .addScore("attributeMatch(aint).queryCompleteness", 1)
+ .addScore("attributeMatch(aint).fieldCompleteness", 0.5f)
+ .addScore("attributeMatch(aint).normalizedWeight", 0)
+ .addScore("attributeMatch(aint).normalizedWeightedWeight", 0);
+ ASSERT_TRUE(ft.execute(exp));
+ ASSERT_TRUE(ft.execute(exp));
+ }
+
+ { // weighted set attributes
+ FtFeatureTest ft(_factory, StringList().
+ add("attributeMatch(wsint)").add("attributeMatch(wsfloat)").add("attributeMatch(wsstr)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsint"); // 2 matches
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsfloat"); // 1 matches
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wsstr"); // 0 matches
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getProperties().add("attributeMatch(wsint).maxWeight", "100");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("wsint") != NULL); // 0
+ ft.getQueryEnv().getTerms()[0].setWeight(search::query::Weight(2));
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("wsint") != NULL); // 1
+ ft.getQueryEnv().getTerms()[1].setWeight(search::query::Weight(3));
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("wsfloat") != NULL); // 2
+ ft.getQueryEnv().getTerms()[2].setWeight(search::query::Weight(0));
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")) != NULL);
+ ft.getQueryEnv().getTerms()[3].setWeight(search::query::Weight(0));
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->setWeight("wsint", 0, 10);
+ mdb->setWeight("wsint", 1, 20);
+ mdb->setWeight("wsfloat", 2, -30);
+ mdb->apply(1);
+ RankResult exp;
+
+ // test all three attributes
+ exp.addScore("attributeMatch(wsint)", 1). // same as completeness
+ addScore("attributeMatch(wsint).matches", 2).
+ addScore("attributeMatch(wsint).totalWeight", 30).
+ addScore("attributeMatch(wsint).averageWeight", 15).
+ addScore("attributeMatch(wsint).completeness", 1).
+ addScore("attributeMatch(wsint).queryCompleteness", 1).
+ addScore("attributeMatch(wsint).fieldCompleteness", 1).
+ addScore("attributeMatch(wsint).normalizedWeight", 0.1f).
+ addScore("attributeMatch(wsint).normalizedWeightedWeight", 0.16f).
+ addScore("attributeMatch(wsfloat)", 0.95). // same as completeness
+ addScore("attributeMatch(wsfloat).matches", 1).
+ addScore("attributeMatch(wsfloat).totalWeight", -30).
+ addScore("attributeMatch(wsfloat).averageWeight", -30).
+ addScore("attributeMatch(wsfloat).completeness", 0.95).
+ addScore("attributeMatch(wsfloat).queryCompleteness", 1).
+ addScore("attributeMatch(wsfloat).fieldCompleteness", 0).
+ addScore("attributeMatch(wsfloat).normalizedWeight", 0).
+ addScore("attributeMatch(wsfloat).normalizedWeightedWeight", 0).
+ addScore("attributeMatch(wsstr)", 0). // same as completeness
+ addScore("attributeMatch(wsstr).matches", 0).
+ addScore("attributeMatch(wsstr).totalWeight", 0).
+ addScore("attributeMatch(wsstr).averageWeight", 0).
+ addScore("attributeMatch(wsstr).completeness", 0).
+ addScore("attributeMatch(wsstr).queryCompleteness", 0).
+ addScore("attributeMatch(wsstr).fieldCompleteness", 0).
+ addScore("attributeMatch(wsstr).normalizedWeight", 0).
+ addScore("attributeMatch(wsstr).normalizedWeightedWeight", 0).
+ setEpsilon(10e-6);
+ ASSERT_TRUE(ft.execute(exp));
+ ASSERT_TRUE(ft.execute(exp));
+
+ // test fieldCompleteness
+ mdb->setWeight("wsint", 0, 0);
+ mdb->setWeight("wsint", 1, 15);
+ mdb->apply(1);
+ exp.clear().
+ addScore("attributeMatch(wsint).fieldCompleteness", 0.5f);
+ ASSERT_TRUE(ft.execute(exp));
+
+ // test that normalized values lies in the interval [0,1].
+ mdb->setWeight("wsfloat", 2, 1000);
+ mdb->apply(1);
+ ft.getQueryEnv().getTerms()[2].setWeight(search::query::Weight(100));
+ exp.clear().
+ addScore("attributeMatch(wsfloat).normalizedWeight", 1).
+ addScore("attributeMatch(wsfloat).normalizedWeightedWeight", 1);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+
+ { // unique only attribute
+ FtFeatureTest ft(_factory, "attributeMatch(unique)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "unique");
+ setupForAttributeTest(ft);
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("unique") != NULL);
+ ASSERT_TRUE(ft.setup());
+
+ RankResult exp;
+ exp.addScore("attributeMatch(unique)", 0). // same as completeness
+ addScore("attributeMatch(unique).matches", 0).
+ addScore("attributeMatch(unique).totalWeight", 0).
+ addScore("attributeMatch(unique).averageWeight", 0).
+ addScore("attributeMatch(unique).completeness", 0).
+ addScore("attributeMatch(unique).queryCompleteness", 0).
+ addScore("attributeMatch(unique).fieldCompleteness", 0).
+ addScore("attributeMatch(unique).normalizedWeight", 0).
+ addScore("attributeMatch(unique).normalizedWeightedWeight", 0);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ {
+ FtFeatureTest ft(_factory, StringList().add("attributeMatch(aint)").add("attributeMatch(wint)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::ARRAY, "aint");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "wint");
+
+ // setup an array and wset attributes with 0 elements
+ AttributePtr aint = AttributeFactory::createAttribute("aint", AVC (AVBT::INT32, AVCT::ARRAY));
+ AttributePtr wint = AttributeFactory::createAttribute("wint", AVC(AVBT::INT32, AVCT::WSET));
+ aint->addReservedDoc();
+ wint->addReservedDoc();
+ ft.getIndexEnv().getAttributeManager().add(aint);
+ ft.getIndexEnv().getAttributeManager().add(wint);
+ aint->addDocs(1);
+ aint->commit();
+ ASSERT_TRUE(aint->getValueCount(0) == 0);
+ wint->addDocs(1);
+ wint->commit();
+ ASSERT_TRUE(wint->getValueCount(0) == 0);
+
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("aint") != NULL);
+ ASSERT_TRUE(ft.getQueryEnv().getBuilder().addAttributeNode("wint") != NULL);
+ ASSERT_TRUE(ft.setup());
+
+ RankResult exp;
+ exp.addScore("attributeMatch(aint)", 0). // same as completeness
+ addScore("attributeMatch(aint).completeness", 0).
+ addScore("attributeMatch(aint).fieldCompleteness", 0).
+ addScore("attributeMatch(wint)", 0). // same as completeness
+ addScore("attributeMatch(wint).completeness", 0).
+ addScore("attributeMatch(wint).fieldCompleteness", 0);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+}
diff --git a/searchlib/src/tests/features/prod_features_fieldmatch.cpp b/searchlib/src/tests/features/prod_features_fieldmatch.cpp
new file mode 100644
index 00000000000..e26d6a92fa6
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features_fieldmatch.cpp
@@ -0,0 +1,1079 @@
+// 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(".prod_features_fieldmatch");
+
+#include <vespa/searchlib/fef/test/ftlib.h>
+
+#include "prod_features.h"
+
+#include <vespa/searchlib/features/fieldmatchfeature.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+using search::AttributeVector;
+
+void
+Test::testFieldMatch()
+{
+ testFieldMatchBluePrint();
+ testFieldMatchExecutor();
+}
+
+
+void
+Test::testFieldMatchBluePrint()
+{
+ FieldMatchBlueprint pt;
+ StringList out;
+ out.add("score").
+ add("proximity").
+ add("completeness").
+ add("queryCompleteness").
+ add("fieldCompleteness").
+ add("orderness").
+ add("relatedness").
+ add("earliness").
+ add("longestSequenceRatio").
+ add("segmentProximity").
+ add("unweightedProximity").
+ add("absoluteProximity").
+ add("occurrence").
+ add("absoluteOccurrence").
+ add("weightedOccurrence").
+ add("weightedAbsoluteOccurrence").
+ add("significantOccurrence").
+
+ add("weight").
+ add("significance").
+ add("importance").
+
+ add("segments").
+ add("matches").
+ add("outOfOrder").
+ add("gaps").
+ add("gapLength").
+ add("longestSequence").
+ add("head").
+ add("tail").
+ add("segmentDistance").
+ add("degradedMatches");
+ {
+ EXPECT_TRUE(assertCreateInstance(pt, "fieldMatch"));
+
+ StringList params, in;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_FAIL(pt, params.add("foo"));
+ FT_SETUP_FAIL(pt, params.add("bar"));
+ params.clear();
+
+ {
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "abar");
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wbar");
+ FT_SETUP_FAIL(pt, ie, params.add("foo"));
+ FT_SETUP_FAIL(pt, ie, params.add("abar"));
+ FT_SETUP_FAIL(pt, ie, params.add("wbar"));
+
+ FT_SETUP_OK(pt, ie, params.clear().add("bar"), in, out);
+ }
+
+ { // test illegal proximity table
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ Properties & p = ie.getProperties();
+ p.add("fieldMatch(foo).proximityLimit", "1");
+
+ // too few elements, should be 3 (1*2 + 1)
+ p.add("fieldMatch(foo).proximityTable", "0.5");
+ p.add("fieldMatch(foo).proximityTable", "1.0");
+ FT_SETUP_FAIL(pt, ie, params);
+
+ // too many elements, should be 3 (1*2 + 1)
+ p.add("fieldMatch(foo).proximityTable", "1.0");
+ p.add("fieldMatch(foo).proximityTable", "0.5");
+ FT_SETUP_FAIL(pt, ie, params);
+ }
+ }
+ { // test dumping with a regular index field
+ FT_DUMP_EMPTY(_factory, "fieldMatch");
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ FT_DUMP_EMPTY(_factory, "fieldMatch", ie); // must be an index field
+
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::ARRAY, "abar");
+ FT_DUMP_EMPTY(_factory, "fieldMatch", ie); // must be single value
+
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "wbar");
+ FT_DUMP_EMPTY(_factory, "fieldMatch", ie); // must be single value
+
+ StringList dump;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ vespalib::string bn = "fieldMatch(bar)";
+ dump.add(bn);
+ for (uint32_t i = 1; i < out.size(); ++i) {
+ dump.add(bn + "." + out[i]);
+ }
+ FT_DUMP(_factory, "fieldMatch", ie, dump);
+ }
+
+ { // test dumping with a filter index field
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ie.getFields()[0].setFilter(true);
+
+ StringList dump;
+ vespalib::string bn = "fieldMatch(foo)";
+ dump.add(bn);
+ dump.add(bn + ".completeness");
+ dump.add(bn + ".queryCompleteness");
+ dump.add(bn + ".weight");
+ dump.add(bn + ".matches");
+ dump.add(bn + ".degradedMatches");
+ FT_DUMP(_factory, "fieldMatch", ie, dump);
+ }
+}
+
+
+void
+Test::testFieldMatchExecutor()
+{
+ testFieldMatchExecutorOutOfOrder();
+ testFieldMatchExecutorSegments();
+ testFieldMatchExecutorGaps();
+ testFieldMatchExecutorHead();
+ testFieldMatchExecutorTail();
+ testFieldMatchExecutorLongestSequence();
+ testFieldMatchExecutorMatches();
+ testFieldMatchExecutorCompleteness();
+ testFieldMatchExecutorOrderness();
+ testFieldMatchExecutorRelatedness();
+ testFieldMatchExecutorLongestSequenceRatio();
+ testFieldMatchExecutorEarliness();
+ testFieldMatchExecutorWeight();
+ testFieldMatchExecutorSignificance();
+ testFieldMatchExecutorImportance();
+ testFieldMatchExecutorOccurrence();
+ testFieldMatchExecutorAbsoluteOccurrence();
+ testFieldMatchExecutorWeightedOccurrence();
+ testFieldMatchExecutorWeightedAbsoluteOccurrence();
+ testFieldMatchExecutorSignificantOccurrence();
+ testFieldMatchExecutorUnweightedProximity();
+ testFieldMatchExecutorReverseProximity();
+ testFieldMatchExecutorAbsoluteProximity();
+ testFieldMatchExecutorMultiSegmentProximity();
+ testFieldMatchExecutorSegmentDistance();
+ testFieldMatchExecutorSegmentProximity();
+ testFieldMatchExecutorSegmentStarts();
+ testFieldMatchExecutorMoreThanASegmentLengthOfUnmatchedQuery();
+ testFieldMatchExecutorQueryRepeats();
+ testFieldMatchExecutorZeroCases();
+ testFieldMatchExecutorExceedingIterationLimit();
+ testFieldMatchExecutorRemaining();
+}
+
+
+void
+Test::testFieldMatchExecutorOutOfOrder()
+{
+ assertFieldMatch("outOfOrder:0","a","a");
+ assertFieldMatch("outOfOrder:0","a b c","a b c");
+ assertFieldMatch("outOfOrder:1","a b c","a c b");
+ assertFieldMatch("outOfOrder:2","a b c","c b a");
+ assertFieldMatch("outOfOrder:2","a b c d e","c x a b x x x x x e x x d");
+ assertFieldMatch("outOfOrder:2","a b c d e","c x a b x x x x x e x x d");
+ assertFieldMatch("outOfOrder:2","a b c d e","c x a b x x x x x e x x d");
+}
+
+
+void
+Test::testFieldMatchExecutorSegments()
+{
+ assertFieldMatch("segments:1","a","a");
+ assertFieldMatch("segments:1","a b c","a b c");
+ assertFieldMatch("segments:1","a b c","a x x b c");
+ assertFieldMatch("segments:2","a b c","a x x x x x x x x x x x x x x x x x x x b c");
+ assertFieldMatch("segments:2","a b c","b c x x x x x x x x x x x x x x x x x x x a");
+ assertFieldMatch("segments:2 gaps:1","a b c","x x x a x x x x x x x x x x x x x x x x x x x b x x c x x");
+ assertFieldMatch("segments:2 gaps:0 outOfOrder:0","a b c","b c x x x x x x x x x x x x x x x x x x x a");
+ assertFieldMatch("segments:2 gaps:1","a b c","x x x b x x c x x x x x x x x x x x x x x x x x x x a x x");
+ assertFieldMatch("segments:2 gaps:1","a y y b c","x x x b x x c x x x x x x x x x x x x x x x x x x x a x x");
+}
+
+
+void
+Test::testFieldMatchExecutorGaps()
+{
+ assertFieldMatch("gaps:0","a","a");
+ assertFieldMatch("gaps:0","x�a","a"); // TODO: which char ?
+ assertFieldMatch("gaps:0 gapLength:0","a b c","a b c");
+ assertFieldMatch("gaps:1 gapLength:1","a b","b a");
+ assertFieldMatch("gaps:1 gapLength:1","a b c","a x b c");
+ assertFieldMatch("gaps:1 gapLength:3","a b c","a x X Xb c");
+ assertFieldMatch("gaps:2 gapLength:2 outOfOrder:1","a b c","a c b");
+ assertFieldMatch("gaps:2 gapLength:2 outOfOrder:0","a b c","a x b x c");
+ assertFieldMatch("gaps:2 gapLength:5 outOfOrder:1","a b c","a x c x b");
+ assertFieldMatch("gaps:3 outOfOrder:2 segments:1","a b c d e","x d x x b c x x a e");
+ assertFieldMatch("gaps:0","y a b c","a b c x");
+}
+
+
+void
+Test::testFieldMatchExecutorHead()
+{
+ assertFieldMatch("head:0","a","a");
+ //assertFieldMatch("head:0","y","a"); // no hit, executor will not run
+ assertFieldMatch("head:1","a","x a");
+ assertFieldMatch("head:2","a b c","x x a b c");
+ assertFieldMatch("head:2","a b c","x x c x x a b");
+ assertFieldMatch("head:2","a b c","x x c x x x x x x x x x x x x x x x a b");
+}
+
+
+void
+Test::testFieldMatchExecutorTail()
+{
+ assertFieldMatch("tail:0","a","a");
+ //assertFieldMatch("tail:0","y","a"); // no hit, executor will not run
+ assertFieldMatch("tail:1","a","a x");
+ assertFieldMatch("tail:2","a b c","a b c x x");
+ assertFieldMatch("tail:2","a b c","x x x c x x x x a b x x");
+ assertFieldMatch("tail:0","a b c","x x c x x x x x x x x x x x x x x x a b");
+}
+
+void
+Test::testFieldMatchExecutorLongestSequence()
+{
+ assertFieldMatch("longestSequence:1","a","a");
+ assertFieldMatch("longestSequence:1","a","a b c");
+ assertFieldMatch("longestSequence:1","b","a b c");
+ assertFieldMatch("longestSequence:3","a b c","x x a b c x x a b x");
+ assertFieldMatch("longestSequence:3 segments:1","a b c","x x a b x x a b c x");
+ assertFieldMatch("longestSequence:2","a b c d","x x c d x x a b x");
+ assertFieldMatch("longestSequence:2","a b c d","x x a b x c d x x");
+ assertFieldMatch("longestSequence:2","a b c d","x x a b x x x x x x x x x x x x x x x x x c d x x");
+ assertFieldMatch("longestSequence:4 segments:1","a b c d","x x a b x x x x x x x x x x x x x x x x x c d x x a b c d");
+}
+
+
+void
+Test::testFieldMatchExecutorMatches()
+{
+ assertFieldMatch("matches:1 queryCompleteness:1 fieldCompleteness:1","a","a");
+ assertFieldMatch("matches:3 queryCompleteness:1 fieldCompleteness:1","a b c","a b c");
+ assertFieldMatch("matches:3 queryCompleteness:1 fieldCompleteness:0.5","a b c","a b c a b d");
+ assertFieldMatch("matches:3 queryCompleteness:0.5 fieldCompleteness:0.25","a y y b c y","a x x b c x a x a b x x");
+}
+
+
+void
+Test::testFieldMatchExecutorCompleteness()
+{
+ assertFieldMatch("completeness:1 queryCompleteness:1 fieldCompleteness:1","a","a");
+ assertFieldMatch("completeness:0 queryCompleteness:0 fieldCompleteness:0","a","x");
+ assertFieldMatch("completeness:0 queryCompleteness:0 fieldCompleteness:0","y","a");
+ assertFieldMatch("completeness:0.975 queryCompleteness:1 fieldCompleteness:0.5","a","a a");
+ assertFieldMatch("completeness:0.525 queryCompleteness:0.5 fieldCompleteness:1","a a","a");
+ assertFieldMatch("completeness:1 queryCompleteness:1 fieldCompleteness:1","a b c","a b c");
+ assertFieldMatch("completeness:0.525 queryCompleteness:0.5 fieldCompleteness:1","a b c d","a b");
+ assertFieldMatch("completeness:0.975 queryCompleteness:1 fieldCompleteness:0.5","a b","a b c d");
+ assertFieldMatch("completeness:0.97 queryCompleteness:1 fieldCompleteness:0.4","a b","a b c d e");
+}
+
+
+void
+Test::testFieldMatchExecutorOrderness()
+{
+ assertFieldMatch("orderness:1", "a","a");
+ // Note: we have no hits -> orderness: 0(1)
+ assertFieldMatch("orderness:0", "a","x");
+ assertFieldMatch("orderness:0", "a a a","a"); // Oh well...
+ assertFieldMatch("orderness:1", "a","a a a");
+ assertFieldMatch("orderness:0", "a b","b a");
+ assertFieldMatch("orderness:0.5","a b c","b a c");
+ assertFieldMatch("orderness:0.5","a b c d","c b d x x x x x x x x x x x x x x x x x x x x x a");
+}
+
+
+void
+Test::testFieldMatchExecutorRelatedness()
+{
+ assertFieldMatch("relatedness:1", "a","a");
+ assertFieldMatch("relatedness:0", "a","x");
+ assertFieldMatch("relatedness:1", "a b","a b");
+ assertFieldMatch("relatedness:1", "a b c","a b c");
+ assertFieldMatch("relatedness:0.5","a b c","a b x x x x x x x x x x x x x x x x x x x x x x x c");
+ assertFieldMatch("relatedness:0.5","a y b y y y c","a b x x x x x x x x x x x x x x x x x x x x x x x c");
+}
+
+
+void
+Test::testFieldMatchExecutorLongestSequenceRatio()
+{
+ assertFieldMatch("longestSequenceRatio:1", "a","a");
+ assertFieldMatch("longestSequenceRatio:0", "a","x");
+ assertFieldMatch("longestSequenceRatio:1", "a a","a");
+ assertFieldMatch("longestSequenceRatio:1", "a","a a");
+ assertFieldMatch("longestSequenceRatio:1", "a b","a b");
+ assertFieldMatch("longestSequenceRatio:1", "a y"," a x");
+ assertFieldMatch("longestSequenceRatio:0.5","a b","a x b");
+ assertFieldMatch("longestSequenceRatio:0.75","a b c d","x x a b x a x c d a b c x d x");
+}
+
+
+void
+Test::testFieldMatchExecutorEarliness()
+{
+ assertFieldMatch("earliness:1", "a","a");
+ assertFieldMatch("earliness:0", "a","x");
+ assertFieldMatch("earliness:1", "a","a a a");
+ assertFieldMatch("earliness:1", "a a a","a");
+ assertFieldMatch("earliness:0.8", "b","a b c");
+ assertFieldMatch("earliness:0.8", "b","a b");
+ assertFieldMatch("earliness:0.9091","a b c","x b c x x x x x a x x x");
+ assertFieldMatch("earliness:0.2", "a b c","x b c a x x x x a x x x x x x x a b c x x");
+}
+
+
+void
+Test::testFieldMatchExecutorWeight()
+{
+ assertFieldMatch("weight:1", "a","a");
+ assertFieldMatch("weight:0", "y","a");
+ assertFieldMatch("weight:0.3333","a a a","a");
+ assertFieldMatch("weight:1", "a","a a a");
+ assertFieldMatch("weight:1", "a b c","a b c");
+ assertFieldMatch("weight:1", "a b c","x x a b x a x c x x a b x c c x");
+
+ assertFieldMatch("weight:0.3333","a b c","a");
+ assertFieldMatch("weight:0.6667","a b c","a b");
+
+ assertFieldMatch("weight:1", "a b c!200","a b c"); // Best
+ assertFieldMatch("weight:0.75","a b c!200","b c"); // Middle
+ assertFieldMatch("weight:0.5", "a b c!200","a b"); // Worst
+
+ assertFieldMatch("weight:1","a!300 b c!200","a b c"); // Best too
+
+ assertFieldMatch("weight:1", "a b c!50","a b c"); // Best
+ assertFieldMatch("weight:0.6","a b c!50","b c"); // Worse
+ assertFieldMatch("weight:0.4","a b c!50","b"); // Worse
+ assertFieldMatch("weight:0.2","a b c!50","c"); // Worst
+ assertFieldMatch("weight:0.8","a b c!50","a b"); // Middle
+
+ assertFieldMatch("weight:1", "a b c!0","a b c"); // Best
+ assertFieldMatch("weight:0.5","a b c!0","b c"); // Worst
+ assertFieldMatch("weight:1", "a b c!0","a b"); // As good as best
+ assertFieldMatch("weight:0", "a b c!0","c"); // No contribution
+
+ assertFieldMatch("weight:0","a!0 b!0","a b");
+ assertFieldMatch("weight:0","a!0 b!0","");
+
+ // The query also has other terms having a total weight of 300
+ // so we add a weight parameter which is the sum of the weights of this query terms + 300
+ assertFieldMatch("weight:0.25", "a","a",400);
+ assertFieldMatch("weight:0", "y","a",400);
+ assertFieldMatch("weight:0.1667","a a a","a",600);
+ assertFieldMatch("weight:0.25", "a","a a a",400);
+ assertFieldMatch("weight:0.5", "a b c","a b c",600);
+ assertFieldMatch("weight:0.5", "a b c","x x a b x a x c x x a b x c c x",600);
+
+ assertFieldMatch("weight:0.1667","a b c","a",600);
+ assertFieldMatch("weight:0.3333","a b c","a b",600);
+
+ assertFieldMatch("weight:0.5714","a b c!200","a b c",700); // Best
+ assertFieldMatch("weight:0.4286","a b c!200","b c",700); // Middle
+ assertFieldMatch("weight:0.2857","a b c!200","a b",700); // Worst
+
+ assertFieldMatch("weight:0.6667","a!300 b c!200","a b c",900); // Better than best
+
+ assertFieldMatch("weight:0.4545","a b c!50","a b c",550); // Best
+ assertFieldMatch("weight:0.2727","a b c!50","b c",550); // Worse
+ assertFieldMatch("weight:0.1818","a b c!50","b",550); // Worse
+ assertFieldMatch("weight:0.0909","a b c!50","c",550); // Worst
+ assertFieldMatch("weight:0.3636","a b c!50","a b",550); // Middle
+
+ assertFieldMatch("weight:0.4","a b c!0","a b c",500); // Best
+ assertFieldMatch("weight:0.2","a b c!0","b c",500); // Worst
+ assertFieldMatch("weight:0.4","a b c!0","a b",500); // As good as best
+ assertFieldMatch("weight:0", "a b c!0","c",500); // No contribution
+
+ assertFieldMatch("weight:0","a!0 b!0","a b",300);
+ assertFieldMatch("weight:0","a!0 b!0","",300);
+}
+
+
+void
+Test::testFieldMatchExecutorSignificance()
+{
+ assertFieldMatch("significance:1", "a","a");
+ assertFieldMatch("significance:0", "a","x");
+ assertFieldMatch("significance:0.3333","a a a","a");
+ assertFieldMatch("significance:1", "a","a a a");
+ assertFieldMatch("significance:1", "a b c","a b c");
+ assertFieldMatch("significance:1", "a b c","x x a b x a x c x x a b x c c x");
+
+ assertFieldMatch("significance:0.3333","a b c","a");
+ assertFieldMatch("significance:0.6667","a b c","a b");
+
+ assertFieldMatch("significance:1", "a b c%0.2","a b c"); // Best
+ assertFieldMatch("significance:0.75","a b c%0.2","b c"); // Middle
+ assertFieldMatch("significance:0.5", "a b c%0.2","a b"); // Worst
+
+ assertFieldMatch("significance:1","a%0.3 b c%0.2","a b c"); // Best too
+
+ assertFieldMatch("significance:1", "a b c%0.05","a b c"); // Best
+ assertFieldMatch("significance:0.6","a b c%0.05","b c"); // Worse
+ assertFieldMatch("significance:0.4","a b c%0.05","b"); // Worse
+ assertFieldMatch("significance:0.2","a b c%0.05","c"); // Worst
+ assertFieldMatch("significance:0.8","a b c%0.05","a b"); // Middle
+
+ assertFieldMatch("significance:1", "a b c%0","a b c"); // Best
+ assertFieldMatch("significance:0.5","a b c%0","b c"); // Worst
+ assertFieldMatch("significance:1", "a b c%0","a b"); // As good as best
+ assertFieldMatch("significance:0", "a b c%0","c"); // No contribution
+
+ assertFieldMatch("significance:0","a%0 b%0","a b");
+ assertFieldMatch("significance:0","a%0 b%0","");
+
+ // The query also has other terms having a total significance of 0.3
+ // so we add a significance parameter which is the sum of the significances of this query terms + 0.3
+ assertFieldMatchTS("significance:0.25", "a","a",0.4f);
+ assertFieldMatchTS("significance:0", "y","a",0.4f);
+ assertFieldMatchTS("significance:0.1667","a a a","a",0.6f);
+ assertFieldMatchTS("significance:0.25", "a","a a a",0.4f);
+ assertFieldMatchTS("significance:0.5", "a b c","a b c",0.6f);
+ assertFieldMatchTS("significance:0.5", "a b c","x x a b x a x c x x a b x c c x",0.6f);
+
+ assertFieldMatchTS("significance:0.1667","a b c","a",0.6f);
+ assertFieldMatchTS("significance:0.3333","a b c","a b",0.6f);
+
+ assertFieldMatchTS("significance:0.5714","a b c%0.2","a b c",0.7f); // Best
+ assertFieldMatchTS("significance:0.4286","a b c%0.2","b c",0.7f); // Middle
+ assertFieldMatchTS("significance:0.2857","a b c%0.2","a b",0.7f); // Worst
+
+ assertFieldMatchTS("significance:0.6667","a%0.3 b c%0.2","a b c",0.9f); // Better than best
+
+ assertFieldMatchTS("significance:0.4545","a b c%0.05","a b c",0.55f); // Best
+ assertFieldMatchTS("significance:0.2727","a b c%0.05","b c",0.55f); // Worse
+ assertFieldMatchTS("significance:0.1818","a b c%0.05","b",0.55f); // Worse
+ assertFieldMatchTS("significance:0.0909","a b c%0.05","c",0.55f); // Worst
+ assertFieldMatchTS("significance:0.3636","a b c%0.05","a b",0.55f); // Middle
+
+ assertFieldMatchTS("significance:0.4","a b c%0","a b c",0.5f); // Best
+ assertFieldMatchTS("significance:0.2","a b c%0","b c",0.5f); // Worst
+ assertFieldMatchTS("significance:0.4","a b c%0","a b",0.5f); // As good as best
+ assertFieldMatchTS("significance:0", "a b c%0","c",0.5f); // No contribution
+
+ assertFieldMatchTS("significance:0","a%0 b%0","a b",0.3f);
+ assertFieldMatchTS("significance:0","a%0 b%0","",0.3f);
+}
+
+
+void
+Test::testFieldMatchExecutorImportance()
+{
+ assertFieldMatch("importance:0.75","a b c", "a x x b x c c c",600);
+ assertFieldMatch("importance:0.85","a b!500 c","a x x b x c c c",1000);
+
+ // Twice as common - twice as weighty, but total weight has the extra 300 - less than the previous
+ assertFieldMatch("importance:0.7857","a b!200%0.05 c","a x x b x c c c",700);
+ // Here higher importancy exactly offsets the lowered uniqueness
+ assertFieldMatch("importance:0.85","a b!500%0.5 c","a x x b x c c c",1000);
+}
+
+
+void
+Test::testFieldMatchExecutorOccurrence()
+{
+ assertFieldMatch("occurrence:0","a","x");
+ assertFieldMatch("occurrence:1","a","a");
+ assertFieldMatch("occurrence:0","a a a","x");
+ assertFieldMatch("occurrence:1","a a a","a");
+ assertFieldMatch("occurrence:1","a a a","a a a");
+ assertFieldMatch("occurrence:1","a a a","a a a a");
+ assertFieldMatch("occurrence:0.3571","a","x x x a x x a x a x x x a a");
+ assertFieldMatch("occurrence:1","a","a a a a a a a a a a a a a a");
+ assertFieldMatch("occurrence:1","a b","a b b a a a a a b a a b a a");
+
+ // tests going beyond the occurrence limit
+ fieldmatch::Params params;
+ params.setMaxOccurrences(10);
+ assertFieldMatch("occurrence:1", "a b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("occurrence:0.9231","a b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("occurrence:0.6", "a b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("occurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("occurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+}
+
+
+void
+Test::testFieldMatchExecutorAbsoluteOccurrence()
+{
+ assertFieldMatch("absoluteOccurrence:0", "a","x");
+ assertFieldMatch("absoluteOccurrence:0.01","a","a");
+ assertFieldMatch("absoluteOccurrence:0","a a a","x");
+ assertFieldMatch("absoluteOccurrence:0.01", "a a a","a");
+ assertFieldMatch("absoluteOccurrence:0.03", "a a a","a a a");
+ assertFieldMatch("absoluteOccurrence:0.04", "a a a","a a a a");
+ assertFieldMatch("absoluteOccurrence:0.05","a","x x x a x x a x a x x x a a");
+ assertFieldMatch("absoluteOccurrence:0.14","a","a a a a a a a a a a a a a a");
+ assertFieldMatch("absoluteOccurrence:0.07","a b","a b b a a a a a b a a b a a");
+
+ // tests going beyond the occurrence limit
+ fieldmatch::Params params;
+ params.setMaxOccurrences(10);
+ assertFieldMatch("absoluteOccurrence:0.6","a b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("absoluteOccurrence:0.6","a b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("absoluteOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("absoluteOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("absoluteOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+}
+
+
+void
+Test::testFieldMatchExecutorWeightedOccurrence()
+{
+ assertFieldMatch("weightedOccurrence:0","a!200","x");
+ assertFieldMatch("weightedOccurrence:1","a!200","a");
+ assertFieldMatch("weightedOccurrence:0","a!200 a a","x");
+ assertFieldMatch("weightedOccurrence:1","a!200 a a","a");
+ assertFieldMatch("weightedOccurrence:1","a a a","a a a");
+ assertFieldMatch("weightedOccurrence:1","a!200 a a","a a a a");
+ assertFieldMatch("weightedOccurrence:0.3571","a!200","x x x a x x a x a x x x a a");
+ assertFieldMatch("weightedOccurrence:1","a!200","a a a a a a a a a a a a a a");
+ assertFieldMatch("weightedOccurrence:0.5","a b","a b b a a a a a b a a b a a");
+
+ assertFieldMatch("weightedOccurrence:0.5714","a!200 b","a b b a a a a a b a a b a a");
+ assertFieldMatch("weightedOccurrence:0.6753","a!1000 b","a b b a a a a a b a a b a a"); // Should be higher
+ assertFieldMatch("weightedOccurrence:0.4286","a b!200","a b b a a a a a b a a b a a"); // Should be lower
+ assertFieldMatch("weightedOccurrence:0.3061","a b!2000","a b b a a a a a b a a b a a"); // Should be even lower
+
+ assertFieldMatch("weightedOccurrence:0.30","a b", "a a b b b b x x x x");
+ assertFieldMatch("weightedOccurrence:0.3333","a b!200","a a b b b b x x x x"); // More frequent is more important - higher
+ assertFieldMatch("weightedOccurrence:0.2667","a!200 b","a a b b b b x x x x"); // Less frequent is more important - lower
+ assertFieldMatch("weightedOccurrence:0.2667","a b!50", "a a b b b b x x x x"); // Same relative
+
+ assertFieldMatch("weightedOccurrence:0","a!0 b!0", "a a b b b b x x x x");
+
+ // tests going beyond the occurrence limit
+ fieldmatch::Params params;
+ params.setMaxOccurrences(10);
+ assertFieldMatch("weightedOccurrence:0.6","a b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("weightedOccurrence:0.6","a b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("weightedOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("weightedOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("weightedOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+
+ assertFieldMatch("weightedOccurrence:0.7333","a!200 b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("weightedOccurrence:0.4667","a b!200","a a a a a a a a a a b b", &params);
+ assertFieldMatch("weightedOccurrence:0.7333","a!200 b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("weightedOccurrence:0.7333","a!200 b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("weightedOccurrence:1", "a!200 b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("weightedOccurrence:1", "a!200 b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+}
+
+
+void
+Test::testFieldMatchExecutorWeightedAbsoluteOccurrence()
+{
+ assertFieldMatch("weightedAbsoluteOccurrence:0", "a!200","x");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.01", "a!200","a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0", "a!200 a a","x");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.01", "a!200 a a","a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.03", "a a a","a a a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.04", "a!200 a a","a a a a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.05", "a!200","x x x a x x a x a x x x a a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.14", "a!200","a a a a a a a a a a a a a a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.07","a b","a b b a a a a a b a a b a a");
+
+ assertFieldMatch("weightedAbsoluteOccurrence:0.08", "a!200 b","a b b a a a a a b a a b a a");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.0945","a!1000 b","a b b a a a a a b a a b a a"); // Should be higher
+ assertFieldMatch("weightedAbsoluteOccurrence:0.06", "a b!200","a b b a a a a a b a a b a a"); // Should be lower
+ assertFieldMatch("weightedAbsoluteOccurrence:0.0429","a b!2000","a b b a a a a a b a a b a a"); // Should be even lower
+
+ assertFieldMatch("weightedAbsoluteOccurrence:0.03", "a b", "a a b b b b x x x x");
+ assertFieldMatch("weightedAbsoluteOccurrence:0.0333","a b!200","a a b b b b x x x x"); // More frequent is more important - higher
+ assertFieldMatch("weightedAbsoluteOccurrence:0.0267","a!200 b","a a b b b b x x x x"); // Less frequent is more important - lower
+ assertFieldMatch("weightedAbsoluteOccurrence:0.0267","a b!50", "a a b b b b x x x x"); // Same relative
+
+ assertFieldMatch("weightedAbsoluteOccurrence:0","a!0 b!0", "a a b b b b x x x x");
+
+ // tests going beyond the occurrence limit
+ fieldmatch::Params params;
+ params.setMaxOccurrences(10);
+ assertFieldMatch("weightedAbsoluteOccurrence:0.6","a b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("weightedAbsoluteOccurrence:0.6","a b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("weightedAbsoluteOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("weightedAbsoluteOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("weightedAbsoluteOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+
+ assertFieldMatch("weightedAbsoluteOccurrence:0.7333","a!200 b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("weightedAbsoluteOccurrence:0.4667","a b!200","a a a a a a a a a a b b", &params);
+ assertFieldMatch("weightedAbsoluteOccurrence:0.7333","a!200 b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("weightedAbsoluteOccurrence:0.7333","a!200 b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("weightedAbsoluteOccurrence:1", "a!200 b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("weightedAbsoluteOccurrence:1", "a!200 b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+}
+
+
+void
+Test::testFieldMatchExecutorSignificantOccurrence()
+{
+ assertFieldMatch("significantOccurrence:0","a%0.2","x");
+ assertFieldMatch("significantOccurrence:1","a%0.2","a");
+ assertFieldMatch("significantOccurrence:0","a%0.2 a a","x");
+ assertFieldMatch("significantOccurrence:1","a%0.2 a a","a");
+ assertFieldMatch("significantOccurrence:1","a a a","a a a");
+ assertFieldMatch("significantOccurrence:1","a%0.2 a a","a a a a");
+ assertFieldMatch("significantOccurrence:0.3571","a%0.2","x x x a x x a x a x x x a a");
+ assertFieldMatch("significantOccurrence:1","a%0.2","a a a a a a a a a a a a a a");
+ assertFieldMatch("significantOccurrence:0.5","a b","a b b a a a a a b a a b a a");
+
+ assertFieldMatch("significantOccurrence:0.5714","a%0.2 b","a b b a a a a a b a a b a a");
+ assertFieldMatch("significantOccurrence:0.6753","a%1 b","a b b a a a a a b a a b a a"); // Should be higher
+ assertFieldMatch("significantOccurrence:0.4286","a b%0.2","a b b a a a a a b a a b a a"); // Should be lower
+ assertFieldMatch("significantOccurrence:0.3247","a b%1","a b b a a a a a b a a b a a"); // Should be even lower
+
+ assertFieldMatch("significantOccurrence:0.30","a b", "a a b b b b x x x x");
+ assertFieldMatch("significantOccurrence:0.3333","a b%0.2","a a b b b b x x x x"); // More frequent is more important - higher
+ assertFieldMatch("significantOccurrence:0.2667","a%0.2 b","a a b b b b x x x x"); // Less frequent is more important - lower
+ assertFieldMatch("significantOccurrence:0.2667","a b%0.05", "a a b b b b x x x x"); // Same relative
+
+ assertFieldMatch("significantOccurrence:0","a%0 b%0", "a a b b b b x x x x");
+
+ // tests going beyond the occurrence limit
+ fieldmatch::Params params;
+ params.setMaxOccurrences(10);
+ assertFieldMatch("significantOccurrence:0.6","a b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("significantOccurrence:0.6","a b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("significantOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("significantOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("significantOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+
+ assertFieldMatch("significantOccurrence:0.7333","a%0.2 b","a a a a a a a a a a b b", &params);
+ assertFieldMatch("significantOccurrence:0.4667","a b%0.2","a a a a a a a a a a b b", &params);
+ assertFieldMatch("significantOccurrence:0.7333","a%0.2 b","a a a a a a a a a a a b b", &params); // Starting to cut off
+ assertFieldMatch("significantOccurrence:0.7333","a%0.2 b","a a a a a a a a a a a a a a a a a a a a a b b", &params); // Way beyond cutoff for a
+ assertFieldMatch("significantOccurrence:1", "a%0.2 b","a a a a a a a a a a b b b b b b b b b b", &params); // Exactly no cutoff
+ assertFieldMatch("significantOccurrence:1", "a%0.2 b","a a a a a a a a a a a b b b b b b b b b b b", &params); // Field is too large to consider field length
+}
+
+void
+Test::testFieldMatchExecutorUnweightedProximity()
+{
+ assertFieldMatch("unweightedProximity:1", "a","a");
+ assertFieldMatch("unweightedProximity:1", "a b c","a b c");
+ assertFieldMatch("unweightedProximity:1", "a b c","a b c x");
+ assertFieldMatch("unweightedProximity:1", "y a b c","a b c x");
+ assertFieldMatch("unweightedProximity:1", "y a b c","a b c x");
+ assertFieldMatch("unweightedProximity:0.855","y a b c","a b x c x");
+ assertFieldMatch("unweightedProximity:0.750","y a b c","a b x x c x");
+ assertFieldMatch("unweightedProximity:0.71", "y a b c","a x b x c x"); // Should be slightly worse than the previous one
+ assertFieldMatch("unweightedProximity:0.605","y a b c","a x b x x c x");
+ assertFieldMatch("unweightedProximity:0.53", "y a b c","a x b x x x c x");
+ assertFieldMatch("unweightedProximity:0.5", "y a b c","a x x b x x c x");
+}
+
+
+void
+Test::testFieldMatchExecutorReverseProximity()
+{
+ assertFieldMatch("unweightedProximity:0.33", "a b","b a");
+ assertFieldMatch("unweightedProximity:0.62", "a b c","c a b");
+ assertFieldMatch("unweightedProximity:0.585", "y a b c","c x a b");
+ assertFieldMatch("unweightedProximity:0.33", "a b c","c b a");
+ assertFieldMatch("unweightedProximity:0.6875","a b c d e","a b d c e");
+ assertFieldMatch("unweightedProximity:0.9275","a b c d e","a b x c d e");
+}
+
+
+void
+Test::testFieldMatchExecutorAbsoluteProximity()
+{
+ assertFieldMatch("absoluteProximity:0.1 proximity:1", "a b","a b");
+ assertFieldMatch("absoluteProximity:0.3 proximity:1", "a 0.3:b","a b");
+ assertFieldMatch("absoluteProximity:0.1 proximity:1", "a 0.0:b","a b");
+ assertFieldMatch("absoluteProximity:1 proximity:1", "a 1.0:b","a b");
+ assertFieldMatch("absoluteProximity:0.033 proximity:0.33", "a b","b a");
+ assertFieldMatch("absoluteProximity:0.0108 proximity:0.0359","a 0.3:b","b a"); // Should be worse than the previous one
+ assertFieldMatch("absoluteProximity:0.1 proximity:1", "a 0.0:b","b a");
+ assertFieldMatch("absoluteProximity:0 proximity:0", "a 1.0:b","b a");
+
+ assertFieldMatch("absoluteProximity:0.0605 proximity:0.605", "a b c","a x b x x c");
+ assertFieldMatch("absoluteProximity:0.0701 proximity:0.2003","a 0.5:b 0.2:c","a x b x x c"); // Most important is close, less important is far: Better
+ assertFieldMatch("absoluteProximity:0.0605 proximity:0.605", "a b c","a x x b x c");
+ assertFieldMatch("absoluteProximity:0.0582 proximity:0.1663","a 0.5:b 0.2:c","a x x b x c"); // Most important is far, less important is close: Worse
+
+ assertFieldMatch("absoluteProximity:0.0727 proximity:0.7267","a b c d","a b x x x x x c d");
+ assertFieldMatch("absoluteProximity:0.1 proximity:1", "a b 0:c d","a b x x x x x c d"); // Should be better because the gap is unimportant
+
+ // test with another proximity table
+ std::vector<feature_t> pt;
+ pt.push_back(0.2);
+ pt.push_back(0.4);
+ pt.push_back(0.6);
+ pt.push_back(0.8);
+ pt.push_back(1.0);
+ pt.push_back(0.8);
+ pt.push_back(0.6);
+ pt.push_back(0.4);
+ pt.push_back(0.2);
+ fieldmatch::Params params;
+ params.setProximityLimit(4);
+ params.setProximityTable(pt);
+ assertFieldMatch("absoluteProximity:0.07 proximity:0.7", "a b c","a x b x x c", &params);
+ assertFieldMatch("absoluteProximity:0.1179 proximity:0.3369","a 0.5:b 0.2:c","a x b x x c", &params); // Most important is close, less important is far: Better
+ assertFieldMatch("absoluteProximity:0.07 proximity:0.7", "a b c","a x x b x c", &params);
+ assertFieldMatch("absoluteProximity:0.0834 proximity:0.2384","a 0.5:b 0.2:c","a x x b x c", &params); // Most important is far, less important is close: Worse
+}
+
+
+void
+Test::testFieldMatchExecutorMultiSegmentProximity()
+{
+ assertFieldMatch("absoluteProximity:0.1 proximity:1", "a b c", "a b x x x x x x x x x x x x x x x x x x x x x x c");
+ assertFieldMatch("absoluteProximity:0.05 proximity:0.5","a b c", "a x x b x x x x x x x x x x x x x x x x x x x x x x c");
+ assertFieldMatch("absoluteProximity:0.075 proximity:0.75","a b c d","a x x b x x x x x x x x x x x x x x x x x x x x x x c d");
+}
+
+
+void
+Test::testFieldMatchExecutorSegmentDistance()
+{
+ assertFieldMatch("segmentDistance:13 absoluteProximity:0.1", "a b c","a b x x x x x x x x x x c");
+ assertFieldMatch("segmentDistance:13 absoluteProximity:0.5", "a 0.5:b c","a b x x x x x x x x x x c");
+ assertFieldMatch("segmentDistance:13 absoluteProximity:0.1", "a b c","b c x x x x x x x x x x a");
+ assertFieldMatch("segmentDistance:25 absoluteProximity:0.1", "a b c","b x x x x x x x x x x x a x x x x x x x x x x c");
+ assertFieldMatch("segmentDistance:13 absoluteProximity:0.006","a b c","a x x x x x x x x x x x b x x x x x x x x c");
+ assertFieldMatch("segmentDistance:24 absoluteProximity:0.1", "a b c","a x x x x x x x x x x x b x x x x x x x x x c");
+ assertFieldMatch("segmentDistance:25 absoluteProximity:0.1", "a b c","a x x x x x x x x x x x b x x x x x x x x x x c");
+ assertFieldMatch("segmentDistance:25 absoluteProximity:0.1", "a b c","c x x x x x x x x x x x b x x x x x x x x x x a");
+}
+
+
+void
+Test::testFieldMatchExecutorSegmentProximity()
+{
+ assertFieldMatch("segmentProximity:1", "a","a");
+ assertFieldMatch("segmentProximity:0", "a","x");
+ assertFieldMatch("segmentProximity:1", "a","a x");
+ assertFieldMatch("segmentProximity:0", "a b","a x x x x x x x x x x x x x x x x x x x x x x x b");
+ assertFieldMatch("segmentProximity:0.4","a b","a x x x x x x x x x x x x x x x x x x x x x x b x x x x x x x x x x x x x x x x");
+ assertFieldMatch("segmentProximity:0", "a b c","a b x x x x x x x x x x x x x x x x x x x x x c");
+ assertFieldMatch("segmentProximity:0.4","a b c","a b x x x x x x x x x x x x x x x x x x x x x c x x x x x x x x x x x x x x x x");
+ assertFieldMatch("segmentProximity:0.4","a b c","b c x x x x x x x x x x x x x x x x x x x x x a x x x x x x x x x x x x x x x x");
+}
+
+
+void
+Test::testFieldMatchExecutorSegmentStarts()
+{
+#ifdef FIELDMATCH_OUTPUTS_SEGMENTSTARTS
+ // Test cases where we choose between multiple different segmentations
+ { // test segmentSelection
+ assertFieldMatch("segments:2 absoluteProximity:0.1 proximity:1 segmentStarts:19,41",
+ "a b c d e","x a b x c x x x x x x x x x x x x x x a b c x x x x x x x x x e x d x c d x x x c d e");
+ // 0 1 2 3 4 5 6 7 8 9�0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+ // 0 1 2 3 4
+ // Should choose - - - - -
+
+ assertFieldMatch("segments:1 absoluteProximity:0.0778 proximity:0.778","a b c d e f","x x a b b b c f e d a b c d x e x x x x x f d e f a b c a a b b c c d d e e f f");
+
+ // Prefer one segment with ok proximity or two segments with great proximity
+ assertFieldMatch("segments:1 segmentStarts:0","a b c d","a b x c d x x x x x x x x x x x a b x x x x x x x x x x x c d");
+ assertFieldMatch("segments:1 segmentStarts:0","a b c d","a b x x x x x x x x c d x x x x x x x x x x x a b x x x x x x x x x x x c d");
+ }
+#endif
+}
+
+
+void
+Test::testFieldMatchExecutorMoreThanASegmentLengthOfUnmatchedQuery()
+{
+ assertFieldMatch("absoluteProximity:0.1 proximity:1","a b y y y y y y y y y y y y y y y","a b");
+ assertFieldMatch("segments:2 absoluteProximity:0.1 proximity:1","a b c d y y y y y y y y y y y y y y y","a b x x x x x x x x x x x x x x x x x x c d");
+ assertFieldMatch("segments:2 absoluteProximity:0.1 proximity:1","a b y y y y y y y y y y y y y y y c d","a b x x x x x x x x x x x x x x x x x x c d");
+}
+
+
+void
+Test::testFieldMatchExecutorQueryRepeats()
+{
+ // Not really handled perfectly, but good enough
+ assertFieldMatch("absoluteProximity:0.1 proximity:1 head:0 tail:0", "a a a","a");
+ assertFieldMatch("absoluteProximity:0.1 proximity:1 head:0 tail:0 gapLength:0","a a b c c","a a b c c");
+ assertFieldMatch("absoluteProximity:0.1 proximity:1 head:0 tail:0 gapLength:0","a a b c c","a b c");
+ assertFieldMatch("absoluteProximity:0.1 proximity:1 head:0 tail:0 gapLength:0","a b a b","a b a b");
+ assertFieldMatch("absoluteProximity:0.0903 proximity:0.9033 head:0 tail:0 gapLength:1","a b a b","a b x a b");
+ // Both terms take the same segment:
+ assertFieldMatch("absoluteProximity:0.1 proximity:1 segments:2 gapLength:0 head:3 tail:18","a a","x x x a x x x x x x x x x x x x x x a x x x");
+ // But not when the second is preferable
+ assertFieldMatch("absoluteProximity:0.1 proximity:1 segments:2 gapLength:0 head:3 tail:3","a b b a","x x x a b x x x x x x x x x x x x x x b a x x x");
+ assertFieldMatch("matches:2 fieldCompleteness:1","a b b b","a b");
+}
+
+
+void
+Test::testFieldMatchExecutorZeroCases()
+{
+ // Note: we have no hits -> absoluteProximity:0(0.1) proximity:0(1)
+ assertFieldMatch("absoluteProximity:0 proximity:0 matches:0","y","a");
+ assertFieldMatch("absoluteProximity:0 proximity:0 matches:0","a","x");
+ assertFieldMatch("absoluteProximity:0 proximity:0 matches:0","","x");
+ assertFieldMatch("absoluteProximity:0 proximity:0 matches:0","y","");
+ assertFieldMatch("absoluteProximity:0 proximity:0 matches:0","","");
+}
+
+
+void
+Test::testFieldMatchExecutorExceedingIterationLimit()
+{
+ // Segments found: a x x b and c d
+ {
+ fieldmatch::Params params;
+ params.setMaxAlternativeSegmentations(0);
+ assertFieldMatch("matches:4 tail:0 proximity:0.75 absoluteProximity:0.075","a b c d","a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d", &params);
+ }
+
+ // Segments found: a x b and c d
+ {
+ fieldmatch::Params params;
+ params.setMaxAlternativeSegmentations(1);
+ assertFieldMatch("matches:4 tail:0 proximity:0.855 absoluteProximity:0.0855","a b c d","a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d", &params);
+ }
+
+ // Segments found: a b and c d
+ {
+ fieldmatch::Params params;
+ params.setMaxAlternativeSegmentations(2);
+ assertFieldMatch("matches:4 tail:0 proximity:1 absoluteProximity:0.1","a b c d","a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d", &params);
+ }
+}
+
+
+void
+Test::testFieldMatchExecutorRemaining()
+{
+
+ { // test match (aka score)
+ // Ordered by decreasing match score per query
+ assertFieldMatch("score:1", "a","a");
+ assertFieldMatch("score:0.9339","a","a x");
+ assertFieldMatch("score:0", "a","x");
+ assertFieldMatch("score:0.9243","a","x a");
+ assertFieldMatch("score:0.9025","a","x a x");
+
+ assertFieldMatch("score:1", "a b","a b");
+ assertFieldMatch("score:0.9558","a b","a b x");
+ assertFieldMatch("score:0.9463","a b","x a b");
+ assertFieldMatch("score:0.1296","a b","a x x x x x x x x x x x x x x x x x x x x x x b");
+ assertFieldMatch("score:0.1288","a b","a x x x x x x x x x x x x x x x x x x x x x x x x x x x b");
+
+ assertFieldMatch("score:0.8647","a b c","x x a x b x x x x x x x x a b c x x x x x x x x c x x");
+ assertFieldMatch("score:0.861", "a b c","x x a x b x x x x x x x x x x a b c x x x x x x c x x");
+ assertFieldMatch("score:0.4869","a b c","a b x x x x x x x x x x x x x x x x x x x x x x c x x");
+ assertFieldMatch("score:0.4853","a b c","x x a x b x x x x x x x x x x b a c x x x x x x c x x");
+ assertFieldMatch("score:0.3621","a b c","a x b x x x x x x x x x x x x x x x x x x x x x c x x");
+ assertFieldMatch("score:0.3619","a b c","x x a x b x x x x x x x x x x x x x x x x x x x c x x");
+ assertFieldMatch("score:0.3584","a b c","x x a x b x x x x x x x x x x x x x x x x x x x x x c");
+ assertFieldMatch("score:0.3421","a b c","x x a x b x x x x x x x x x x x x x x x x x x x x x x");
+
+ assertFieldMatch("score:0.3474","a b c","x x a x b x x x x x x x x x x x x x x b x x x b x b x");
+ }
+
+ { // test repeated match
+ // gap==1 caused by finding two possible segments due to repeated matching
+ assertFieldMatch("fieldCompleteness:1 queryCompleteness:0.6667 segments:1 earliness:1 gaps:1",
+ "pizza hut pizza","pizza hut");
+ }
+
+ //------------------- extra tests -------------------//
+
+ { // test with a query on an attribute field
+ LOG(info, "Query on an attribute field");
+ vespalib::string feature = "fieldMatch(foo)";
+ FtFeatureTest ft(_factory, feature);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+ ft.getQueryEnv().getBuilder().addAttributeNode("bar");
+ ASSERT_TRUE(ft.setup());
+ ASSERT_TRUE(ft.execute(toRankResult(feature, "score:0")));
+ }
+
+
+ { // test with query on another index field as well
+ LOG(info, "Query on an another index field");
+ FtFeatureTest ft(_factory, StringList().add("fieldMatch(foo)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")); // search on 'foo' (0)
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("bar")); // search on 'bar' (1)
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ // add occurrence for 'foo' with query=a
+ ASSERT_TRUE(mdb->setFieldLength("foo", 1));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 0)); // a
+
+ // add occurrence for 'bar' with query=a
+ ASSERT_TRUE(mdb->setFieldLength("bar", 2));
+ ASSERT_TRUE(mdb->addOccurence("bar", 1, 1)); // x a
+
+ ASSERT_TRUE(mdb->apply(1));
+
+ ASSERT_TRUE(ft.execute(toRankResult("fieldMatch(foo)", "score:1 matches:1 queryCompleteness:1 fieldCompleteness:1")));
+ ASSERT_TRUE(ft.execute(toRankResult("fieldMatch(foo)", "score:0"), 2)); // another docid -> no hit -> default values
+ }
+
+ { // search on more than one document
+ LOG(info, "Query on more than one document");
+ FtFeatureTest ft(_factory, StringList().add("fieldMatch(foo)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")); // 'a' (0)
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")); // 'b' (1)
+ ASSERT_TRUE(ft.setup());
+
+ // check that we get the same results as this
+ // assertFieldMatch("score:1", "a b","a b");
+ // assertFieldMatch("score:0.9558","a b","a b x");
+ // assertFieldMatch("score:0.932", "a b","x a b");
+
+ { // docid 1: "a b"
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 2));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 0)); // 'a'
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, 1)); // 'b'
+ ASSERT_TRUE(mdb->apply(1));
+ ASSERT_TRUE(ft.execute(toRankResult("fieldMatch(foo)", "score:1 matches:2"), 1));
+ }
+ { // docid 2: "a b x"
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 3));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 0)); // 'a'
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, 1)); // 'b'
+ ASSERT_TRUE(mdb->apply(1));
+ RankResult rr = toRankResult("fieldMatch(foo)", "score:0.9558 matches:2");
+ rr.setEpsilon(1e-4); // same as java tests
+ ASSERT_TRUE(ft.execute(rr, 1));
+ }
+ { // docid 3: "x a b"
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 3));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 1)); // 'a'
+ ASSERT_TRUE(mdb->addOccurence("foo", 1, 2)); // 'b'
+ ASSERT_TRUE(mdb->apply(2));
+ RankResult rr = toRankResult("fieldMatch(foo)", "score:0.9463 matches:2");
+ rr.setEpsilon(1e-4); // same as java tests
+ ASSERT_TRUE(ft.execute(rr, 2));
+ }
+ }
+
+ { // test where not all hits have position information
+ LOG(info, "Not all hits have position information");
+ FtFeatureTest ft(_factory, StringList().add("fieldMatch(foo)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo"))->setWeight(search::query::Weight(200)); // search for 'a' (termId 0)
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo"))->setWeight(search::query::Weight(400)); // search for 'b' (termId 1)
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo"))->setWeight(search::query::Weight(600)); // search for 'c' (termId 2)
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo"))->setWeight(search::query::Weight(800)); // search for 'd' (termId 3)
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("bar"))->setWeight(search::query::Weight(1000)); // search for 'e' (termId 4)
+ ASSERT_TRUE(ft.setup());
+
+ assertFieldMatch("score:0.3389 completeness:0.5083 degradedMatches:0", "a b c d", "x a b");
+
+ // field: x a b
+ { // no pos occ for term b -> score is somewhat degraded (lower .occurrence)
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ // add occurrence with query term 'a'
+ ASSERT_TRUE(mdb->setFieldLength("foo", 3));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 1));
+ // add hit with query term 'b'
+ mdb->getTermFieldMatchData(1, 0)->reset(1);
+ ASSERT_TRUE(mdb->apply(1));
+ ASSERT_TRUE(ft.execute(toRankResult("fieldMatch(foo)",
+ "score:0.3231 completeness:0.5083 queryCompleteness:0.5 weight:0.2 matches:2 degradedMatches:1").
+ setEpsilon(1e-4)));
+ }
+ { // no pos occ for term a & b
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ // add hit with query term 'a'
+ mdb->getTermFieldMatchData(0, 0)->reset(1);
+ // add hit with query term 'b'
+ mdb->getTermFieldMatchData(1, 0)->reset(1);
+ ASSERT_TRUE(mdb->apply(1));
+ ASSERT_TRUE(ft.execute(toRankResult("fieldMatch(foo)",
+ "score:0 completeness:0.475 queryCompleteness:0.5 weight:0.2 matches:2 degradedMatches:2").
+ setEpsilon(1e-4)));
+ }
+ }
+
+ { // invalid field length
+ LOG(info, "We have an invalid field length");
+ FtFeatureTest ft(_factory, StringList().add("fieldMatch(foo)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo"))->setWeight(search::query::Weight(100)); // search for 'a' (termId 0)
+ ASSERT_TRUE(ft.setup());
+
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ // add occurrence with query term 'a'
+ ASSERT_TRUE(mdb->setFieldLength("foo", search::fef::FieldPositionsIterator::UNKNOWN_LENGTH)); // invalid field length
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 10));
+
+ ASSERT_TRUE(mdb->apply(1));
+
+ ASSERT_TRUE(ft.execute(toRankResult("fieldMatch(foo)", "score:0 matches:1 degradedMatches:0")));
+ }
+
+ { // test default values when we do not have hits in the field
+ LOG(info, "Default values when we have no hits");
+ FtFeatureTest ft(_factory, StringList().add("fieldMatch(foo)"));
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo")); // search on 'foo' (0)
+ ASSERT_TRUE(ft.setup());
+
+ // must create this so that term match data is configured with the term data object
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+
+ RankResult rr = toRankResult("fieldMatch(foo)",
+ "score:0 "
+ "proximity:0 "
+ "completeness:0 "
+ "queryCompleteness:0 "
+ "fieldCompleteness:0 "
+ "orderness:0 "
+ "relatedness:0 "
+ "earliness:0 "
+ "longestSequenceRatio:0 "
+ "segmentProximity:0 "
+ "unweightedProximity:0 "
+ "absoluteProximity:0 "
+ "occurrence:0 "
+ "absoluteOccurrence:0 "
+ "weightedOccurrence:0 "
+ "weightedAbsoluteOccurrence:0 "
+ "significantOccurrence:0 "
+ "weight:0 "
+ "significance:0 "
+ "importance:0 "
+ "segments:0 "
+ "matches:0 "
+ "outOfOrder:0 "
+ "gaps:0 "
+ "gapLength:0 "
+ "longestSequence:0 "
+ "head:0 "
+ "tail:0 "
+ "segmentDistance:0 ")
+ .setEpsilon(10e-6);
+
+ ASSERT_TRUE(ft.execute(rr, 1)); // another docid -> no hit -> default values
+ }
+}
diff --git a/searchlib/src/tests/features/prod_features_fieldtermmatch.cpp b/searchlib/src/tests/features/prod_features_fieldtermmatch.cpp
new file mode 100644
index 00000000000..04caadd2029
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features_fieldtermmatch.cpp
@@ -0,0 +1,113 @@
+// 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(".prod_features_fieldtermmatch");
+
+#include "prod_features.h"
+#include <vespa/searchlib/features/fieldtermmatchfeature.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+void
+Test::testFieldTermMatch()
+{
+ {
+ // Test blueprint.
+ FieldTermMatchBlueprint pt;
+ {
+ EXPECT_TRUE(assertCreateInstance(pt, "fieldTermMatch"));
+
+ StringList params, in, out;
+ FT_SETUP_FAIL(pt, params);
+ FT_SETUP_FAIL(pt, params.add("foo"));
+ FT_SETUP_FAIL(pt, params.add("0"));
+ FT_SETUP_FAIL(pt, params.add("1"));
+ params.clear();
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FT_SETUP_FAIL(pt, ie, params.add("foo"));
+ FT_SETUP_OK (pt, ie, params.add("0"), in,
+ out.add("firstPosition")
+ .add("lastPosition")
+ .add("occurrences").add("weight").add("exactness"));
+ FT_SETUP_FAIL(pt, ie, params.add("1"));
+ }
+ {
+ FT_DUMP_EMPTY(_factory, "fieldTermMatch");
+
+ FtIndexEnvironment ie;
+ ie.getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "foo");
+ FT_DUMP_EMPTY(_factory, "fieldTermMatch", ie); // must be an index field
+
+ StringList dump;
+ ie.getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "bar");
+ for (uint32_t term = 0; term < 5; ++term) {
+ vespalib::string bn = vespalib::make_string("fieldTermMatch(bar,%u)", term);
+ dump.add(bn + ".firstPosition").add(bn + ".occurrences").add(bn + ".weight");
+ }
+ FT_DUMP(_factory, "fieldTermMatch", ie, dump);
+
+ ie.getProperties().add("fieldTermMatch.numTerms", "0");
+ FT_DUMP_EMPTY(_factory, "fieldTermMatch", ie);
+
+ ie.getProperties().add("fieldTermMatch.numTerms.bar", "5");
+ FT_DUMP(_factory, "fieldTermMatch", ie, dump);
+ }
+ }
+
+ { // Test executor.
+ FtFeatureTest ft(_factory, "fieldTermMatch(foo,0)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ASSERT_TRUE(ft.setup());
+ RankResult exp;
+ exp .addScore("fieldTermMatch(foo,0).firstPosition", 1000000)
+ .addScore("fieldTermMatch(foo,0).lastPosition", 1000000)
+ .addScore("fieldTermMatch(foo,0).occurrences", 0)
+ .addScore("fieldTermMatch(foo,0).weight", 0)
+ .addScore("fieldTermMatch(foo,0).exactness", 0);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ {
+ // Test executor.
+ FtFeatureTest ft(_factory, "fieldTermMatch(foo,0)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addAllFields();
+ ASSERT_TRUE(ft.setup());
+
+ search::fef::test::MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ ASSERT_TRUE(mdb->setFieldLength("foo", 100));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 10));
+ ASSERT_TRUE(mdb->addOccurence("foo", 0, 20));
+ ASSERT_TRUE(mdb->apply(1));
+
+ search::fef::test::RankResult exp;
+ exp .addScore("fieldTermMatch(foo,0).firstPosition", 10)
+ .addScore("fieldTermMatch(foo,0).lastPosition", 20)
+ .addScore("fieldTermMatch(foo,0).occurrences", 2)
+ .addScore("fieldTermMatch(foo,0).weight", 2)
+ .addScore("fieldTermMatch(foo,0).exactness", 1);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+ {
+ // Test executor (match without position information)
+ FtFeatureTest ft(_factory, "fieldTermMatch(foo,0)");
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ ft.getQueryEnv().getBuilder().addIndexNode(StringList().add("foo"));
+ ASSERT_TRUE(ft.setup());
+
+ // make sure the term match data is initialized with the term data
+ MatchDataBuilder::UP mdb = ft.createMatchDataBuilder();
+ mdb->getTermFieldMatchData(0, 0)->reset(1);
+
+ search::fef::test::RankResult exp;
+ exp .addScore("fieldTermMatch(foo,0).firstPosition", 1000000)
+ .addScore("fieldTermMatch(foo,0).lastPosition", 1000000)
+ .addScore("fieldTermMatch(foo,0).occurrences", 1)
+ .addScore("fieldTermMatch(foo,0).weight", 0)
+ .addScore("fieldTermMatch(foo,0).exactness", 0);
+ ASSERT_TRUE(ft.execute(exp));
+ }
+}
diff --git a/searchlib/src/tests/features/prod_features_framework.cpp b/searchlib/src/tests/features/prod_features_framework.cpp
new file mode 100644
index 00000000000..5ce5e2c3177
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features_framework.cpp
@@ -0,0 +1,174 @@
+// 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(".prod_features_framework");
+
+#include "prod_features.h"
+#include <vespa/searchlib/features/valuefeature.h>
+
+using namespace search::features;
+using namespace search::fef;
+using namespace search::fef::test;
+
+void
+Test::testFramework()
+{
+ LOG(info, "testFramework()");
+ IndexEnvironment indexEnv;
+ { // test index environment builder
+ IndexEnvironmentBuilder ieb(indexEnv);
+ ieb.addField(FieldType::INDEX, CollectionType::SINGLE, "foo")
+ .addField(FieldType::ATTRIBUTE, CollectionType::WEIGHTEDSET, "bar")
+ .addField(FieldType::INDEX, CollectionType::ARRAY, "baz");
+ {
+ const FieldInfo * info = indexEnv.getFieldByName("foo");
+ ASSERT_TRUE(info != NULL);
+ EXPECT_EQUAL(info->id(), 0u);
+ EXPECT_TRUE(info->type() == FieldType::INDEX);
+ EXPECT_TRUE(info->collection() == CollectionType::SINGLE);
+ }
+ {
+ const FieldInfo * info = indexEnv.getFieldByName("bar");
+ ASSERT_TRUE(info != NULL);
+ EXPECT_EQUAL(info->id(), 1u);
+ EXPECT_TRUE(info->type() == FieldType::ATTRIBUTE);
+ EXPECT_TRUE(info->collection() == CollectionType::WEIGHTEDSET);
+ }
+ {
+ const FieldInfo * info = indexEnv.getFieldByName("baz");
+ ASSERT_TRUE(info != NULL);
+ EXPECT_EQUAL(info->id(), 2u);
+ EXPECT_TRUE(info->type() == FieldType::INDEX);
+ EXPECT_TRUE(info->collection() == CollectionType::ARRAY);
+ }
+ ASSERT_TRUE(indexEnv.getFieldByName("qux") == NULL);
+ }
+
+ QueryEnvironment queryEnv(&indexEnv);
+ MatchDataLayout layout;
+ { // test query environment builder
+ QueryEnvironmentBuilder qeb(queryEnv, layout);
+ {
+ SimpleTermData &tr = qeb.addAllFields();
+ ASSERT_TRUE(tr.lookupField(0) != 0);
+ ASSERT_TRUE(tr.lookupField(1) != 0);
+ ASSERT_TRUE(tr.lookupField(2) != 0);
+ EXPECT_TRUE(tr.lookupField(3) == 0);
+ EXPECT_TRUE(tr.lookupField(0)->getHandle() == 0u);
+ EXPECT_TRUE(tr.lookupField(1)->getHandle() == 1u);
+ EXPECT_TRUE(tr.lookupField(2)->getHandle() == 2u);
+ const ITermData *tp = queryEnv.getTerm(0);
+ ASSERT_TRUE(tp != NULL);
+ EXPECT_EQUAL(tp, &tr);
+ }
+ {
+ SimpleTermData *tr = qeb.addAttributeNode("bar");
+ ASSERT_TRUE(tr != 0);
+ ASSERT_TRUE(tr->lookupField(1) != 0);
+ EXPECT_TRUE(tr->lookupField(0) == 0);
+ EXPECT_TRUE(tr->lookupField(2) == 0);
+ EXPECT_TRUE(tr->lookupField(3) == 0);
+ EXPECT_TRUE(tr->lookupField(1)->getHandle() == 3u);
+ const ITermData *tp = queryEnv.getTerm(1);
+ ASSERT_TRUE(tp != NULL);
+ EXPECT_EQUAL(tp, tr);
+ }
+ }
+
+ MatchData::UP data = layout.createMatchData();
+ EXPECT_EQUAL(data->getNumTermFields(), 4u);
+ EXPECT_EQUAL(data->getNumFeatures(), 0u);
+
+ { // check match data access
+ MatchDataBuilder mdb(queryEnv, *data);
+
+ // setup some occurence lists
+ ASSERT_TRUE(mdb.addOccurence("foo", 0, 20));
+ ASSERT_TRUE(mdb.addOccurence("foo", 0, 10));
+ ASSERT_TRUE(mdb.setFieldLength("foo", 50));
+ ASSERT_TRUE(mdb.addOccurence("baz", 0, 15));
+ ASSERT_TRUE(mdb.addOccurence("baz", 0, 5));
+ ASSERT_TRUE(mdb.setFieldLength("baz", 100));
+ ASSERT_TRUE(mdb.apply(100));
+
+ {
+ {
+ TermFieldMatchData *tfmd = mdb.getTermFieldMatchData(0, 0);
+ ASSERT_TRUE(tfmd != NULL);
+
+ FieldPositionsIterator itr = tfmd->getIterator(); // foo (index)
+ ASSERT_TRUE(itr.valid());
+ EXPECT_EQUAL(itr.getFieldLength(), 50u);
+ EXPECT_EQUAL(itr.getPosition(), 10u);
+ itr.next();
+ ASSERT_TRUE(itr.valid());
+ EXPECT_EQUAL(itr.getPosition(), 20u);
+ itr.next();
+ ASSERT_TRUE(!itr.valid());
+ }
+ {
+ TermFieldMatchData *tfmd = mdb.getTermFieldMatchData(0, 1);
+ ASSERT_TRUE(tfmd != NULL);
+
+ FieldPositionsIterator itr = tfmd->getIterator(); // bar (attribute)
+ ASSERT_TRUE(!itr.valid());
+ }
+ {
+ TermFieldMatchData *tfmd = mdb.getTermFieldMatchData(0, 2);
+ ASSERT_TRUE(tfmd != NULL);
+
+ FieldPositionsIterator itr = tfmd->getIterator(); // baz (index)
+ ASSERT_TRUE(itr.valid());
+ EXPECT_EQUAL(itr.getFieldLength(), 100u);
+ EXPECT_EQUAL(itr.getPosition(), 5u);
+ itr.next();
+ ASSERT_TRUE(itr.valid());
+ EXPECT_EQUAL(itr.getPosition(), 15u);
+ itr.next();
+ ASSERT_TRUE(!itr.valid());
+ }
+ }
+ {
+ TermFieldMatchData *tfmd = mdb.getTermFieldMatchData(1, 1);
+ ASSERT_TRUE(tfmd != NULL);
+
+ FieldPositionsIterator itr = tfmd->getIterator(); // bar (attribute)
+ ASSERT_TRUE(!itr.valid());
+ }
+ }
+ { // check that data is cleared
+ MatchDataBuilder mdb(queryEnv, *data);
+ EXPECT_EQUAL(mdb.getTermFieldMatchData(0, 0)->getDocId(), TermFieldMatchData::invalidId());
+ EXPECT_EQUAL(mdb.getTermFieldMatchData(0, 1)->getDocId(), TermFieldMatchData::invalidId());
+ EXPECT_EQUAL(mdb.getTermFieldMatchData(0, 2)->getDocId(), TermFieldMatchData::invalidId());
+ EXPECT_EQUAL(mdb.getTermFieldMatchData(1, 1)->getDocId(), TermFieldMatchData::invalidId());
+
+ // test illegal things
+ ASSERT_TRUE(!mdb.addOccurence("foo", 1, 10)); // invalid term/field combination
+ }
+
+ BlueprintFactory factory;
+ factory.addPrototype(Blueprint::SP(new ValueBlueprint()));
+ Properties overrides;
+
+ { // test feature test runner
+ FeatureTest ft(factory, indexEnv, queryEnv, layout,
+ StringList().add("value(10)").add("value(20)").add("value(30)"), overrides);
+ MatchDataBuilder::UP mdb1 = ft.createMatchDataBuilder();
+ EXPECT_TRUE(mdb1.get() == NULL);
+ EXPECT_TRUE(!ft.execute(RankResult().addScore("value(10)", 10.0f)));
+ ASSERT_TRUE(ft.setup());
+ MatchDataBuilder::UP mdb2 = ft.createMatchDataBuilder();
+ EXPECT_TRUE(mdb2.get() != NULL);
+
+ EXPECT_TRUE(ft.execute(RankResult().addScore("value(10)", 10.0f).addScore("value(20)", 20.0f)));
+ EXPECT_TRUE(!ft.execute(RankResult().addScore("value(10)", 20.0f)));
+ EXPECT_TRUE(!ft.execute(RankResult().addScore("value(5)", 5.0f)));
+ }
+ { // test simple constructor
+ MatchDataLayout mdl; // match data layout cannot be reused
+ FeatureTest ft(factory, indexEnv, queryEnv, mdl, "value(10)", overrides);
+ ASSERT_TRUE(ft.setup());
+ EXPECT_TRUE(ft.execute(10.0f));
+ }
+}
diff --git a/searchlib/src/tests/features/prod_features_test.sh b/searchlib/src/tests/features/prod_features_test.sh
new file mode 100755
index 00000000000..bec2b49807f
--- /dev/null
+++ b/searchlib/src/tests/features/prod_features_test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+VESPA_LOG_TARGET=file:vlog2.txt $VALGRIND ./searchlib_prod_features_test_app
+rm -rf *.dat
diff --git a/searchlib/src/tests/features/ranking_expression/.gitignore b/searchlib/src/tests/features/ranking_expression/.gitignore
new file mode 100644
index 00000000000..63ab51e663a
--- /dev/null
+++ b/searchlib/src/tests/features/ranking_expression/.gitignore
@@ -0,0 +1 @@
+searchlib_ranking_expression_test_app
diff --git a/searchlib/src/tests/features/ranking_expression/CMakeLists.txt b/searchlib/src/tests/features/ranking_expression/CMakeLists.txt
new file mode 100644
index 00000000000..4caddaa7bd8
--- /dev/null
+++ b/searchlib/src/tests/features/ranking_expression/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_ranking_expression_test_app
+ SOURCES
+ ranking_expression_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_ranking_expression_test_app COMMAND searchlib_ranking_expression_test_app)
diff --git a/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp b/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp
new file mode 100644
index 00000000000..64fb3477951
--- /dev/null
+++ b/searchlib/src/tests/features/ranking_expression/ranking_expression_test.cpp
@@ -0,0 +1,90 @@
+// 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/vespalib/testkit/test_kit.h>
+
+#include <vespa/vespalib/eval/value_type.h>
+#include <vespa/searchlib/fef/feature_type.h>
+#include <vespa/searchlib/fef/featurenameparser.h>
+#include <vespa/searchlib/features/rankingexpressionfeature.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+
+using namespace search::features;
+using namespace search::fef::test;
+using namespace search::fef;
+using namespace vespalib::eval;
+
+using TypeMap = std::map<vespalib::string,vespalib::string>;
+
+struct SetupResult {
+ IndexEnvironment index_env;
+ RankingExpressionBlueprint rank;
+ DummyDependencyHandler deps;
+ bool setup_ok;
+ SetupResult(const TypeMap &object_inputs,
+ const vespalib::string &expression)
+ : index_env(), rank(), deps(rank), setup_ok(false)
+ {
+ rank.setName("self");
+ index_env.getProperties().add("self.rankingScript", expression);
+ for (const auto &input: object_inputs) {
+ deps.define_object_input(input.first, ValueType::from_spec(input.second));
+ }
+ setup_ok = rank.setup(index_env, {});
+ EXPECT_TRUE(!deps.accept_type_mismatch);
+ }
+};
+
+void verify_output_type(const TypeMap &object_inputs,
+ const vespalib::string &expression, const FeatureType &expect)
+{
+ SetupResult result(object_inputs, expression);
+ EXPECT_TRUE(result.setup_ok);
+ EXPECT_EQUAL(1u, result.deps.output.size());
+ ASSERT_EQUAL(1u, result.deps.output_type.size());
+ if (expect.is_object()) {
+ EXPECT_EQUAL(expect.type(), result.deps.output_type[0].type());
+ } else {
+ EXPECT_TRUE(!result.deps.output_type[0].is_object());
+ }
+}
+
+void verify_setup_fail(const TypeMap &object_inputs,
+ const vespalib::string &expression)
+{
+ SetupResult result(object_inputs, expression);
+ EXPECT_TRUE(!result.setup_ok);
+ EXPECT_EQUAL(0u, result.deps.output.size());
+}
+
+TEST("require that expression with only number inputs produce number output (compiled)") {
+ TEST_DO(verify_output_type({}, "a*b", FeatureType::number()));
+}
+
+TEST("require that expression with object input produces object output (interpreted)") {
+ TEST_DO(verify_output_type({{"b", "double"}}, "a*b", FeatureType::object(ValueType::double_type())));
+}
+
+TEST("require that expression with internal tensor operations produce object output (interpreted)") {
+ TEST_DO(verify_output_type({}, "a*b*sum({{x:1}:5,{x:2}:7})", FeatureType::object(ValueType::double_type())));
+}
+
+TEST("require that ranking expression can resolve to concrete complex type") {
+ TEST_DO(verify_output_type({{"a", "tensor(x{},y{})"}, {"b", "tensor(y{},z{})"}}, "a*b",
+ FeatureType::object(ValueType::from_spec("tensor(x{},y{},z{})"))));
+}
+
+TEST("require that ranking expression can resolve to abstract complex type") {
+ TEST_DO(verify_output_type({{"a", "tensor"}}, "a*b", FeatureType::object(ValueType::from_spec("tensor"))));
+}
+
+TEST("require that ranking expression can resolve to 'any' type") {
+ TEST_DO(verify_output_type({{"a", "tensor(x{},y{})"}, {"b", "tensor"}}, "a*b",
+ FeatureType::object(ValueType::from_spec("any"))));
+}
+
+TEST("require that setup fails for incompatible types") {
+ TEST_DO(verify_setup_fail({{"a", "tensor(x{},y{})"}, {"b", "tensor(y[10],z{})"}}, "a*b"));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/raw_score/.gitignore b/searchlib/src/tests/features/raw_score/.gitignore
new file mode 100644
index 00000000000..a1b2d4e3f16
--- /dev/null
+++ b/searchlib/src/tests/features/raw_score/.gitignore
@@ -0,0 +1 @@
+searchlib_raw_score_test_app
diff --git a/searchlib/src/tests/features/raw_score/CMakeLists.txt b/searchlib/src/tests/features/raw_score/CMakeLists.txt
new file mode 100644
index 00000000000..a672b7b071d
--- /dev/null
+++ b/searchlib/src/tests/features/raw_score/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_raw_score_test_app
+ SOURCES
+ raw_score_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_raw_score_test_app COMMAND searchlib_raw_score_test_app)
diff --git a/searchlib/src/tests/features/raw_score/FILES b/searchlib/src/tests/features/raw_score/FILES
new file mode 100644
index 00000000000..479927259ee
--- /dev/null
+++ b/searchlib/src/tests/features/raw_score/FILES
@@ -0,0 +1 @@
+raw_score_test.cpp
diff --git a/searchlib/src/tests/features/raw_score/raw_score_test.cpp b/searchlib/src/tests/features/raw_score/raw_score_test.cpp
new file mode 100644
index 00000000000..0a15ff69318
--- /dev/null
+++ b/searchlib/src/tests/features/raw_score/raw_score_test.cpp
@@ -0,0 +1,151 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/raw_score_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+const std::string featureName("rawScore(foo)");
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ virtual void visitDumpFeature(const vespalib::string &) {
+ TEST_ERROR("no features should be dumped");
+ }
+ FeatureDumpFixture() : IDumpFeatureVisitor() {}
+};
+
+struct RankFixture : BlueprintFactoryFixture, IndexFixture {
+ QueryEnvironment queryEnv;
+ RankSetup rankSetup;
+ RankProgram::UP rankProgram;
+ MatchDataLayout mdl;
+ std::vector<TermFieldHandle> fooHandles;
+ std::vector<TermFieldHandle> barHandles;
+ RankFixture(size_t fooCnt, size_t barCnt)
+ : queryEnv(&indexEnv), rankSetup(factory, indexEnv),
+ rankProgram(), mdl(), fooHandles(), barHandles()
+ {
+ for (size_t i = 0; i < fooCnt; ++i) {
+ uint32_t fieldId = indexEnv.getFieldByName("foo")->id();
+ fooHandles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.addField(fieldId).setHandle(fooHandles.back());
+ queryEnv.getTerms().push_back(term);
+ }
+ for (size_t i = 0; i < barCnt; ++i) {
+ uint32_t fieldId = indexEnv.getFieldByName("bar")->id();
+ barHandles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.addField(fieldId).setHandle(barHandles.back());
+ queryEnv.getTerms().push_back(term);
+ }
+ rankSetup.setFirstPhaseRank(featureName);
+ rankSetup.setIgnoreDefaultRankFeatures(true);
+ ASSERT_TRUE(rankSetup.compile());
+ rankProgram = rankSetup.create_first_phase_program();
+ rankProgram->setup(mdl, queryEnv);
+ }
+ feature_t getScore(uint32_t docId) {
+ rankProgram->run(docId);
+ return *Utils::getScoreFeature(*rankProgram);
+ }
+ void setScore(TermFieldHandle handle, uint32_t docId, feature_t score) {
+ rankProgram->match_data().resolveTermField(handle)->setRawScore(docId, score);
+ }
+ void setFooScore(uint32_t i, uint32_t docId, feature_t score) {
+ ASSERT_LESS(i, fooHandles.size());
+ setScore(fooHandles[i], docId, score);
+ }
+ void setBarScore(uint32_t i, uint32_t docId, feature_t score) {
+ ASSERT_LESS(i, barHandles.size());
+ setScore(barHandles[i], docId, score);
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("rawScore");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<RawScoreBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that no features are dumped", RawScoreBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+}
+
+TEST_FF("require that setup can be done on index field", RawScoreBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "foo")));
+}
+
+TEST_FF("require that setup can be done on attribute field", RawScoreBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(bar)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "bar")));
+}
+
+TEST_FF("require that setup fails for unknown field", RawScoreBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(unknown)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(!((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "unknown")));
+}
+
+TEST_F("require that not searching a filed will give it 0.0 raw score", RankFixture(0, 3)) {
+ EXPECT_EQUAL(0.0, f1.getScore(10));
+}
+
+TEST_F("require that raw score can be obtained", RankFixture(1, 0)) {
+ f1.setFooScore(0, 10, 5.0);
+ EXPECT_EQUAL(5.0, f1.getScore(10));
+}
+
+TEST_F("require that multiple raw scores are accumulated", RankFixture(3, 0)) {
+ f1.setFooScore(0, 10, 1.0);
+ f1.setFooScore(1, 10, 2.0);
+ f1.setFooScore(2, 10, 3.0);
+ EXPECT_EQUAL(6.0, f1.getScore(10));
+}
+
+TEST_F("require that stale raw scores are ignored", RankFixture(3, 0)) {
+ f1.setFooScore(0, 10, 1.0);
+ f1.setFooScore(1, 9, 2.0);
+ f1.setFooScore(2, 10, 3.0);
+ EXPECT_EQUAL(4.0, f1.getScore(10));
+}
+
+TEST_F("require that raw scores from other fields are ignored", RankFixture(2, 2)) {
+ f1.setFooScore(0, 10, 1.0);
+ f1.setFooScore(1, 10, 2.0);
+ f1.setBarScore(0, 10, 5.0);
+ f1.setBarScore(1, 10, 6.0);
+ EXPECT_EQUAL(3.0, f1.getScore(10));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/subqueries/.gitignore b/searchlib/src/tests/features/subqueries/.gitignore
new file mode 100644
index 00000000000..63dc19177d1
--- /dev/null
+++ b/searchlib/src/tests/features/subqueries/.gitignore
@@ -0,0 +1 @@
+searchlib_subqueries_test_app
diff --git a/searchlib/src/tests/features/subqueries/CMakeLists.txt b/searchlib/src/tests/features/subqueries/CMakeLists.txt
new file mode 100644
index 00000000000..45845e8ec1b
--- /dev/null
+++ b/searchlib/src/tests/features/subqueries/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_subqueries_test_app
+ SOURCES
+ subqueries_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_subqueries_test_app COMMAND searchlib_subqueries_test_app)
diff --git a/searchlib/src/tests/features/subqueries/subqueries_test.cpp b/searchlib/src/tests/features/subqueries/subqueries_test.cpp
new file mode 100644
index 00000000000..160ec404b20
--- /dev/null
+++ b/searchlib/src/tests/features/subqueries/subqueries_test.cpp
@@ -0,0 +1,162 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/subqueries_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "bar");
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ virtual void visitDumpFeature(const vespalib::string &) {
+ TEST_ERROR("no features should be dumped");
+ }
+ FeatureDumpFixture() : IDumpFeatureVisitor() {}
+};
+
+struct RankFixture : BlueprintFactoryFixture, IndexFixture {
+ QueryEnvironment queryEnv;
+ RankSetup rankSetup;
+ RankProgram::UP rankProgram;
+ MatchDataLayout mdl;
+ std::vector<TermFieldHandle> fooHandles;
+ std::vector<TermFieldHandle> barHandles;
+ RankFixture(size_t fooCnt, size_t barCnt,
+ std::string featureName = "subqueries(foo)")
+ : queryEnv(&indexEnv), rankSetup(factory, indexEnv),
+ rankProgram(), mdl(), fooHandles(), barHandles()
+ {
+ fooHandles = addFields(fooCnt, indexEnv.getFieldByName("foo")->id());
+ barHandles = addFields(barCnt, indexEnv.getFieldByName("bar")->id());
+ rankSetup.setFirstPhaseRank(featureName);
+ rankSetup.setIgnoreDefaultRankFeatures(true);
+ ASSERT_TRUE(rankSetup.compile());
+ rankProgram = rankSetup.create_first_phase_program();
+ rankProgram->setup(mdl, queryEnv);
+ }
+ std::vector<TermFieldHandle> addFields(size_t count, uint32_t fieldId) {
+ std::vector<TermFieldHandle> handles;
+ for (size_t i = 0; i < count; ++i) {
+ handles.push_back(mdl.allocTermField(fieldId));
+ SimpleTermData term;
+ term.addField(fieldId).setHandle(handles.back());
+ queryEnv.getTerms().push_back(term);
+ }
+ return handles;
+ }
+ feature_t getSubqueries(uint32_t docId) {
+ rankProgram->run(docId);
+ return *Utils::getScoreFeature(*rankProgram);
+ }
+ void setSubqueries(TermFieldHandle handle, uint32_t docId,
+ uint64_t subqueries) {
+ rankProgram->match_data().resolveTermField(handle)->setSubqueries(docId, subqueries);
+ }
+ void setFooSubqueries(uint32_t i, uint32_t docId, uint64_t subqueries) {
+ ASSERT_LESS(i, fooHandles.size());
+ setSubqueries(fooHandles[i], docId, subqueries);
+ }
+ void setBarSubqueries(uint32_t i, uint32_t docId, uint64_t subqueries) {
+ ASSERT_LESS(i, barHandles.size());
+ setSubqueries(barHandles[i], docId, subqueries);
+ }
+};
+
+TEST_F("require that blueprint can be created from factory",
+ BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("subqueries");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<SubqueriesBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that no features are dumped",
+ SubqueriesBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+}
+
+TEST_FF("require that setup can be done on index field",
+ SubqueriesBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, {"foo"}));
+}
+
+TEST_FF("require that setup can be done on attribute field",
+ SubqueriesBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(bar)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, {"bar"}));
+}
+
+TEST_FF("require that setup fails for unknown field",
+ SubqueriesBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(unknown)", f1.getBaseName().c_str()));
+ EXPECT_FALSE(((Blueprint&)f1).setup(f2.indexEnv, {"unknown"}));
+}
+
+TEST_F("require that not searching a field will give it 0 subqueries",
+ RankFixture(0, 3)) {
+ EXPECT_EQUAL(0, f1.getSubqueries(10));
+}
+
+TEST_F("require that subqueries can be obtained", RankFixture(1, 0)) {
+ f1.setFooSubqueries(0, 10, 0x1234);
+ EXPECT_EQUAL(0x1234, f1.getSubqueries(10));
+}
+
+TEST_F("require that msb subqueries can be obtained",
+ RankFixture(1, 0, "subqueries(foo).msb")) {
+ f1.setFooSubqueries(0, 10, 0x123412345678ULL);
+ EXPECT_EQUAL(0x1234, f1.getSubqueries(10));
+}
+
+TEST_F("require that multiple subqueries are accumulated", RankFixture(3, 0)) {
+ f1.setFooSubqueries(0, 10, 1);
+ f1.setFooSubqueries(1, 10, 2);
+ f1.setFooSubqueries(2, 10, 4);
+ EXPECT_EQUAL(7, f1.getSubqueries(10));
+}
+
+TEST_F("require that stale subqueries are ignored", RankFixture(3, 0)) {
+ f1.setFooSubqueries(0, 10, 1);
+ f1.setFooSubqueries(1, 9, 2);
+ f1.setFooSubqueries(2, 10, 4);
+ EXPECT_EQUAL(5, f1.getSubqueries(10));
+}
+
+TEST_F("require that subqueries from other fields are ignored",
+ RankFixture(2, 2)) {
+ f1.setFooSubqueries(0, 10, 1);
+ f1.setFooSubqueries(1, 10, 2);
+ f1.setBarSubqueries(0, 10, 4);
+ f1.setBarSubqueries(1, 10, 8);
+ EXPECT_EQUAL(3, f1.getSubqueries(10));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/tensor/.gitignore b/searchlib/src/tests/features/tensor/.gitignore
new file mode 100644
index 00000000000..ae6d6dfb414
--- /dev/null
+++ b/searchlib/src/tests/features/tensor/.gitignore
@@ -0,0 +1 @@
+searchlib_tensor_test_app
diff --git a/searchlib/src/tests/features/tensor/CMakeLists.txt b/searchlib/src/tests/features/tensor/CMakeLists.txt
new file mode 100644
index 00000000000..33f7d44d8fe
--- /dev/null
+++ b/searchlib/src/tests/features/tensor/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_tensor_test_app
+ SOURCES
+ tensor_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_tensor_test_app COMMAND searchlib_tensor_test_app)
diff --git a/searchlib/src/tests/features/tensor/FILES b/searchlib/src/tests/features/tensor/FILES
new file mode 100644
index 00000000000..6ece9b360b5
--- /dev/null
+++ b/searchlib/src/tests/features/tensor/FILES
@@ -0,0 +1 @@
+tensor_test.cpp
diff --git a/searchlib/src/tests/features/tensor/tensor_test.cpp b/searchlib/src/tests/features/tensor/tensor_test.cpp
new file mode 100644
index 00000000000..caceea0f47b
--- /dev/null
+++ b/searchlib/src/tests/features/tensor/tensor_test.cpp
@@ -0,0 +1,237 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/eval/function.h>
+
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/tensorattribute.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <vespa/searchlib/fef/fef.h>
+#include <vespa/vespalib/tensor/tensor_factory.h>
+#include <vespa/vespalib/tensor/default_tensor.h>
+#include <vespa/vespalib/tensor/serialization/typed_binary_format.h>
+#include <vespa/searchlib/attribute/tensorattribute.h>
+#include <vespa/vespalib/eval/interpreted_function.h>
+#include <vespa/vespalib/tensor/default_tensor_engine.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::indexproperties;
+using namespace search::fef::test;
+using namespace search::features;
+using search::AttributeFactory;
+using search::attribute::TensorAttribute;
+using search::AttributeVector;
+using vespalib::eval::Value;
+using vespalib::eval::Function;
+using vespalib::tensor::Tensor;
+using vespalib::tensor::TensorCells;
+using vespalib::tensor::DenseTensorCells;
+using vespalib::tensor::TensorDimensions;
+using vespalib::tensor::TensorFactory;
+using vespalib::tensor::TensorType;
+using vespalib::eval::InterpretedFunction;
+using vespalib::tensor::DefaultTensorEngine;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+typedef search::AttributeVector::SP AttributePtr;
+typedef FtTestApp FTA;
+
+namespace
+{
+
+Tensor::UP createTensor(const TensorCells &cells,
+ const TensorDimensions &dimensions) {
+ vespalib::tensor::DefaultTensor::builder builder;
+ return TensorFactory::create(cells, dimensions, builder);
+}
+
+}
+
+struct ExecFixture
+{
+ BlueprintFactory factory;
+ FtFeatureTest test;
+ ExecFixture(const vespalib::string &feature)
+ : factory(),
+ test(factory, feature)
+ {
+ setup_search_features(factory);
+ setupAttributeVectors();
+ setupQueryEnvironment();
+ ASSERT_TRUE(test.setup());
+ }
+ void addAttributeField(const vespalib::string &attrName) {
+ test.getIndexEnv().getBuilder().addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, attrName);
+ }
+ AttributeVector::SP createStringAttribute(const vespalib::string &attrName) {
+ addAttributeField(attrName);
+ return AttributeFactory::createAttribute(attrName, AVC(AVBT::STRING, AVCT::SINGLE));
+ }
+ AttributeVector::SP createTensorAttribute(const vespalib::string &attrName, const vespalib::string &type) {
+ addAttributeField(attrName);
+ AVC config(AVBT::TENSOR, AVCT::SINGLE);
+ config.setTensorType(TensorType::fromSpec(type));
+ return AttributeFactory::createAttribute(attrName, config);
+ }
+ void setAttributeTensorType(const vespalib::string &attrName, const vespalib::string &type) {
+ type::Attribute::set(test.getIndexEnv().getProperties(), attrName, type);
+ }
+ void setQueryTensorType(const vespalib::string &queryFeatureName, const vespalib::string &type) {
+ type::QueryFeature::set(test.getIndexEnv().getProperties(), queryFeatureName, type);
+ }
+ void setupAttributeVectors() {
+ std::vector<AttributePtr> attrs;
+ attrs.push_back(createTensorAttribute("tensorattr", "tensor(x{})"));
+ attrs.push_back(createStringAttribute("singlestr"));
+ attrs.push_back(createTensorAttribute("wrongtype", "tensor(y{})"));
+ addAttributeField("null");
+ setAttributeTensorType("tensorattr", "tensor(x{})");
+ setAttributeTensorType("wrongtype", "tensor(x{})");
+ setAttributeTensorType("null", "tensor(x{})");
+
+ for (const auto &attr : attrs) {
+ attr->addReservedDoc();
+ attr->addDocs(2);
+ attr->clearDoc(1);
+ attr->clearDoc(2);
+ attr->commit();
+ test.getIndexEnv().getAttributeManager().add(attr);
+ }
+
+ TensorAttribute *tensorAttr =
+ dynamic_cast<TensorAttribute *>(attrs[0].get());
+
+ tensorAttr->setTensor(1, *createTensor({ {{{"x", "a"}}, 3},
+ {{{"x", "b"}}, 5},
+ {{{"x", "c"}}, 7} },
+ { "x" }));
+
+ for (const auto &attr : attrs) {
+ attr->commit();
+ }
+ }
+ void setQueryTensor(const vespalib::string &tensorName,
+ const vespalib::string &tensorTypeSpec,
+ const TensorCells &cells,
+ const TensorDimensions &dimensions)
+ {
+ auto tensor = createTensor(cells, dimensions);
+ vespalib::nbostream stream;
+ vespalib::tensor::TypedBinaryFormat::serialize(stream, *tensor);
+ test.getQueryEnv().getProperties().add(tensorName,
+ vespalib::stringref(stream.peek(), stream.size()));
+ setQueryTensorType(tensorName, tensorTypeSpec);
+ }
+
+ void setupQueryEnvironment() {
+ setQueryTensor("tensorquery",
+ "tensor(q{})",
+ { {{{"q", "d"}}, 11 },
+ {{{"q", "e"}}, 13 },
+ {{{"q", "f"}}, 17 } },
+ { "q" });
+ setQueryTensor("mappedtensorquery",
+ "tensor(x[2])",
+ { {{{"x", "0"},{"y", "0"}}, 11 },
+ {{{"x", "0"},{"y", "1"}}, 13 },
+ {{{"x", "1"},{"y", "0"}}, 17 } },
+ { "x", "y" });
+ setQueryTensorType("null", "tensor(q{})");
+ }
+ const Tensor &extractTensor() {
+ const Value::CREF *value = test.resolveObjectFeature();
+ ASSERT_TRUE(value != nullptr);
+ ASSERT_TRUE(value->get().is_tensor());
+ return static_cast<const Tensor &>(*value->get().as_tensor());
+ }
+ const Tensor &execute(uint32_t docId = 1) {
+ test.executeOnly(docId);
+ return extractTensor();
+ }
+};
+
+struct AsTensor {
+ InterpretedFunction ifun;
+ InterpretedFunction::Context ctx;
+ const Value *result;
+ explicit AsTensor(const vespalib::string &expr)
+ : ifun(DefaultTensorEngine::ref(), Function::parse(expr)), ctx(), result(&ifun.eval(ctx))
+ {
+ ASSERT_TRUE(result->is_tensor());
+ }
+ bool operator==(const Tensor &rhs) const { return static_cast<const Tensor &>(*result->as_tensor()).equals(rhs); }
+};
+
+std::ostream &operator<<(std::ostream &os, const AsTensor &my_tensor) {
+ os << my_tensor.result->as_tensor();
+ return os;
+}
+
+TEST_F("require that tensor attribute can be extracted as tensor in attribute feature",
+ ExecFixture("attribute(tensorattr)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {x:b}:5, {x:c}:7, {x:a}:3 }"), f.execute());
+}
+
+TEST_F("require that tensor from query can be extracted as tensor in query feature",
+ ExecFixture("query(tensorquery)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {q:f}:17, {q:d}:11, {q:e}:13 }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if attribute does not exists",
+ ExecFixture("attribute(null)"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if tensor type is wrong",
+ ExecFixture("attribute(wrongtype)"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if query parameter is not found",
+ ExecFixture("query(null)"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if document has no tensor",
+ ExecFixture("attribute(tensorattr)")) {
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute(2));
+}
+
+struct AsDenseTensor {
+ Tensor::UP tensor;
+ explicit AsDenseTensor(const DenseTensorCells &cells)
+ : tensor(TensorFactory::createDense(cells))
+ {
+ ASSERT_TRUE(!!tensor);
+ }
+ bool operator==(const Tensor &rhs) const { return tensor->equals(rhs); }
+};
+
+
+std::ostream &operator<<(std::ostream &os, const AsDenseTensor &my_tensor) {
+ os << *my_tensor.tensor;
+ return os;
+}
+
+TEST_F("require that tensor from query is mapped",
+ ExecFixture("query(mappedtensorquery)")) {
+ EXPECT_EQUAL(AsDenseTensor({ {{{"x", 0}}, 24},
+ {{{"x", 1}}, 17} }),
+ f.execute());
+}
+
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/tensor_from_labels/.gitignore b/searchlib/src/tests/features/tensor_from_labels/.gitignore
new file mode 100644
index 00000000000..0e241941ca3
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_labels/.gitignore
@@ -0,0 +1 @@
+searchlib_tensor_from_labels_test_app
diff --git a/searchlib/src/tests/features/tensor_from_labels/CMakeLists.txt b/searchlib/src/tests/features/tensor_from_labels/CMakeLists.txt
new file mode 100644
index 00000000000..db1814a0f66
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_labels/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_tensor_from_labels_test_app
+ SOURCES
+ tensor_from_labels_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_tensor_from_labels_test_app COMMAND searchlib_tensor_from_labels_test_app)
diff --git a/searchlib/src/tests/features/tensor_from_labels/FILES b/searchlib/src/tests/features/tensor_from_labels/FILES
new file mode 100644
index 00000000000..daecb2bbf5b
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_labels/FILES
@@ -0,0 +1 @@
+tensor_from_labels_test.cpp
diff --git a/searchlib/src/tests/features/tensor_from_labels/tensor_from_labels_test.cpp b/searchlib/src/tests/features/tensor_from_labels/tensor_from_labels_test.cpp
new file mode 100644
index 00000000000..b15ffb956ce
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_labels/tensor_from_labels_test.cpp
@@ -0,0 +1,211 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/eval/function.h>
+#include <vespa/vespalib/eval/interpreted_function.h>
+#include <vespa/vespalib/tensor/tensor.h>
+#include <vespa/vespalib/tensor/default_tensor_engine.h>
+
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <vespa/searchlib/features/tensor_from_labels_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+using search::AttributeFactory;
+using search::IntegerAttribute;
+using search::StringAttribute;
+using vespalib::eval::Value;
+using vespalib::eval::Function;
+using vespalib::eval::InterpretedFunction;
+using vespalib::tensor::Tensor;
+using vespalib::tensor::DefaultTensorEngine;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+typedef search::AttributeVector::SP AttributePtr;
+typedef FtTestApp FTA;
+
+struct SetupFixture
+{
+ TensorFromLabelsBlueprint blueprint;
+ IndexEnvironment indexEnv;
+ SetupFixture()
+ : blueprint(),
+ indexEnv()
+ {
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", SetupFixture)
+{
+ EXPECT_TRUE(FTA::assertCreateInstance(f.blueprint, "tensorFromLabels"));
+}
+
+TEST_F("require that setup fails if source spec is invalid", SetupFixture)
+{
+ FTA::FT_SETUP_FAIL(f.blueprint, f.indexEnv, StringList().add("source(foo)"));
+}
+
+TEST_F("require that setup succeeds with attribute source", SetupFixture)
+{
+ FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("attribute(foo)"),
+ StringList(), StringList().add("tensor"));
+}
+
+TEST_F("require that setup succeeds with query source", SetupFixture)
+{
+ FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("query(foo)"),
+ StringList(), StringList().add("tensor"));
+}
+
+struct ExecFixture
+{
+ BlueprintFactory factory;
+ FtFeatureTest test;
+ ExecFixture(const vespalib::string &feature)
+ : factory(),
+ test(factory, feature)
+ {
+ setup_search_features(factory);
+ setupAttributeVectors();
+ setupQueryEnvironment();
+ ASSERT_TRUE(test.setup());
+ }
+ void setupAttributeVectors() {
+ std::vector<AttributePtr> attrs;
+ attrs.push_back(AttributeFactory::createAttribute("astr", AVC(AVBT::STRING, AVCT::ARRAY)));
+ attrs.push_back(AttributeFactory::createAttribute("aint", AVC(AVBT::INT32, AVCT::ARRAY)));
+ attrs.push_back(AttributeFactory::createAttribute("wsstr", AVC(AVBT::STRING, AVCT::WSET)));
+
+ for (const auto &attr : attrs) {
+ attr->addReservedDoc();
+ attr->addDocs(1);
+ test.getIndexEnv().getAttributeManager().add(attr);
+ }
+
+ StringAttribute *astr = static_cast<StringAttribute *>(attrs[0].get());
+ // Note that the weight parameter is not used
+ astr->append(1, "a", 0);
+ astr->append(1, "b", 0);
+ astr->append(1, "c", 0);
+
+ IntegerAttribute *aint = static_cast<IntegerAttribute *>(attrs[1].get());
+ aint->append(1, 3, 0);
+ aint->append(1, 5, 0);
+ aint->append(1, 7, 0);
+
+ for (const auto &attr : attrs) {
+ attr->commit();
+ }
+ }
+ void setupQueryEnvironment() {
+ test.getQueryEnv().getProperties().add("astr_query", "[d e f]");
+ test.getQueryEnv().getProperties().add("aint_query", "[11 13 17]");
+ }
+ const Tensor &extractTensor() {
+ const Value::CREF *value = test.resolveObjectFeature();
+ ASSERT_TRUE(value != nullptr);
+ ASSERT_TRUE(value->get().is_tensor());
+ return static_cast<const Tensor &>(*value->get().as_tensor());
+ }
+ const Tensor &execute() {
+ test.executeOnly();
+ return extractTensor();
+ }
+};
+
+struct AsTensor {
+ InterpretedFunction ifun;
+ InterpretedFunction::Context ctx;
+ const Value *result;
+ explicit AsTensor(const vespalib::string &expr)
+ : ifun(DefaultTensorEngine::ref(), Function::parse(expr)), ctx(), result(&ifun.eval(ctx))
+ {
+ ASSERT_TRUE(result->is_tensor());
+ }
+ bool operator==(const Tensor &rhs) const { return static_cast<const Tensor &>(*result->as_tensor()).equals(rhs); }
+};
+
+std::ostream &operator<<(std::ostream &os, const AsTensor &my_tensor) {
+ os << my_tensor.result->as_tensor();
+ return os;
+}
+
+// Tests for attribute source:
+
+TEST_F("require that array string attribute can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromLabels(attribute(astr))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {astr:a}:1, {astr:b}:1, {astr:c}:1 }"), f.execute());
+}
+
+TEST_F("require that array string attribute can be converted to tensor (explicit dimension)",
+ ExecFixture("tensorFromLabels(attribute(astr),dim)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {dim:a}:1, {dim:b}:1, {dim:c}:1 }"), f.execute());
+}
+
+TEST_F("require that array integer attribute can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromLabels(attribute(aint))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {aint:7}:1, {aint:3}:1, {aint:5}:1 }"), f.execute());
+}
+
+TEST_F("require that array attribute can be converted to tensor (explicit dimension)",
+ ExecFixture("tensorFromLabels(attribute(aint),dim)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {dim:7}:1, {dim:3}:1, {dim:5}:1 }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if attribute does not exists",
+ ExecFixture("tensorFromLabels(attribute(null))"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if attribute type is not supported",
+ ExecFixture("tensorFromLabels(attribute(wsstr))"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+
+// Tests for query source:
+
+TEST_F("require that string array from query can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromLabels(query(astr_query))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {astr_query:d}:1, {astr_query:e}:1, {astr_query:f}:1 }"), f.execute());
+}
+
+TEST_F("require that integer array from query can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromLabels(query(aint_query))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {aint_query:13}:1, {aint_query:17}:1, {aint_query:11}:1 }"), f.execute());
+}
+
+TEST_F("require that string array from query can be converted to tensor (explicit dimension)",
+ ExecFixture("tensorFromLabels(query(astr_query),dim)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {dim:d}:1, {dim:e}:1, {dim:f}:1 }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if query parameter is not found",
+ ExecFixture("tensorFromLabels(query(null))"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/tensor_from_weighted_set/.gitignore b/searchlib/src/tests/features/tensor_from_weighted_set/.gitignore
new file mode 100644
index 00000000000..a56eade053e
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_weighted_set/.gitignore
@@ -0,0 +1 @@
+searchlib_tensor_from_weighted_set_test_app
diff --git a/searchlib/src/tests/features/tensor_from_weighted_set/CMakeLists.txt b/searchlib/src/tests/features/tensor_from_weighted_set/CMakeLists.txt
new file mode 100644
index 00000000000..7c38b301679
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_weighted_set/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_tensor_from_weighted_set_test_app
+ SOURCES
+ tensor_from_weighted_set_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_tensor_from_weighted_set_test_app COMMAND searchlib_tensor_from_weighted_set_test_app)
diff --git a/searchlib/src/tests/features/tensor_from_weighted_set/FILES b/searchlib/src/tests/features/tensor_from_weighted_set/FILES
new file mode 100644
index 00000000000..639a54230b1
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_weighted_set/FILES
@@ -0,0 +1 @@
+tensor_from_weighted_set_test.cpp
diff --git a/searchlib/src/tests/features/tensor_from_weighted_set/tensor_from_weighted_set_test.cpp b/searchlib/src/tests/features/tensor_from_weighted_set/tensor_from_weighted_set_test.cpp
new file mode 100644
index 00000000000..163fd5b5389
--- /dev/null
+++ b/searchlib/src/tests/features/tensor_from_weighted_set/tensor_from_weighted_set_test.cpp
@@ -0,0 +1,198 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/eval/function.h>
+#include <vespa/vespalib/eval/interpreted_function.h>
+#include <vespa/vespalib/tensor/tensor.h>
+#include <vespa/vespalib/tensor/default_tensor_engine.h>
+
+#include <vespa/searchlib/attribute/attributefactory.h>
+#include <vespa/searchlib/attribute/attributevector.h>
+#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <vespa/searchlib/features/tensor_from_weighted_set_feature.h>
+#include <vespa/searchlib/fef/fef.h>
+
+using search::feature_t;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+using search::AttributeFactory;
+using search::IntegerAttribute;
+using search::StringAttribute;
+using vespalib::eval::Value;
+using vespalib::eval::Function;
+using vespalib::eval::InterpretedFunction;
+using vespalib::tensor::Tensor;
+using vespalib::tensor::DefaultTensorEngine;
+
+typedef search::attribute::Config AVC;
+typedef search::attribute::BasicType AVBT;
+typedef search::attribute::CollectionType AVCT;
+typedef search::AttributeVector::SP AttributePtr;
+typedef FtTestApp FTA;
+
+struct SetupFixture
+{
+ TensorFromWeightedSetBlueprint blueprint;
+ IndexEnvironment indexEnv;
+ SetupFixture()
+ : blueprint(),
+ indexEnv()
+ {
+ }
+};
+
+TEST_F("require that blueprint can be created from factory", SetupFixture)
+{
+ EXPECT_TRUE(FTA::assertCreateInstance(f.blueprint, "tensorFromWeightedSet"));
+}
+
+TEST_F("require that setup fails if source spec is invalid", SetupFixture)
+{
+ FTA::FT_SETUP_FAIL(f.blueprint, f.indexEnv, StringList().add("source(foo)"));
+}
+
+TEST_F("require that setup succeeds with attribute source", SetupFixture)
+{
+ FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("attribute(foo)"),
+ StringList(), StringList().add("tensor"));
+}
+
+TEST_F("require that setup succeeds with query source", SetupFixture)
+{
+ FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("query(foo)"),
+ StringList(), StringList().add("tensor"));
+}
+
+struct ExecFixture
+{
+ BlueprintFactory factory;
+ FtFeatureTest test;
+ ExecFixture(const vespalib::string &feature)
+ : factory(),
+ test(factory, feature)
+ {
+ setup_search_features(factory);
+ setupAttributeVectors();
+ setupQueryEnvironment();
+ ASSERT_TRUE(test.setup());
+ }
+ void setupAttributeVectors() {
+ std::vector<AttributePtr> attrs;
+ attrs.push_back(AttributeFactory::createAttribute("wsstr", AVC(AVBT::STRING, AVCT::WSET)));
+ attrs.push_back(AttributeFactory::createAttribute("wsint", AVC(AVBT::INT32, AVCT::WSET)));
+ attrs.push_back(AttributeFactory::createAttribute("astr", AVC(AVBT::STRING, AVCT::ARRAY)));
+
+ for (const auto &attr : attrs) {
+ attr->addReservedDoc();
+ attr->addDocs(1);
+ test.getIndexEnv().getAttributeManager().add(attr);
+ }
+
+ StringAttribute *wsstr = static_cast<StringAttribute *>(attrs[0].get());
+ wsstr->append(1, "a", 3);
+ wsstr->append(1, "b", 5);
+ wsstr->append(1, "c", 7);
+
+ IntegerAttribute *wsint = static_cast<IntegerAttribute *>(attrs[1].get());
+ wsint->append(1, 11, 3);
+ wsint->append(1, 13, 5);
+ wsint->append(1, 17, 7);
+
+ for (const auto &attr : attrs) {
+ attr->commit();
+ }
+ }
+ void setupQueryEnvironment() {
+ test.getQueryEnv().getProperties().add("wsquery", "{d:11,e:13,f:17}");
+ }
+ const Tensor &extractTensor() {
+ const Value::CREF *value = test.resolveObjectFeature();
+ ASSERT_TRUE(value != nullptr);
+ ASSERT_TRUE(value->get().is_tensor());
+ return static_cast<const Tensor &>(*value->get().as_tensor());
+ }
+ const Tensor &execute() {
+ test.executeOnly();
+ return extractTensor();
+ }
+};
+
+struct AsTensor {
+ InterpretedFunction ifun;
+ InterpretedFunction::Context ctx;
+ const Value *result;
+ explicit AsTensor(const vespalib::string &expr)
+ : ifun(DefaultTensorEngine::ref(), Function::parse(expr)), ctx(), result(&ifun.eval(ctx))
+ {
+ ASSERT_TRUE(result->is_tensor());
+ }
+ bool operator==(const Tensor &rhs) const { return static_cast<const Tensor &>(*result->as_tensor()).equals(rhs); }
+};
+
+std::ostream &operator<<(std::ostream &os, const AsTensor &my_tensor) {
+ os << my_tensor.result->as_tensor();
+ return os;
+}
+
+TEST_F("require that weighted set string attribute can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromWeightedSet(attribute(wsstr))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {wsstr:b}:5, {wsstr:c}:7, {wsstr:a}:3 }"), f.execute());
+}
+
+TEST_F("require that weighted set string attribute can be converted to tensor (explicit dimension)",
+ ExecFixture("tensorFromWeightedSet(attribute(wsstr),dim)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {dim:a}:3, {dim:b}:5, {dim:c}:7 }"), f.execute());
+}
+
+TEST_F("require that weighted set integer attribute can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromWeightedSet(attribute(wsint))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {wsint:13}:5, {wsint:17}:7, {wsint:11}:3 }"), f.execute());
+}
+
+TEST_F("require that weighted set integer attribute can be converted to tensor (explicit dimension)",
+ ExecFixture("tensorFromWeightedSet(attribute(wsint),dim)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {dim:17}:7, {dim:11}:3, {dim:13}:5 }"), f.execute());
+}
+
+TEST_F("require that weighted set from query can be converted to tensor (default dimension)",
+ ExecFixture("tensorFromWeightedSet(query(wsquery))"))
+{
+ EXPECT_EQUAL(AsTensor("{ {wsquery:f}:17, {wsquery:d}:11, {wsquery:e}:13 }"), f.execute());
+}
+
+TEST_F("require that weighted set from query can be converted to tensor (explicit dimension)",
+ ExecFixture("tensorFromWeightedSet(query(wsquery),dim)"))
+{
+ EXPECT_EQUAL(AsTensor("{ {dim:d}:11, {dim:e}:13, {dim:f}:17 }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if attribute does not exists",
+ ExecFixture("tensorFromWeightedSet(attribute(null))"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if attribute type is not supported",
+ ExecFixture("tensorFromWeightedSet(attribute(astr))"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_F("require that empty tensor is created if query parameter is not found",
+ ExecFixture("tensorFromWeightedSet(query(null))"))
+{
+ EXPECT_EQUAL(AsTensor("{ }"), f.execute());
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/text_similarity_feature/.gitignore b/searchlib/src/tests/features/text_similarity_feature/.gitignore
new file mode 100644
index 00000000000..9ffa5b46a43
--- /dev/null
+++ b/searchlib/src/tests/features/text_similarity_feature/.gitignore
@@ -0,0 +1 @@
+searchlib_text_similarity_feature_test_app
diff --git a/searchlib/src/tests/features/text_similarity_feature/CMakeLists.txt b/searchlib/src/tests/features/text_similarity_feature/CMakeLists.txt
new file mode 100644
index 00000000000..e0cb043c8f1
--- /dev/null
+++ b/searchlib/src/tests/features/text_similarity_feature/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_text_similarity_feature_test_app
+ SOURCES
+ text_similarity_feature_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_text_similarity_feature_test_app COMMAND searchlib_text_similarity_feature_test_app)
diff --git a/searchlib/src/tests/features/text_similarity_feature/FILES b/searchlib/src/tests/features/text_similarity_feature/FILES
new file mode 100644
index 00000000000..dfa5173742d
--- /dev/null
+++ b/searchlib/src/tests/features/text_similarity_feature/FILES
@@ -0,0 +1 @@
+text_similarity_feature_test.cpp
diff --git a/searchlib/src/tests/features/text_similarity_feature/text_similarity_feature_test.cpp b/searchlib/src/tests/features/text_similarity_feature/text_similarity_feature_test.cpp
new file mode 100644
index 00000000000..6a6b9d0a48e
--- /dev/null
+++ b/searchlib/src/tests/features/text_similarity_feature/text_similarity_feature_test.cpp
@@ -0,0 +1,245 @@
+// 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/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/features/text_similarity_feature.h>
+#include <vespa/searchlib/fef/test/ftlib.h>
+#include <initializer_list>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+std::vector<vespalib::string> featureNamesFoo() {
+ std::vector<vespalib::string> f;
+ f.push_back("textSimilarity(foo).score");
+ f.push_back("textSimilarity(foo).proximity");
+ f.push_back("textSimilarity(foo).order");
+ f.push_back("textSimilarity(foo).queryCoverage");
+ f.push_back("textSimilarity(foo).fieldCoverage");
+ return f;
+}
+
+const size_t SCORE = 0;
+const size_t PROXIMITY = 1;
+const size_t ORDER = 2;
+const size_t QUERY = 3;
+const size_t FIELD = 4;
+
+FtIndex indexFoo() {
+ FtIndex idx;
+ idx.field("foo");
+ return idx;
+}
+
+struct BlueprintFactoryFixture {
+ BlueprintFactory factory;
+ BlueprintFactoryFixture() : factory()
+ {
+ setup_search_features(factory);
+ }
+};
+
+struct IndexFixture {
+ IndexEnvironment indexEnv;
+ IndexFixture() : indexEnv()
+ {
+ IndexEnvironmentBuilder builder(indexEnv);
+ builder.addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ builder.addField(FieldType::INDEX, CollectionType::WEIGHTEDSET, "bar");
+ builder.addField(FieldType::ATTRIBUTE, CollectionType::SINGLE, "baz");
+ }
+};
+
+struct FeatureDumpFixture : public IDumpFeatureVisitor {
+ std::vector<vespalib::string> expect;
+ size_t dumped;
+ virtual void visitDumpFeature(const vespalib::string &name) {
+ EXPECT_LESS(dumped, expect.size());
+ EXPECT_EQUAL(expect[dumped++], name);
+ }
+ FeatureDumpFixture() : IDumpFeatureVisitor(), expect(featureNamesFoo()), dumped(0) {}
+};
+
+struct RankFixture : BlueprintFactoryFixture {
+ RankFixture() : BlueprintFactoryFixture() {}
+ double get_feature(const vespalib::string &query, const FtIndex &index, size_t select,
+ bool useStaleMatchData = false)
+ {
+ std::vector<vespalib::string> names = featureNamesFoo();
+ ASSERT_TRUE(names.size() == 5u);
+ FtFeatureTest ft(factory, names);
+ ft.getIndexEnv().getBuilder().addField(FieldType::INDEX, CollectionType::SINGLE, "foo");
+ FtTestApp::FT_SETUP(ft, FtUtil::toQuery(query), index, 1);
+ RankResult actual;
+ EXPECT_TRUE(ft.executeOnly(actual, useStaleMatchData ? 2 : 1));
+ return actual.getScore(names[select]);
+ }
+};
+
+double prox(uint32_t dist) {
+ return (dist > 8) ? 0 : (1.0 - (((dist-1)/8.0) * ((dist-1)/8.0)));
+}
+
+double comb(std::initializer_list<double> values) {
+ double sum = 0.0;
+ for (double value: values) {
+ sum += value;
+ }
+ return (sum/values.size());
+}
+
+double mix(double proximity, double order, double query, double field) {
+ return (0.35 * proximity) + (0.15 * order) + (0.30 * query) + (0.20 * field);
+}
+
+TEST_F("require that blueprint can be created from factory", BlueprintFactoryFixture) {
+ Blueprint::SP bp = f.factory.createBlueprint("textSimilarity");
+ EXPECT_TRUE(bp.get() != 0);
+ EXPECT_TRUE(dynamic_cast<TextSimilarityBlueprint*>(bp.get()) != 0);
+}
+
+TEST_FFF("require that appropriate features are dumped", TextSimilarityBlueprint, IndexFixture, FeatureDumpFixture) {
+ f1.visitDumpFeatures(f2.indexEnv, f3);
+ EXPECT_EQUAL(f3.expect.size(), f3.dumped);
+}
+
+TEST_FF("require that setup can be done on single value index field", TextSimilarityBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(foo)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "foo")));
+}
+
+TEST_FF("require that setup can not be done on weighted set index field", TextSimilarityBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(bar)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(!((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "bar")));
+}
+
+TEST_FF("require that setup can not be done on single value attribute field", TextSimilarityBlueprint, IndexFixture) {
+ DummyDependencyHandler deps(f1);
+ f1.setName(vespalib::make_string("%s(baz)", f1.getBaseName().c_str()));
+ EXPECT_TRUE(!((Blueprint&)f1).setup(f2.indexEnv, std::vector<vespalib::string>(1, "baz")));
+}
+
+TEST_F("require that no match gives zero outputs", RankFixture) {
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), SCORE));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), PROXIMITY));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), ORDER));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), QUERY));
+ EXPECT_EQUAL(0.0, f1.get_feature("x", indexFoo().element("y"), FIELD));
+}
+
+TEST_F("require that minal perfect match gives max outputs", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), SCORE));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), QUERY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x"), FIELD));
+}
+
+TEST_F("require that larger perfect match gives max outputs", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), SCORE));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), QUERY));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e f g", indexFoo().element("a b c d e f g"), FIELD));
+}
+
+TEST_F("require that extra query terms reduces order but not proximity", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("x y", indexFoo().element("x"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x y y", indexFoo().element("x"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("x y y y", indexFoo().element("x"), PROXIMITY));
+
+ EXPECT_EQUAL(0.0, f1.get_feature("x y", indexFoo().element("x"), ORDER));
+ EXPECT_EQUAL(0.0, f1.get_feature("x y y", indexFoo().element("x"), ORDER));
+ EXPECT_EQUAL(0.0, f1.get_feature("x y y y", indexFoo().element("x"), ORDER));
+}
+
+TEST_F("require that extra field terms reduces proximity but not order", RankFixture) {
+ EXPECT_EQUAL(prox(2), f1.get_feature("x", indexFoo().element("x y"), PROXIMITY));
+ EXPECT_EQUAL(prox(3), f1.get_feature("x", indexFoo().element("x y y"), PROXIMITY));
+ EXPECT_EQUAL(prox(4), f1.get_feature("x", indexFoo().element("x y y y"), PROXIMITY));
+
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x y"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x y y"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("x", indexFoo().element("x y y y"), ORDER));
+}
+
+TEST_F("require that proximity acts as expected", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("a x b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(3), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("a x x b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(4), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("a x x x b c d e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(2), prox(2)}), f1.get_feature("a b c d e", indexFoo().element("a x b x c x d x e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(1), prox(3)}), f1.get_feature("a b c d e", indexFoo().element("a x b x c d x x e"), PROXIMITY));
+}
+
+TEST_F("require that field order does not affect proximity score", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("d c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("d x c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(3), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("d x x c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(4), prox(1), prox(1), prox(1)}), f1.get_feature("a b c d e", indexFoo().element("d x x x c a b e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(2), prox(2)}), f1.get_feature("a b c d e", indexFoo().element("d x c x a x b x e"), PROXIMITY));
+ EXPECT_EQUAL(comb({prox(2), prox(2), prox(1), prox(3)}), f1.get_feature("a b c d e", indexFoo().element("d x c x a b x x e"), PROXIMITY));
+}
+
+TEST_F("require that order score acts as expected", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), ORDER));
+ EXPECT_EQUAL(comb({1.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("a b c e d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b a c e d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b a e d c"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 0.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("e d c b a"), ORDER));
+}
+
+TEST_F("require that proximity does not affect order score", RankFixture) {
+ EXPECT_EQUAL(1.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), ORDER));
+ EXPECT_EQUAL(comb({1.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("a x b x c x e x d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 1.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b x a x c x e x d"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 1.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("b x a x e x d x c"), ORDER));
+ EXPECT_EQUAL(comb({0.0, 0.0, 0.0, 0.0}), f1.get_feature("a b c d e", indexFoo().element("e x d x c x b x a"), ORDER));
+}
+
+TEST_F("require that query coverage acts as expected", RankFixture) {
+ EXPECT_EQUAL(5.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), QUERY));
+ EXPECT_EQUAL(4.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c d"), QUERY));
+ EXPECT_EQUAL(3.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c"), QUERY));
+ EXPECT_EQUAL(2.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b"), QUERY));
+ EXPECT_EQUAL(4.0/7.0, f1.get_feature("a!200 b!200 c d e", indexFoo().element("a b"), QUERY));
+ EXPECT_EQUAL(2.0/7.0, f1.get_feature("a b c!500", indexFoo().element("a b"), QUERY));
+ EXPECT_EQUAL(5.0/7.0, f1.get_feature("a b c!500", indexFoo().element("c"), QUERY));
+}
+
+TEST_F("require that field coverage acts as expected", RankFixture) {
+ EXPECT_EQUAL(5.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b c d e"), FIELD));
+ EXPECT_EQUAL(4.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a x c d e"), FIELD));
+ EXPECT_EQUAL(3.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a b x x e"), FIELD));
+ EXPECT_EQUAL(2.0/5.0, f1.get_feature("a b c d e", indexFoo().element("x x x d e"), FIELD));
+}
+
+TEST_F("require that first unique match is used per query term", RankFixture) {
+ EXPECT_EQUAL(prox(3), f1.get_feature("a b", indexFoo().element("a a a b"), PROXIMITY));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b", indexFoo().element("a a a b"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b", indexFoo().element("a a a b"), QUERY));
+ EXPECT_EQUAL(2.0/4.0, f1.get_feature("a b", indexFoo().element("a a a b"), FIELD));
+
+ EXPECT_EQUAL(comb({prox(1), prox(2)}), f1.get_feature("a b a", indexFoo().element("a a a b"), PROXIMITY));
+ EXPECT_EQUAL(0.5, f1.get_feature("a b a", indexFoo().element("a a a b"), ORDER));
+ EXPECT_EQUAL(1.0, f1.get_feature("a b a", indexFoo().element("a a a b"), QUERY));
+ EXPECT_EQUAL(3.0/4.0, f1.get_feature("a b a", indexFoo().element("a a a b"), FIELD));
+}
+
+TEST_F("require that overall score combines individual signals appropriately", RankFixture) {
+ EXPECT_EQUAL(comb({prox(1), prox(3), prox(2)}), f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), PROXIMITY));
+ EXPECT_EQUAL(comb({1.0, 0.0, 1.0}), f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), ORDER));
+ EXPECT_EQUAL(4.0/5.0, f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), QUERY));
+ EXPECT_EQUAL(4.0/7.0, f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), FIELD));
+ EXPECT_EQUAL(mix(comb({prox(1), prox(3), prox(2)}), comb({1.0, 0.0, 1.0}), 4.0/5.0, 4.0/7.0),
+ f1.get_feature("a b c d e", indexFoo().element("a c x x b x d"), SCORE));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/features/util/.gitignore b/searchlib/src/tests/features/util/.gitignore
new file mode 100644
index 00000000000..14e50fdaf47
--- /dev/null
+++ b/searchlib/src/tests/features/util/.gitignore
@@ -0,0 +1 @@
+searchlib_util_test_app
diff --git a/searchlib/src/tests/features/util/CMakeLists.txt b/searchlib/src/tests/features/util/CMakeLists.txt
new file mode 100644
index 00000000000..95a0bf3b45d
--- /dev/null
+++ b/searchlib/src/tests/features/util/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_util_test_app
+ SOURCES
+ util_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_util_test_app COMMAND searchlib_util_test_app)
diff --git a/searchlib/src/tests/features/util/FILES b/searchlib/src/tests/features/util/FILES
new file mode 100644
index 00000000000..f0bd0a06305
--- /dev/null
+++ b/searchlib/src/tests/features/util/FILES
@@ -0,0 +1 @@
+util_test.cpp
diff --git a/searchlib/src/tests/features/util/util_test.cpp b/searchlib/src/tests/features/util/util_test.cpp
new file mode 100644
index 00000000000..d2f97631d0f
--- /dev/null
+++ b/searchlib/src/tests/features/util/util_test.cpp
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/searchlib/features/utils.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+
+using namespace search;
+using namespace search::fef;
+using namespace search::fef::test;
+using namespace search::features;
+
+SimpleTermData make_term(uint32_t uid) {
+ SimpleTermData term;
+ term.setUniqueId(uid);
+ return term;
+}
+
+struct TermLabelFixture {
+ IndexEnvironment indexEnv;
+ QueryEnvironment queryEnv;
+ TermLabelFixture() : indexEnv(), queryEnv(&indexEnv) {
+ queryEnv.getTerms().push_back(make_term(5));
+ queryEnv.getTerms().push_back(make_term(0));
+ queryEnv.getTerms().push_back(make_term(10));
+ queryEnv.getProperties().add("vespa.label.foo.id", "5");
+ queryEnv.getProperties().add("vespa.label.bar.id", "0"); // undefined uid
+ queryEnv.getProperties().add("vespa.label.baz.id", "10");
+ queryEnv.getProperties().add("vespa.label.fox.id", "7"); // non-existing
+ }
+};
+
+TEST_F("require that label can be mapped to term", TermLabelFixture) {
+ EXPECT_EQUAL((ITermData*)&f1.queryEnv.getTerms()[0], util::getTermByLabel(f1.queryEnv, "foo"));
+ EXPECT_EQUAL((ITermData*)0, util::getTermByLabel(f1.queryEnv, "bar"));
+ EXPECT_EQUAL((ITermData*)&f1.queryEnv.getTerms()[2], util::getTermByLabel(f1.queryEnv, "baz"));
+ EXPECT_EQUAL((ITermData*)0, util::getTermByLabel(f1.queryEnv, "fox"));
+ EXPECT_EQUAL((ITermData*)0, util::getTermByLabel(f1.queryEnv, "unknown"));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }