summaryrefslogtreecommitdiffstats
path: root/searchlib/src/tests/fef/object_passing/object_passing_test.cpp
blob: ce9585f11cbc9296e602087cbf6dbbefd5a24067 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2017 Yahoo Holdings. 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/vespalib/stllike/string.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/searchlib/features/valuefeature.h>
#include <vespa/searchlib/fef/blueprintfactory.h>
#include <vespa/searchlib/fef/test/indexenvironment.h>
#include <vespa/searchlib/fef/test/queryenvironment.h>
#include <vespa/searchlib/fef/test/plugin/unbox.h>
#include <vespa/searchlib/fef/matchdatalayout.h>
#include <vespa/searchlib/fef/rank_program.h>
#include <vespa/searchlib/fef/verify_feature.h>
#include <vespa/eval/eval/value_type.h>
#include <vespa/searchlib/fef/feature_type.h>

using namespace search::fef;
using namespace search::fef::test;
using namespace search::features;
using vespalib::eval::ValueType;

struct ProxyExecutor : FeatureExecutor {
    bool                      input_is_object;
    bool                      output_is_object;
    double                    number_value;
    vespalib::eval::Value::UP object_value;
    ProxyExecutor(bool input_is_object_in, bool output_is_object_in)
        : input_is_object(input_is_object_in), output_is_object(output_is_object_in),
          number_value(0.0), object_value() {}
    bool isPure() override { return true; }
    void execute(uint32_t) override {
        double was_object = 0.0;
        if (input_is_object) {
            was_object = 1.0;
            number_value = inputs().get_object(0).get().as_double();
            object_value.reset(new vespalib::eval::DoubleValue(number_value));
        } else {
            number_value = inputs().get_number(0);
            object_value.reset(new vespalib::eval::DoubleValue(number_value));
        }
        if (output_is_object) {
            outputs().set_object(0, *object_value);
        } else {
            outputs().set_number(0, number_value);
        }
        outputs().set_number(1, was_object);
    }
};

struct ProxyBlueprint : Blueprint {
    vespalib::string name;
    AcceptInput accept_input;
    bool object_input;
    bool object_output;
    ProxyBlueprint(const vespalib::string &name_in, AcceptInput accept_input_in, bool object_output_in)
        : Blueprint(name_in), name(name_in), accept_input(accept_input_in), object_input(false), object_output(object_output_in) {}
    void visitDumpFeatures(const IIndexEnvironment &, IDumpFeatureVisitor &) const override {}
    Blueprint::UP createInstance() const override {
        return Blueprint::UP(new ProxyBlueprint(name, accept_input, object_output));
    }
    bool setup(const IIndexEnvironment &, const std::vector<vespalib::string> &params) override {
        ASSERT_EQUAL(1u, params.size());
        object_input = defineInput(params[0], accept_input);
        describeOutput("value", "the value", object_output ? FeatureType::object(ValueType::double_type()) : FeatureType::number());
        describeOutput("was_object", "whether input was object", FeatureType::number());
        return true;
    }
    FeatureExecutor &createExecutor(const IQueryEnvironment &, vespalib::Stash &stash) const override {
        return stash.create<ProxyExecutor>(object_input, object_output);
    }
};

struct Fixture {
    BlueprintFactory factory;
    IndexEnvironment indexEnv;

    explicit Fixture() {
        factory.addPrototype(std::make_shared<ValueBlueprint>());
        factory.addPrototype(std::make_shared<UnboxBlueprint>());
        factory.addPrototype(std::make_shared<ProxyBlueprint>("box",         Blueprint::AcceptInput::NUMBER, true));
        factory.addPrototype(std::make_shared<ProxyBlueprint>("maybe_box",   Blueprint::AcceptInput::ANY,    true));
        factory.addPrototype(std::make_shared<ProxyBlueprint>("maybe_unbox", Blueprint::AcceptInput::ANY,    false));
    }

    double eval(const vespalib::string &feature) {
        BlueprintResolver::SP resolver(new BlueprintResolver(factory, indexEnv));
        resolver->addSeed(feature);
        ASSERT_TRUE(resolver->compile());
        MatchDataLayout mdl;
        MatchData::UP md = mdl.createMatchData();
        QueryEnvironment queryEnv(&indexEnv);
        Properties overrides;
        RankProgram program(resolver);
        program.setup(*md, queryEnv, overrides);        
        auto result = program.get_seeds();
        EXPECT_EQUAL(1u, result.num_features());
        EXPECT_TRUE(!result.is_object(0)); // verifies auto-unboxing
        return result.resolve(0).as_number(1);
    }

    bool verify(const vespalib::string &feature) {
        return verifyFeature(factory, indexEnv, feature, "unit test");
    }
};

TEST_F("require that values can be boxed and unboxed", Fixture()) {
    EXPECT_EQUAL(3.0, f1.eval("box(value(3))"));
    EXPECT_EQUAL(0.0, f1.eval("box(value(3)).was_object"));
    EXPECT_EQUAL(3.0, f1.eval("unbox(box(value(3)))"));
    EXPECT_EQUAL(1.0, f1.eval("maybe_unbox(box(value(3))).was_object"));
    EXPECT_EQUAL(3.0, f1.eval("box(unbox(box(value(3))))"));
    EXPECT_EQUAL(0.0, f1.eval("box(unbox(box(value(3)))).was_object"));
}

TEST_F("require that output features may be either objects or numbers", Fixture()) {
    EXPECT_TRUE(f1.verify("value(3)"));
    EXPECT_TRUE(f1.verify("box(value(3))"));
}

TEST_F("require that feature input/output types must be compatible", Fixture()) {
    EXPECT_TRUE(!f1.verify("unbox(value(3))"));
    EXPECT_TRUE(f1.verify("maybe_unbox(value(3))"));
    EXPECT_TRUE(f1.verify("unbox(box(value(3)))"));
    EXPECT_TRUE(!f1.verify("unbox(box(box(value(3))))"));
    EXPECT_TRUE(f1.verify("unbox(maybe_box(box(value(3))))"));
    EXPECT_TRUE(f1.verify("unbox(box(unbox(box(value(3)))))"));
}

TEST_MAIN() { TEST_RUN_ALL(); }