summaryrefslogtreecommitdiffstats
path: root/searchlib/src/tests/ranksetup
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/ranksetup
Publish
Diffstat (limited to 'searchlib/src/tests/ranksetup')
-rw-r--r--searchlib/src/tests/ranksetup/.gitignore5
-rw-r--r--searchlib/src/tests/ranksetup/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/ranksetup/DESC1
-rw-r--r--searchlib/src/tests/ranksetup/FILES1
-rw-r--r--searchlib/src/tests/ranksetup/ranksetup_test.cpp922
-rw-r--r--searchlib/src/tests/ranksetup/verify_feature/.gitignore1
-rw-r--r--searchlib/src/tests/ranksetup/verify_feature/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/ranksetup/verify_feature/FILES1
-rw-r--r--searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp58
9 files changed, 1005 insertions, 0 deletions
diff --git a/searchlib/src/tests/ranksetup/.gitignore b/searchlib/src/tests/ranksetup/.gitignore
new file mode 100644
index 00000000000..754597f65f8
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/.gitignore
@@ -0,0 +1,5 @@
+.depend
+Makefile
+ranksetup_test
+/.gdbinit
+searchlib_ranksetup_test_app
diff --git a/searchlib/src/tests/ranksetup/CMakeLists.txt b/searchlib/src/tests/ranksetup/CMakeLists.txt
new file mode 100644
index 00000000000..712f1ffefa4
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/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_ranksetup_test_app
+ SOURCES
+ ranksetup_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_ranksetup_test_app COMMAND searchlib_ranksetup_test_app)
diff --git a/searchlib/src/tests/ranksetup/DESC b/searchlib/src/tests/ranksetup/DESC
new file mode 100644
index 00000000000..37f7cc6f2c5
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/DESC
@@ -0,0 +1 @@
+ranksetup test. Take a look at ranksetup.cpp for details.
diff --git a/searchlib/src/tests/ranksetup/FILES b/searchlib/src/tests/ranksetup/FILES
new file mode 100644
index 00000000000..f1fce1d28ff
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/FILES
@@ -0,0 +1 @@
+ranksetup.cpp
diff --git a/searchlib/src/tests/ranksetup/ranksetup_test.cpp b/searchlib/src/tests/ranksetup/ranksetup_test.cpp
new file mode 100644
index 00000000000..aee04ef4cb7
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/ranksetup_test.cpp
@@ -0,0 +1,922 @@
+// 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("ranksetup_test");
+#include <vespa/vespalib/testkit/testapp.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <vespa/searchlib/common/feature.h>
+
+#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/fef/blueprint.h>
+#include <vespa/searchlib/fef/blueprintfactory.h>
+#include <vespa/searchlib/fef/featureexecutor.h>
+#include <vespa/searchlib/fef/featurenamebuilder.h>
+#include <vespa/searchlib/fef/idumpfeaturevisitor.h>
+#include <vespa/searchlib/fef/indexproperties.h>
+#include <vespa/searchlib/fef/matchdatalayout.h>
+#include <vespa/searchlib/fef/rank_program.h>
+#include <vespa/searchlib/fef/ranksetup.h>
+#include <vespa/searchlib/fef/utils.h>
+
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/queryenvironment.h>
+#include <vespa/searchlib/fef/test/rankresult.h>
+
+#include <vespa/searchlib/features/rankingexpressionfeature.h>
+#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/features/valuefeature.h>
+#include <vespa/searchlib/fef/test/plugin/chain.h>
+#include <vespa/searchlib/fef/test/plugin/double.h>
+#include <vespa/searchlib/fef/test/plugin/setup.h>
+#include <vespa/searchlib/fef/test/plugin/staticrank.h>
+#include <vespa/searchlib/fef/test/plugin/sum.h>
+#include <vespa/searchlib/fef/test/plugin/cfgvalue.h>
+#include <vespa/searchlib/fef/test/dummy_dependency_handler.h>
+
+using namespace search::fef;
+using namespace search::features;
+using namespace search::fef::test;
+using search::feature_t;
+
+typedef FeatureNameBuilder FNB;
+
+//-----------------------------------------------------------------------------
+// DumpFeatureVisitor
+//-----------------------------------------------------------------------------
+class DumpFeatureVisitor : public IDumpFeatureVisitor
+{
+public:
+ DumpFeatureVisitor() {}
+ virtual void visitDumpFeature(const vespalib::string & name) {
+ std::cout << "dump feature: " << name << std::endl;
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// RankEnvironment
+//-----------------------------------------------------------------------------
+class RankEnvironment
+{
+private:
+ const BlueprintFactory & _factory;
+ const IIndexEnvironment & _indexEnv;
+ const IQueryEnvironment & _queryEnv;
+
+public:
+ RankEnvironment(const BlueprintFactory & bfactory,
+ const IIndexEnvironment & indexEnv, const IQueryEnvironment & queryEnv) :
+ _factory(bfactory), _indexEnv(indexEnv), _queryEnv(queryEnv) {}
+
+ const BlueprintFactory & factory() const { return _factory; }
+ const IIndexEnvironment & indexEnvironment() const { return _indexEnv; }
+ const IQueryEnvironment & queryEnvironment() const { return _queryEnv; }
+};
+
+
+//-----------------------------------------------------------------------------
+// RankExecutor
+//-----------------------------------------------------------------------------
+class RankExecutor
+{
+private:
+ vespalib::string _initRank;
+ vespalib::string _finalRank;
+ const RankEnvironment & _rankEnv;
+ MatchDataLayout _layout;
+ std::unique_ptr<RankSetup> _rs;
+ RankProgram::UP _firstPhaseProgram;
+ RankProgram::UP _secondPhaseProgram;
+
+public:
+ RankExecutor(const vespalib::string &initRank,
+ const vespalib::string &finalRank, const RankEnvironment &rankEnv) :
+ _initRank(initRank), _finalRank(finalRank), _rankEnv(rankEnv), _layout(),
+ _rs(), _firstPhaseProgram(), _secondPhaseProgram() {}
+ bool setup();
+ RankResult execute(uint32_t docId = 0);
+};
+
+bool
+RankExecutor::setup()
+{
+ _rs = std::unique_ptr<RankSetup>(new RankSetup(_rankEnv.factory(), _rankEnv.indexEnvironment()));
+ if (_initRank.empty()) {
+ return false;
+ }
+ _rs->setFirstPhaseRank(_initRank);
+
+ if (!_finalRank.empty()) {
+ _rs->setSecondPhaseRank(_finalRank);
+ }
+
+ if (!_rs->compile()) {
+ return false;
+ }
+
+ _firstPhaseProgram = _rs->create_first_phase_program();
+ _firstPhaseProgram->setup(_layout, _rankEnv.queryEnvironment());
+ if (!_finalRank.empty()) {
+ _secondPhaseProgram = _rs->create_second_phase_program();
+ _secondPhaseProgram->setup(_layout, _rankEnv.queryEnvironment());
+ }
+ return true;
+}
+
+RankResult
+RankExecutor::execute(uint32_t docId)
+{
+ RankResult result;
+ _firstPhaseProgram->run(docId);
+ result.addScore(_initRank, *Utils::getScoreFeature(*_firstPhaseProgram));
+
+ if (_secondPhaseProgram.get() != nullptr) {
+ _secondPhaseProgram->run(docId);
+ result.addScore(_finalRank, *Utils::getScoreFeature(*_secondPhaseProgram));
+ }
+
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+// FeatureDumper
+//-----------------------------------------------------------------------------
+class FeatureDumper
+{
+private:
+ const RankEnvironment & _rankEnv;
+ RankSetup _setup;
+ MatchDataLayout _layout;
+ RankProgram::UP _rankProgram;
+
+public:
+ FeatureDumper(const RankEnvironment & rankEnv) :
+ _rankEnv(rankEnv),
+ _setup(_rankEnv.factory(), _rankEnv.indexEnvironment()),
+ _layout(),
+ _rankProgram() {}
+ void addDumpFeature(const vespalib::string &name);
+ void configure();
+ bool setup();
+ RankResult dump();
+};
+
+void
+FeatureDumper::addDumpFeature(const vespalib::string &name)
+{
+ _setup.addDumpFeature(name);
+}
+
+void
+FeatureDumper::configure()
+{
+ _setup.configure();
+}
+
+bool
+FeatureDumper::setup()
+{
+ if (!_setup.compile()) {
+ return false;
+ }
+
+ _rankProgram = _setup.create_dump_program();
+ _rankProgram->setup(_layout, _rankEnv.queryEnvironment());
+ return true;
+}
+
+RankResult
+FeatureDumper::dump()
+{
+ _rankProgram->run(1);
+ std::map<vespalib::string, feature_t> features = Utils::getSeedFeatures(*_rankProgram);
+ RankResult retval;
+ for (auto itr = features.begin(); itr != features.end(); ++itr) {
+ retval.addScore(itr->first, itr->second);
+ }
+ return retval;
+}
+
+
+//-----------------------------------------------------------------------------
+// RankSetupTest
+//-----------------------------------------------------------------------------
+class RankSetupTest : public vespalib::TestApp
+{
+private:
+ BlueprintFactory _factory;
+ search::AttributeManager _manager;
+ IndexEnvironment _indexEnv;
+ QueryEnvironment _queryEnv;
+ RankEnvironment _rankEnv;
+ DumpFeatureVisitor _visitor;
+
+ void testValueBlueprint();
+ void testDoubleBlueprint();
+ void testSumBlueprint();
+ void testStaticRankBlueprint();
+ void testChainBlueprint();
+ void testCfgValueBlueprint();
+ void testCompilation();
+ void testRankSetup();
+ bool testExecution(const vespalib::string & initRank, feature_t initScore,
+ const vespalib::string & finalRank = "", feature_t finalScore = 0.0f, uint32_t docId = 0);
+ bool testExecution(const RankEnvironment &rankEnv,
+ const vespalib::string & initRank, feature_t initScore,
+ const vespalib::string & finalRank = "", feature_t finalScore = 0.0f, uint32_t docId = 0);
+ void testExecution();
+ void testFeatureDump();
+
+ void checkFeatures(std::map<vespalib::string, feature_t> &exp, std::map<vespalib::string, feature_t> &actual);
+ void testFeatureNormalization();
+
+public:
+ RankSetupTest();
+ int Main();
+};
+
+
+void
+RankSetupTest::testValueBlueprint()
+{
+ ValueBlueprint prototype;
+ prototype.visitDumpFeatures(_indexEnv, _visitor);
+ { // basic test
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ bp->setName("value");
+ EXPECT_EQUAL(bp->getName(), "value");
+ std::vector<vespalib::string> params;
+ params.push_back("5.5");
+ params.push_back("10.5");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 0u);
+ EXPECT_EQUAL(deps.output.size(), 2u);
+ EXPECT_EQUAL(deps.output[0], "0");
+ EXPECT_EQUAL(deps.output[1], "1");
+
+ FeatureExecutor::LP fe = bp->createExecutor(_queryEnv);
+ ValueExecutor * vfe = static_cast<ValueExecutor *>(fe.get());
+ EXPECT_EQUAL(vfe->getValues().size(), 2u);
+ EXPECT_EQUAL(vfe->getValues()[0], 5.5f);
+ EXPECT_EQUAL(vfe->getValues()[1], 10.5f);
+ }
+ { // invalid params
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ EXPECT_TRUE(!bp->setup(_indexEnv, params));
+ }
+}
+
+void
+RankSetupTest::testDoubleBlueprint()
+{
+ DoubleBlueprint prototype;
+ prototype.visitDumpFeatures(_indexEnv, _visitor);
+ { // basic test
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ params.push_back("value(5.5).0");
+ params.push_back("value(10.5).0");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 2u);
+ EXPECT_EQUAL(deps.input[0], "value(5.5).0");
+ EXPECT_EQUAL(deps.input[1], "value(10.5).0");
+ EXPECT_EQUAL(deps.output.size(), 2u);
+ EXPECT_EQUAL(deps.output[0], "0");
+ EXPECT_EQUAL(deps.output[1], "1");
+ }
+}
+
+void
+RankSetupTest::testSumBlueprint()
+{
+ SumBlueprint prototype;
+ prototype.visitDumpFeatures(_indexEnv, _visitor);
+ { // basic test
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ params.push_back("value(5.5, 10.5).0");
+ params.push_back("value(5.5, 10.5).1");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 2u);
+ EXPECT_EQUAL(deps.input[0], "value(5.5, 10.5).0");
+ EXPECT_EQUAL(deps.input[1], "value(5.5, 10.5).1");
+ EXPECT_EQUAL(deps.output.size(), 1u);
+ EXPECT_EQUAL(deps.output[0], "out");
+ }
+}
+
+void
+RankSetupTest::testStaticRankBlueprint()
+{
+ StaticRankBlueprint prototype;
+ { // basic test
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ params.push_back("sr1");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 0u);
+ EXPECT_EQUAL(deps.output.size(), 1u);
+ EXPECT_EQUAL(deps.output[0], "out");
+ }
+ { // invalid params
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ EXPECT_TRUE(!bp->setup(_indexEnv, params));
+ params.push_back("sr1");
+ params.push_back("sr2");
+ EXPECT_TRUE(!bp->setup(_indexEnv, params));
+ }
+}
+
+void
+RankSetupTest::testChainBlueprint()
+{
+ ChainBlueprint prototype;
+ { // chaining
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ params.push_back("basic");
+ params.push_back("2");
+ params.push_back("4");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 1u);
+ EXPECT_EQUAL(deps.input[0], "chain(basic,1,4)");
+ }
+ { // leaf node
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ params.push_back("basic");
+ params.push_back("1");
+ params.push_back("4");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 1u);
+ EXPECT_EQUAL(deps.input[0], "value(4)");
+ }
+ { // cycle
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ params.push_back("cycle");
+ params.push_back("1");
+ params.push_back("4");
+ EXPECT_TRUE(bp->setup(_indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 1u);
+ EXPECT_EQUAL(deps.input[0], "chain(cycle,4,4)");
+ }
+ { // invalid params
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ std::vector<vespalib::string> params;
+ EXPECT_TRUE(!bp->setup(_indexEnv, params));
+ params.push_back("basic");
+ params.push_back("0");
+ params.push_back("4");
+ EXPECT_TRUE(!bp->setup(_indexEnv, params));
+ }
+}
+
+void
+RankSetupTest::testCfgValueBlueprint()
+{
+ CfgValueBlueprint prototype;
+ IndexEnvironment indexEnv;
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "1.0");
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "2.0");
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "3.0");
+
+ { // basic test
+ Blueprint::UP bp = prototype.createInstance();
+ DummyDependencyHandler deps(*bp);
+ bp->setName("test_cfgvalue(foo)");
+ std::vector<vespalib::string> params;
+ params.push_back("foo");
+
+ EXPECT_TRUE(bp->setup(indexEnv, params));
+ EXPECT_EQUAL(deps.input.size(), 0u);
+ EXPECT_EQUAL(deps.output.size(), 3u);
+ EXPECT_EQUAL(deps.output[0], "0");
+ EXPECT_EQUAL(deps.output[1], "1");
+ EXPECT_EQUAL(deps.output[2], "2");
+
+ FeatureExecutor::LP fe = bp->createExecutor(_queryEnv);
+ ValueExecutor *vfe = static_cast<ValueExecutor *>(fe.get());
+ EXPECT_EQUAL(vfe->getValues().size(), 3u);
+ EXPECT_EQUAL(vfe->getValues()[0], 1.0f);
+ EXPECT_EQUAL(vfe->getValues()[1], 2.0f);
+ EXPECT_EQUAL(vfe->getValues()[2], 3.0f);
+ }
+}
+
+
+void
+RankSetupTest::testCompilation()
+{
+ { // unknown blueprint
+ RankSetup rs(_factory, _indexEnv);
+ rs.setFirstPhaseRank("unknown");
+ EXPECT_TRUE(!rs.compile());
+ }
+ { // unknown output for initial rank
+ RankSetup rs(_factory, _indexEnv);
+ rs.setFirstPhaseRank("value(2).1");
+ EXPECT_TRUE(!rs.compile());
+ }
+ { // unknown output for dependency
+ RankSetup rs(_factory, _indexEnv);
+ rs.setFirstPhaseRank(FNB().baseName("mysum").parameter("value(2).1").buildName());
+ EXPECT_TRUE(!rs.compile());
+ }
+ { // illegal input parameters
+ RankSetup rs(_factory, _indexEnv);
+ rs.setFirstPhaseRank("value.0");
+ EXPECT_TRUE(!rs.compile());
+ }
+ { // illegal feature name
+ RankSetup rs(_factory, _indexEnv);
+ rs.setFirstPhaseRank("value(2).");
+ EXPECT_TRUE(!rs.compile());
+ }
+ { // almost too deep dependency graph
+ RankSetup rs(_factory, _indexEnv);
+ std::ostringstream oss;
+ oss << "chain(basic," << (BlueprintResolver::MAX_DEP_DEPTH - 1) << ",4)"; // gives tree height == MAX_DEP_DEPTH
+ rs.setFirstPhaseRank(oss.str());
+ EXPECT_TRUE(rs.compile());
+ }
+ { // too deep dependency graph
+ RankSetup rs(_factory, _indexEnv);
+ std::ostringstream oss;
+ oss << "chain(basic," << BlueprintResolver::MAX_DEP_DEPTH << ",4)"; // gives tree height == MAX_DEP_DEPTH + 1
+ rs.setFirstPhaseRank(oss.str());
+ EXPECT_TRUE(!rs.compile());
+ }
+ { // cycle
+ RankSetup rs(_factory, _indexEnv);
+ // c(c,4,2) -> c(c,3,2) -> c(c,2,2) -> c(c,1,2) -> c(c,2,2)
+ rs.setFirstPhaseRank("chain(cycle,4,2)");
+ EXPECT_TRUE(!rs.compile());
+ }
+}
+
+void RankSetupTest::testRankSetup()
+{
+ using namespace search::fef::indexproperties;
+ IndexEnvironment env;
+ env.getProperties().add(rank::FirstPhase::NAME, "firstphase");
+ env.getProperties().add(rank::SecondPhase::NAME, "secondphase");
+ env.getProperties().add(dump::Feature::NAME, "foo");
+ env.getProperties().add(dump::Feature::NAME, "bar");
+ env.getProperties().add(matching::NumThreadsPerSearch::NAME, "3");
+ env.getProperties().add(matchphase::DegradationAttribute::NAME, "mystaticrankattr");
+ env.getProperties().add(matchphase::DegradationAscendingOrder::NAME, "true");
+ env.getProperties().add(matchphase::DegradationMaxHits::NAME, "12345");
+ env.getProperties().add(matchphase::DegradationMaxFilterCoverage::NAME, "0.19");
+ env.getProperties().add(matchphase::DegradationSamplePercentage::NAME, "0.9");
+ env.getProperties().add(matchphase::DegradationPostFilterMultiplier::NAME, "0.7");
+ env.getProperties().add(matchphase::DiversityAttribute::NAME, "mycategoryattr");
+ env.getProperties().add(matchphase::DiversityMinGroups::NAME, "37");
+ env.getProperties().add(matchphase::DiversityCutoffFactor::NAME, "7.1");
+ env.getProperties().add(matchphase::DiversityCutoffStrategy::NAME, "strict");
+ env.getProperties().add(hitcollector::HeapSize::NAME, "50");
+ env.getProperties().add(hitcollector::ArraySize::NAME, "60");
+ env.getProperties().add(hitcollector::EstimatePoint::NAME, "70");
+ env.getProperties().add(hitcollector::EstimateLimit::NAME, "80");
+ env.getProperties().add(hitcollector::RankScoreDropLimit::NAME, "90.5");
+
+ RankSetup rs(_factory, env);
+ rs.configure();
+ EXPECT_EQUAL(rs.getFirstPhaseRank(), vespalib::string("firstphase"));
+ EXPECT_EQUAL(rs.getSecondPhaseRank(), vespalib::string("secondphase"));
+ ASSERT_TRUE(rs.getDumpFeatures().size() == 2);
+ EXPECT_EQUAL(rs.getDumpFeatures()[0], vespalib::string("foo"));
+ EXPECT_EQUAL(rs.getDumpFeatures()[1], vespalib::string("bar"));
+ EXPECT_EQUAL(rs.getNumThreadsPerSearch(), 3u);
+ EXPECT_EQUAL(rs.getDegradationAttribute(), "mystaticrankattr");
+ EXPECT_EQUAL(rs.isDegradationOrderAscending(), true);
+ EXPECT_EQUAL(rs.getDegradationMaxHits(), 12345u);
+ EXPECT_EQUAL(rs.getDegradationSamplePercentage(), 0.9);
+ EXPECT_EQUAL(rs.getDegradationMaxFilterCoverage(), 0.19);
+ EXPECT_EQUAL(rs.getDegradationPostFilterMultiplier(), 0.7);
+ EXPECT_EQUAL(rs.getDiversityAttribute(), "mycategoryattr");
+ EXPECT_EQUAL(rs.getDiversityMinGroups(), 37u);
+ EXPECT_EQUAL(rs.getDiversityCutoffFactor(), 7.1);
+ EXPECT_EQUAL(rs.getDiversityCutoffStrategy(), "strict");
+ EXPECT_EQUAL(rs.getHeapSize(), 50u);
+ EXPECT_EQUAL(rs.getArraySize(), 60u);
+ EXPECT_EQUAL(rs.getEstimatePoint(), 70u);
+ EXPECT_EQUAL(rs.getEstimateLimit(), 80u);
+ EXPECT_EQUAL(rs.getRankScoreDropLimit(), 90.5);
+}
+
+bool
+RankSetupTest::testExecution(const vespalib::string & initRank, feature_t initScore,
+ const vespalib::string & finalRank, feature_t finalScore, uint32_t docId)
+{
+ return testExecution(_rankEnv, initRank, initScore, finalRank, finalScore, docId);
+}
+
+bool
+RankSetupTest::testExecution(const RankEnvironment &rankEnv, const vespalib::string & initRank, feature_t initScore,
+ const vespalib::string & finalRank, feature_t finalScore, uint32_t docId)
+{
+ bool ok = true;
+ RankExecutor re(initRank, finalRank, rankEnv);
+ ok = ok && re.setup();
+ EXPECT_TRUE(ok);
+ RankResult exp;
+ exp.addScore(initRank, initScore);
+ if (finalRank != "") {
+ exp.addScore(finalRank, finalScore);
+ }
+ RankResult rs = re.execute(docId);
+ ok = ok && (exp == rs);
+ EXPECT_EQUAL(exp, rs);
+ return ok;
+}
+
+void
+RankSetupTest::testExecution()
+{
+ { // value executor
+ vespalib::string v = FNB().baseName("value").parameter("5.5").parameter("10.5").buildName();
+ EXPECT_TRUE(testExecution(v + ".0", 5.5f));
+ EXPECT_TRUE(testExecution(v + ".0", 5.5f, v + ".1", 10.5f));
+ EXPECT_TRUE(testExecution(v, 5.5f));
+ }
+ { // double executor
+ vespalib::string d1 = FNB().baseName("double").parameter("value(2).0").parameter("value(8).0").buildName();
+ vespalib::string d2 = FNB().baseName("double").parameter("value(2)").parameter("value(8)").buildName();
+ EXPECT_TRUE(testExecution(d1 + ".0", 4.0f));
+ EXPECT_TRUE(testExecution(d1 + ".0", 4.0f, d1 + ".1", 16.0f));
+ EXPECT_TRUE(testExecution(d2, 4.0f));
+ }
+ { // sum executor
+ vespalib::string s1 = FNB().baseName("mysum").parameter("value(2).0").parameter("value(4).0").output("out").buildName();
+ vespalib::string s2 = FNB().baseName("mysum").parameter("value(2)").parameter("value(4)").buildName();
+ EXPECT_TRUE(testExecution(s1, 6.0f));
+ EXPECT_TRUE(testExecution(s2, 6.0f));
+ }
+ { // static rank executor
+ vespalib::string sr1 = "staticrank(staticrank1)";
+ vespalib::string sr2 = "staticrank(staticrank2)";
+ for (uint32_t i = 0; i < 5; ++i) {
+ EXPECT_TRUE(testExecution(sr1, static_cast<feature_t>(i + 100),
+ sr2, static_cast<feature_t>(i + 200), i));
+ }
+ }
+ { // test topologic sorting
+ vespalib::string v1 = "value(2)";
+ vespalib::string d1 = FNB().baseName("double").parameter(v1).buildName();
+ vespalib::string d2 = FNB().baseName("double").parameter(d1).buildName();
+
+ {
+ vespalib::string s1 = FNB().baseName("mysum").parameter(v1).parameter(d1).parameter(d2).buildName();
+ EXPECT_TRUE(testExecution(s1, 14.0f));
+ }
+ {
+ vespalib::string s1 = FNB().baseName("mysum").parameter(d2).parameter(d1).parameter(v1).buildName();
+ EXPECT_TRUE(testExecution(s1, 14.0f));
+ }
+ }
+ { // output used by more than one
+ vespalib::string v1 = "value(2)";
+ vespalib::string d1 = FNB().baseName("double").parameter(v1).buildName();
+ vespalib::string d2 = FNB().baseName("double").parameter(v1).buildName();
+ vespalib::string s1 = FNB().baseName("mysum").parameter(d1).parameter(d2).buildName();
+ EXPECT_TRUE(testExecution(s1, 8.0f));
+ }
+ { // output not shared between phases
+ vespalib::string v1 = "value(2)";
+ vespalib::string v2 = "value(8)";
+ vespalib::string d1 = FNB().baseName("double").parameter(v1).buildName();
+ vespalib::string d2 = FNB().baseName("double").parameter(v2).buildName();
+ EXPECT_TRUE(testExecution(d1, 4.0f, d2, 16.0f));
+ }
+ { // output shared between phases
+ vespalib::string v1 = "value(2)";
+ vespalib::string v2 = "value(8)";
+ vespalib::string v3 = "value(32)";
+ vespalib::string d1 = FNB().baseName("double").parameter(v1).buildName();
+ vespalib::string d2 = FNB().baseName("double").parameter(v2).buildName();
+ vespalib::string d3 = FNB().baseName("double").parameter(v3).buildName();
+ vespalib::string s1 = FNB().baseName("mysum").parameter(d1).parameter(d2).buildName();
+ vespalib::string s2 = FNB().baseName("mysum").parameter(d2).parameter(d3).buildName();
+ EXPECT_TRUE(testExecution(s1, 20.0f, s2, 80.0f));
+ }
+ { // max dependency depth
+ uint32_t maxDepth = BlueprintResolver::MAX_DEP_DEPTH;
+ std::ostringstream oss;
+ oss << "chain(basic," << (maxDepth - 1) << ",4)"; // gives tree height == MAX_DEP_DEPTH;
+ EXPECT_TRUE(testExecution(oss.str(), 4.0f));
+ }
+ {
+ IndexEnvironment indexEnv;
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "1.0");
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "2.0");
+ indexEnv.getProperties().add("test_cfgvalue(bar).value", "5.0");
+
+ vespalib::string s = FNB().baseName("mysum")
+ .parameter("test_cfgvalue(foo).0")
+ .parameter("test_cfgvalue(foo).1")
+ .buildName();
+
+ EXPECT_TRUE(testExecution(RankEnvironment(_factory, indexEnv, _queryEnv),
+ s, 3.0f, "test_cfgvalue(bar).0", 5.0f));
+ }
+}
+
+void
+RankSetupTest::testFeatureDump()
+{
+ {
+ FeatureDumper dumper(_rankEnv);
+ dumper.addDumpFeature("value(2)");
+ dumper.addDumpFeature("value(4)");
+ dumper.addDumpFeature("double(value(4))");
+ dumper.addDumpFeature("double(value(8))");
+ dumper.addDumpFeature("mysum(value(4),value(16))");
+ dumper.addDumpFeature("mysum(double(value(8)),double(value(32)))");
+ EXPECT_TRUE(dumper.setup());
+
+ RankResult exp;
+ exp.addScore("value(2)", 2.0f);
+ exp.addScore("value(4)", 4.0f);
+ exp.addScore(FNB().baseName("double").parameter("value(4)").buildName(), 8.0f);
+ exp.addScore(FNB().baseName("double").parameter("value(8)").buildName(), 16.0f);
+ exp.addScore(FNB().baseName("mysum").parameter("value(4)").parameter("value(16)").buildName(), 20.0f);
+ exp.addScore(FNB().baseName("mysum").
+ parameter(FNB().baseName("double").parameter("value(8)").buildName()).
+ parameter(FNB().baseName("double").parameter("value(32)").buildName()).
+ buildName(), 80.0f);
+ EXPECT_EQUAL(exp, dumper.dump());
+ }
+ {
+ FeatureDumper dumper(_rankEnv);
+ dumper.addDumpFeature("value(50)");
+ dumper.addDumpFeature("value(100)");
+ EXPECT_TRUE(dumper.setup());
+ RankResult exp;
+ exp.addScore("value(50)", 50.0f);
+ exp.addScore("value(100)", 100.0f);
+ EXPECT_EQUAL(exp, dumper.dump());
+ }
+ {
+ FeatureDumper dumper(_rankEnv);
+ dumper.addDumpFeature(FNB().baseName("rankingExpression").parameter("if(4<2,3,4)").buildName());
+ EXPECT_TRUE(dumper.setup());
+ RankResult exp;
+ exp.addScore(FNB().baseName("rankingExpression").parameter("if(4<2,3,4)").buildName(), 4.0f);
+ EXPECT_EQUAL(exp, dumper.dump());
+ }
+
+ {
+ FeatureDumper dumper(_rankEnv);
+ dumper.addDumpFeature(FNB().baseName("rankingExpression").parameter("if(mysum(value(12),value(10))>2,3,4)").buildName());
+ EXPECT_TRUE(dumper.setup());
+ RankResult exp;
+ exp.addScore(FNB().baseName("rankingExpression").parameter("if(mysum(value(12),value(10))>2,3,4)").buildName(), 3.0f);
+ EXPECT_EQUAL(exp, dumper.dump());
+ }
+ { // dump features indicated by visitation
+ IndexEnvironment indexEnv;
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "1.0");
+ indexEnv.getProperties().add("test_cfgvalue(bar).value", "5.0");
+ indexEnv.getProperties().add("test_cfgvalue.dump", "test_cfgvalue(foo)");
+ indexEnv.getProperties().add("test_cfgvalue.dump", "test_cfgvalue(bar)");
+ indexEnv.getProperties().add(indexproperties::rank::FirstPhase::NAME, "");
+ indexEnv.getProperties().add(indexproperties::rank::SecondPhase::NAME, "");
+
+ RankEnvironment rankEnv(_factory, indexEnv, _queryEnv);
+ FeatureDumper dumper(rankEnv);
+ dumper.configure();
+ EXPECT_TRUE(dumper.setup());
+ RankResult exp;
+ exp.addScore("test_cfgvalue(foo)", 1.0);
+ exp.addScore("test_cfgvalue(bar)", 5.0);
+ EXPECT_EQUAL(exp, dumper.dump());
+ }
+ { // ignore features indicated by visitation
+ IndexEnvironment indexEnv;
+ indexEnv.getProperties().add("test_cfgvalue(foo).value", "1.0");
+ indexEnv.getProperties().add("test_cfgvalue(bar).value", "5.0");
+ indexEnv.getProperties().add("test_cfgvalue.dump", "test_cfgvalue(foo)");
+ indexEnv.getProperties().add("test_cfgvalue.dump", "test_cfgvalue(bar)");
+ indexEnv.getProperties().add(indexproperties::dump::IgnoreDefaultFeatures::NAME, "true");
+ indexEnv.getProperties().add(indexproperties::dump::Feature::NAME, "test_cfgvalue(foo)");
+ indexEnv.getProperties().add(indexproperties::rank::FirstPhase::NAME, "");
+ indexEnv.getProperties().add(indexproperties::rank::SecondPhase::NAME, "");
+
+ RankEnvironment rankEnv(_factory, indexEnv, _queryEnv);
+ FeatureDumper dumper(rankEnv);
+ dumper.configure();
+ EXPECT_TRUE(dumper.setup());
+ RankResult exp;
+ exp.addScore("test_cfgvalue(foo)", 1.0);
+ EXPECT_EQUAL(exp, dumper.dump());
+ }
+}
+
+void
+RankSetupTest::checkFeatures(std::map<vespalib::string, feature_t> &exp, std::map<vespalib::string, feature_t> &actual)
+{
+ typedef std::map<vespalib::string, feature_t>::const_iterator ITR;
+ if (!EXPECT_EQUAL(exp.size(), actual.size())) {
+ return;
+ }
+ ITR exp_itr = exp.begin();
+ ITR exp_end = exp.end();
+ ITR actual_itr = actual.begin();
+ ITR actual_end = actual.end();
+ for (; exp_itr != exp_end && actual_itr != actual_end; ++exp_itr, ++actual_itr) {
+ EXPECT_EQUAL(exp_itr->first, actual_itr->first);
+ EXPECT_APPROX(exp_itr->second, actual_itr->second, 0.001);
+ }
+ EXPECT_EQUAL(exp_itr == exp_end, actual_itr == actual_end);
+}
+
+void
+RankSetupTest::testFeatureNormalization()
+{
+ BlueprintFactory factory;
+ factory.addPrototype(Blueprint::SP(new ValueBlueprint()));
+ factory.addPrototype(Blueprint::SP(new SumBlueprint()));
+
+ IndexEnvironment idxEnv;
+ RankSetup rankSetup(factory, idxEnv);
+
+ rankSetup.setFirstPhaseRank(" mysum ( value ( 1 ) , value ( 1 ) ) ");
+ rankSetup.setSecondPhaseRank(" mysum ( value ( 2 ) , value ( 2 ) ) ");
+ rankSetup.addSummaryFeature(" mysum ( value ( 5 ) , value ( 5 ) ) ");
+ rankSetup.addSummaryFeature(" mysum ( \"value( 5 )\" , \"value( 5 )\" ) ");
+ rankSetup.addDumpFeature(" mysum ( value ( 10 ) , value ( 10 ) ) ");
+ rankSetup.addDumpFeature(" mysum ( \"value( 10 )\" , \"value( 10 )\" ) ");
+
+ ASSERT_TRUE(rankSetup.compile());
+
+ { // RANK context
+ MatchDataLayout layout;
+ QueryEnvironment queryEnv;
+ RankProgram::UP firstPhaseProgram = rankSetup.create_first_phase_program();
+ RankProgram::UP secondPhaseProgram = rankSetup.create_second_phase_program();
+ RankProgram::UP summaryProgram = rankSetup.create_summary_program();
+ firstPhaseProgram->setup(layout, queryEnv);
+ secondPhaseProgram->setup(layout, queryEnv);
+ summaryProgram->setup(layout, queryEnv);
+
+ firstPhaseProgram->run(1);
+ EXPECT_APPROX(2.0, *Utils::getScoreFeature(*firstPhaseProgram), 0.001);
+ secondPhaseProgram->run(1);
+ EXPECT_APPROX(4.0, *Utils::getScoreFeature(*secondPhaseProgram), 0.001);
+ summaryProgram->run(1);
+
+ { // rank seed features
+ std::map<vespalib::string, feature_t> actual = Utils::getSeedFeatures(*summaryProgram);
+ std::map<vespalib::string, feature_t> exp;
+ exp["mysum(value(5),value(5))"] = 10.0;
+ exp["mysum(\"value( 5 )\",\"value( 5 )\")"] = 10.0;
+ TEST_DO(checkFeatures(exp, actual));
+ }
+ { // all rank features (1. phase)
+ std::map<vespalib::string, feature_t> actual = Utils::getAllFeatures(*firstPhaseProgram);
+ std::map<vespalib::string, feature_t> exp;
+ exp["value(1)"] = 1.0;
+ exp["value(1).0"] = 1.0;
+ exp["mysum(value(1),value(1))"] = 2.0;
+ exp["mysum(value(1),value(1)).out"] = 2.0;
+ TEST_DO(checkFeatures(exp, actual));
+ }
+ { // all rank features (2. phase)
+ std::map<vespalib::string, feature_t> actual = Utils::getAllFeatures(*secondPhaseProgram);
+ std::map<vespalib::string, feature_t> exp;
+ exp["value(2)"] = 2.0;
+ exp["value(2).0"] = 2.0;
+ exp["mysum(value(2),value(2))"] = 4.0;
+ exp["mysum(value(2),value(2)).out"] = 4.0;
+ TEST_DO(checkFeatures(exp, actual));
+ }
+ { // all rank features (summary)
+ std::map<vespalib::string, feature_t> actual = Utils::getAllFeatures(*summaryProgram);
+ std::map<vespalib::string, feature_t> exp;
+ exp["value(5)"] = 5.0;
+ exp["value(5).0"] = 5.0;
+ exp["mysum(value(5),value(5))"] = 10.0;
+ exp["mysum(value(5),value(5)).out"] = 10.0;
+ exp["mysum(\"value( 5 )\",\"value( 5 )\")"] = 10.0;
+ exp["mysum(\"value( 5 )\",\"value( 5 )\").out"] = 10.0;
+ TEST_DO(checkFeatures(exp, actual));
+ }
+ }
+
+ { // DUMP context
+ MatchDataLayout layout;
+ QueryEnvironment queryEnv;
+ RankProgram::UP rankProgram = rankSetup.create_dump_program();
+ rankProgram->setup(layout, queryEnv);
+ rankProgram->run(1);
+
+ { // dump seed features
+ std::map<vespalib::string, feature_t> actual = Utils::getSeedFeatures(*rankProgram);
+ std::map<vespalib::string, feature_t> exp;
+ exp["mysum(value(10),value(10))"] = 20.0;
+ exp["mysum(\"value( 10 )\",\"value( 10 )\")"] = 20.0;
+ TEST_DO(checkFeatures(exp, actual));
+ }
+
+ { // all dump features
+ std::map<vespalib::string, feature_t> actual = Utils::getAllFeatures(*rankProgram);
+ std::map<vespalib::string, feature_t> exp;
+
+ exp["value(10)"] = 10.0;
+ exp["value(10).0"] = 10.0;
+
+ exp["mysum(value(10),value(10))"] = 20.0;
+ exp["mysum(value(10),value(10)).out"] = 20.0;
+
+ exp["mysum(\"value( 10 )\",\"value( 10 )\")"] = 20.0;
+ exp["mysum(\"value( 10 )\",\"value( 10 )\").out"] = 20.0;
+
+ TEST_DO(checkFeatures(exp, actual));
+ }
+ }
+}
+
+
+RankSetupTest::RankSetupTest() :
+ _factory(),
+ _manager(),
+ _indexEnv(),
+ _queryEnv(),
+ _rankEnv(_factory, _indexEnv, _queryEnv),
+ _visitor()
+{
+ // register blueprints
+ setup_fef_test_plugin(_factory);
+ _factory.addPrototype(Blueprint::SP(new ValueBlueprint()));
+ _factory.addPrototype(Blueprint::SP(new RankingExpressionBlueprint()));
+
+ // setup an original attribute manager with two attributes
+ search::attribute::Config cfg(search::attribute::BasicType::INT32,
+ search::attribute::CollectionType::SINGLE);
+ search::AttributeVector::SP av1 =
+ search::AttributeFactory::createAttribute("staticrank1", cfg);
+ search::AttributeVector::SP av2 =
+ search::AttributeFactory::createAttribute("staticrank2", cfg);
+ av1->addDocs(5);
+ av2->addDocs(5);
+ for (uint32_t i = 0; i < 5; ++i) {
+ (static_cast<search::IntegerAttribute *>(av1.get()))->update(i, i + 100);
+ (static_cast<search::IntegerAttribute *>(av2.get()))->update(i, i + 200);
+ }
+ av1->commit();
+ av2->commit();
+ _manager.add(av1);
+ _manager.add(av2);
+
+ // set the index environment
+ _queryEnv.setIndexEnv(&_indexEnv);
+
+ // set the manager
+ _queryEnv.overrideAttributeManager(&_manager);
+}
+
+
+int
+RankSetupTest::Main()
+{
+ TEST_INIT("ranksetup_test");
+
+ testValueBlueprint();
+ testDoubleBlueprint();
+ testSumBlueprint();
+ testStaticRankBlueprint();
+ testChainBlueprint();
+ testCfgValueBlueprint();
+
+ testCompilation();
+ testRankSetup();
+ testExecution();
+ testFeatureDump();
+ testFeatureNormalization();
+
+ TEST_DONE();
+}
+
+TEST_APPHOOK(RankSetupTest);
diff --git a/searchlib/src/tests/ranksetup/verify_feature/.gitignore b/searchlib/src/tests/ranksetup/verify_feature/.gitignore
new file mode 100644
index 00000000000..69a39cd13f2
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/verify_feature/.gitignore
@@ -0,0 +1 @@
+searchlib_verify_feature_test_app
diff --git a/searchlib/src/tests/ranksetup/verify_feature/CMakeLists.txt b/searchlib/src/tests/ranksetup/verify_feature/CMakeLists.txt
new file mode 100644
index 00000000000..8ffd79fe327
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/verify_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_verify_feature_test_app
+ SOURCES
+ verify_feature_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_verify_feature_test_app COMMAND searchlib_verify_feature_test_app)
diff --git a/searchlib/src/tests/ranksetup/verify_feature/FILES b/searchlib/src/tests/ranksetup/verify_feature/FILES
new file mode 100644
index 00000000000..652373e33da
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/verify_feature/FILES
@@ -0,0 +1 @@
+verify_feature_test.cpp
diff --git a/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp b/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp
new file mode 100644
index 00000000000..1e49cd4aae6
--- /dev/null
+++ b/searchlib/src/tests/ranksetup/verify_feature/verify_feature_test.cpp
@@ -0,0 +1,58 @@
+// 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/fef/fef.h>
+#include <vespa/searchlib/fef/test/indexenvironment.h>
+#include <vespa/searchlib/fef/test/plugin/setup.h>
+#include <vespa/searchlib/features/valuefeature.h>
+
+using namespace search::features;
+using namespace search::fef::test;
+using namespace search::fef;
+
+struct RankFixture {
+ BlueprintFactory factory;
+ IndexEnvironment indexEnv;
+
+ RankFixture() : factory(), indexEnv() {
+ setup_fef_test_plugin(factory);
+ factory.addPrototype(Blueprint::SP(new ValueBlueprint()));
+ }
+
+ bool verify(const std::string &feature) const {
+ return verifyFeature(factory, indexEnv, feature, "feature verification test");
+ }
+};
+
+TEST_F("verify valid rank feature", RankFixture) {
+ EXPECT_TRUE(f1.verify("value(1, 2, 3).0"));
+ EXPECT_TRUE(f1.verify("value(1, 2, 3).1"));
+ EXPECT_TRUE(f1.verify("value(1, 2, 3).2"));
+}
+
+TEST_F("verify unknown feature", RankFixture) {
+ EXPECT_FALSE(f1.verify("unknown"));
+}
+
+TEST_F("verify unknown output", RankFixture) {
+ EXPECT_FALSE(f1.verify("value(1, 2, 3).3"));
+}
+
+TEST_F("verify illegal input parameters", RankFixture) {
+ EXPECT_FALSE(f1.verify("value.0"));
+}
+
+TEST_F("verify illegal feature name", RankFixture) {
+ EXPECT_FALSE(f1.verify("value(2)."));
+}
+
+TEST_F("verify too deep dependency graph", RankFixture) {
+ EXPECT_TRUE(f1.verify("chain(basic, 63, 4)"));
+ EXPECT_FALSE(f1.verify("chain(basic, 64, 4)"));
+}
+
+TEST_F("verify dependency cycle", RankFixture) {
+ EXPECT_FALSE(f1.verify("chain(cycle, 4, 2)"));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }