diff options
author | Haavard <havardpe@yahoo-inc.com> | 2016-09-12 10:00:22 +0000 |
---|---|---|
committer | Haavard <havardpe@yahoo-inc.com> | 2016-09-13 13:55:31 +0000 |
commit | e73369b1a6c100acef088d6bc7b4a7757f958a39 (patch) | |
tree | 4bfa152a0ee49a00eeca5aef0c16b5d7fc2be59b /vespalib/src | |
parent | 236f096bd71bba0c0cd7b2fdbe74c2bd7e2c22b5 (diff) |
extend conformance test WIP
Diffstat (limited to 'vespalib/src')
3 files changed, 252 insertions, 19 deletions
diff --git a/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp b/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp index 95991d9dd79..91dea9fb0ce 100644 --- a/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp +++ b/vespalib/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp @@ -8,16 +8,12 @@ using vespalib::eval::SimpleTensorEngine; using vespalib::eval::test::TensorConformance; using vespalib::tensor::DefaultTensorEngine; -TEST_F("require that reference tensor implementation passes conformance test", - TensorConformance(SimpleTensorEngine::ref())) -{ - TEST_DO(f1.run_all_tests()); +TEST("require that reference tensor implementation passes conformance test") { + TEST_DO(TensorConformance::run_tests(SimpleTensorEngine::ref())); } -IGNORE_TEST_F("require that production tensor implementation passes conformance test", - TensorConformance(DefaultTensorEngine::ref())) -{ - TEST_DO(f1.run_all_tests()); +IGNORE_TEST("require that production tensor implementation passes conformance test") { + TEST_DO(TensorConformance::run_tests(DefaultTensorEngine::ref())); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp index fa62fcc1f10..c844a59ca47 100644 --- a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp +++ b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.cpp @@ -4,22 +4,264 @@ #include <vespa/vespalib/testkit/test_kit.h> #include "tensor_conformance.h" #include <vespa/vespalib/eval/simple_tensor_engine.h> +#include <vespa/vespalib/eval/tensor_spec.h> +#include <vespa/vespalib/eval/function.h> +#include <vespa/vespalib/eval/interpreted_function.h> namespace vespalib { namespace eval { namespace test { namespace { -void dummy_test(const TensorEngine &engine) { - EXPECT_TRUE(&engine == &SimpleTensorEngine::ref()); +// virtual ValueType type_of(const Tensor &tensor) const = 0; +// virtual bool equal(const Tensor &a, const Tensor &b) const = 0; + +// virtual TensorFunction::UP compile(tensor_function::Node_UP expr) const { return std::move(expr); } + +// virtual std::unique_ptr<Tensor> create(const TensorSpec &spec) const = 0; + +// virtual const Value &reduce(const Tensor &tensor, const BinaryOperation &op, const std::vector<vespalib::string> &dimensions, Stash &stash) const = 0; +// virtual const Value &map(const UnaryOperation &op, const Tensor &a, Stash &stash) const = 0; +// virtual const Value &apply(const BinaryOperation &op, const Tensor &a, const Tensor &b, Stash &stash) const = 0; + +// Random access sequence of numbers +struct Sequence { + virtual double get(size_t i) const = 0; + virtual ~Sequence() {} +}; + +// Sequence of natural numbers (starting at 1) +struct N : Sequence { + double get(size_t i) const override { return (1.0 + i); } +}; + +// Sequence of another sequence divided by 10 +struct Div10 : Sequence { + const Sequence &seq; + Div10(const Sequence &seq_in) : seq(seq_in) {} + double get(size_t i) const override { return (seq.get(i) / 10.0); } +}; + +// Sequence of a unary operator applied to a sequence +struct OpSeq : Sequence { + const Sequence &seq; + const UnaryOperation &op; + OpSeq(const Sequence &seq_in, const UnaryOperation &op_in) : seq(seq_in), op(op_in) {} + double get(size_t i) const override { return op.eval(seq.get(i)); } +}; + +// pre-defined sequence of numbers +struct Seq : Sequence { + std::vector<double> seq; + Seq(const std::vector<double> &seq_in) : seq(seq_in) {} + double get(size_t i) const override { + ASSERT_LESS(i, seq.size()); + return seq[i]; + } +}; + +// custom op1 +struct MyOp : CustomUnaryOperation { + double eval(double b) const override { return ((b + 1) * 2); } +}; + +// A collection of labels for a single dimension +struct Space { + vespalib::string name; + size_t size; // indexed + std::vector<vespalib::string> keys; // mapped + Space(const vespalib::string &name_in, size_t size_in) + : name(name_in), size(size_in), keys() {} + Space(const vespalib::string &name_in, const std::vector<vespalib::string> &keys_in) + : name(name_in), size(0), keys(keys_in) {} +}; + +// Infer the tensor type spanned by the given spaces +vespalib::string infer_type(const std::vector<Space> &spaces) { + if (spaces.empty()) { + return "double"; + } + std::vector<ValueType::Dimension> dimensions; + for (const auto &space: spaces) { + if (space.size == 0) { + dimensions.emplace_back(space.name); // mapped + } else { + dimensions.emplace_back(space.name, space.size); // indexed + } + } + return ValueType::tensor_type(dimensions).to_spec(); } +// Mix spaces with a number sequence to make a tensor spec +class TensorSpecBuilder +{ +private: + using Label = TensorSpec::Label; + using Address = TensorSpec::Address; + + const std::vector<Space> &_spaces; + const Sequence &_seq; + TensorSpec _spec; + Address _addr; + size_t _gen_idx; + + void generate(size_t space_idx) { + if (space_idx == _spaces.size()) { + _spec.add(_addr, _seq.get(_gen_idx++)); + } else { + const Space &space = _spaces[space_idx]; + if (space.size > 0) { // indexed + for (size_t i = 0; i < space.size; ++i) { + _addr.emplace(space.name, Label(i)).first->second = Label(i); + generate(space_idx + 1); + } + } else { // mapped + for (const vespalib::string &key: space.keys) { + _addr.emplace(space.name, Label(key)).first->second = Label(key); + generate(space_idx + 1); + } + } + } + } + +public: + TensorSpecBuilder(const std::vector<Space> &spaces, const Sequence &seq) + : _spaces(spaces), _seq(seq), _spec(infer_type(spaces)), _addr(), _gen_idx(0) {} + TensorSpec build() { + generate(0); + return _spec; + } +}; + +// Test wrapper to avoid passing global test parameters around +struct TestContext { + const TensorEngine &engine; + TestContext(const TensorEngine &engine_in) : engine(engine_in) {} + + std::unique_ptr<Tensor> tensor(const std::vector<Space> &spaces, const Sequence &seq) { + return engine.create(TensorSpecBuilder(spaces, seq).build()); + } + + void verify_create_type(const vespalib::string &type_spec) { + auto tensor = engine.create(TensorSpec(type_spec)); + EXPECT_TRUE(&engine == &tensor->engine()); + EXPECT_EQUAL(type_spec, engine.type_of(*tensor).to_spec()); + } + + void verify_not_equal(const Tensor &a, const Tensor &b) { + EXPECT_FALSE(a == b); + EXPECT_FALSE(b == a); + } + + void verify_verbatim_tensor(const vespalib::string &tensor_expr, const Tensor &expect) { + InterpretedFunction::Context ctx; + InterpretedFunction ifun(engine, Function::parse(tensor_expr)); + const Value &result = ifun.eval(ctx); + if (EXPECT_TRUE(result.is_tensor())) { + const Tensor *actual = result.as_tensor(); + EXPECT_EQUAL(*actual, expect); + } + } + + void test_tensor_create_type() { + TEST_DO(verify_create_type("double")); + TEST_DO(verify_create_type("tensor(x{})")); + TEST_DO(verify_create_type("tensor(x{},y{})")); + TEST_DO(verify_create_type("tensor(x[5])")); + TEST_DO(verify_create_type("tensor(x[5],y[10])")); + TEST_DO(verify_create_type("tensor(x{},y[10])")); + TEST_DO(verify_create_type("tensor(x[5],y{})")); + } + + void test_tensor_inequality() { + TEST_DO(verify_not_equal(*engine.create(TensorSpec("double")), + *engine.create(TensorSpec("tensor(x{})")))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("double")), + *engine.create(TensorSpec("tensor(x[1])")))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})")), + *engine.create(TensorSpec("tensor(y{})")))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])")), + *engine.create(TensorSpec("tensor(x[2])")))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])")), + *engine.create(TensorSpec("tensor(y[1])")))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})")), + *engine.create(TensorSpec("tensor(x[1])")))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("double").add({}, 1)), + *engine.create(TensorSpec("double").add({}, 2)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 1)), + *engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 2)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 1)), + *engine.create(TensorSpec("tensor(x{})").add({{"x", "b"}}, 1)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{})").add({{"x", "a"}}, 1)), + *engine.create(TensorSpec("tensor(x{},y{})").add({{"x", "a"},{"y", "a"}}, 1)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 1)), + *engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 2)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 1)), + *engine.create(TensorSpec("tensor(x[2])").add({{"x", 0}}, 1)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[2])").add({{"x", 0}}, 1)), + *engine.create(TensorSpec("tensor(x[2])").add({{"x", 1}}, 1)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[1])").add({{"x", 0}}, 1)), + *engine.create(TensorSpec("tensor(x[1],y[1])").add({{"x", 0},{"y", 0}}, 1)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "a"},{"y", 0}}, 1)), + *engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "a"},{"y", 0}}, 2)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "a"},{"y", 0}}, 1)), + *engine.create(TensorSpec("tensor(x{},y[1])").add({{"x", "b"},{"y", 0}}, 1)))); + TEST_DO(verify_not_equal(*engine.create(TensorSpec("tensor(x[2],y{})").add({{"x", 0},{"y", "a"}}, 1)), + *engine.create(TensorSpec("tensor(x[2],y{})").add({{"x", 1},{"y", "a"}}, 1)))); + } + + void test_verbatim_tensors() { + TEST_DO(verify_verbatim_tensor("{}", *engine.create(TensorSpec("double")))); + TEST_DO(verify_verbatim_tensor("{{}:5}", *engine.create(TensorSpec("double").add({}, 5)))); + TEST_DO(verify_verbatim_tensor("{{x:foo}:1,{x:bar}:2,{x:baz}:3}", *engine.create(TensorSpec("tensor(x{})") + .add({{"x", "foo"}}, 1) + .add({{"x", "bar"}}, 2) + .add({{"x", "baz"}}, 3)))); + TEST_DO(verify_verbatim_tensor("{{x:foo,y:a}:1,{y:b,x:bar}:2}", *engine.create(TensorSpec("tensor(x{},y{})") + .add({{"x", "foo"}, {"y", "a"}}, 1) + .add({{"x", "bar"}, {"y", "b"}}, 2)))); + } + + void verify_map_op(const UnaryOperation &op, const Tensor &input, const Tensor &expect) { + Stash stash; + const Value &result = engine.map(op, input, stash); + if (EXPECT_TRUE(result.is_tensor())) { + const Tensor &actual = *result.as_tensor(); + EXPECT_EQUAL(actual, expect); + } + } + + void test_map_op(const UnaryOperation &op, const Sequence &seq) { + TEST_DO(verify_map_op(op, + *tensor({Space("x", 10)}, seq), + *tensor({Space("x", 10)}, OpSeq(seq, op)))); + TEST_DO(verify_map_op(op, + *tensor({Space("x", {"a", "b", "c"})}, seq), + *tensor({Space("x", {"a", "b", "c"})}, OpSeq(seq, op)))); + } + + void test_tensor_map() { + TEST_DO(test_map_op(operation::Floor(), Div10(N()))); + TEST_DO(test_map_op(operation::Ceil(), Div10(N()))); + TEST_DO(test_map_op(operation::Sqrt(), Div10(N()))); + TEST_DO(test_map_op(MyOp(), Div10(N()))); + } + + void run_all_tests() { + TEST_DO(test_tensor_create_type()); + TEST_DO(test_tensor_inequality()); + TEST_DO(test_verbatim_tensors()); + TEST_DO(test_tensor_map()); + } +}; + } // namespace vespalib::eval::test::<unnamed> void -TensorConformance::run_all_tests() const +TensorConformance::run_tests(const TensorEngine &engine) { - TEST_DO(dummy_test(_engine)); + TestContext ctx(engine); + ctx.run_all_tests(); } } // namespace vespalib::eval::test diff --git a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h index 0fbde9475f3..8d90f548b33 100644 --- a/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h +++ b/vespalib/src/vespa/vespalib/eval/test/tensor_conformance.h @@ -12,13 +12,8 @@ namespace test { * A collection of tensor-related tests that can be run for various * implementations of the TensorEngine interface. **/ -class TensorConformance -{ -private: - const TensorEngine &_engine; -public: - TensorConformance(const TensorEngine &engine) : _engine(engine) {} - void run_all_tests() const; +struct TensorConformance { + static void run_tests(const TensorEngine &engine); }; } // namespace vespalib::eval::test |