// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generate.h" using namespace vespalib; using namespace vespalib::eval; using namespace vespalib::eval::test; using namespace vespalib::slime::convenience; using slime::JsonFormat; using tensor::DefaultTensorEngine; //----------------------------------------------------------------------------- uint8_t unhex(char c) { if (c >= '0' && c <= '9') { return (c - '0'); } if (c >= 'A' && c <= 'F') { return ((c - 'A') + 10); } TEST_ERROR("bad hex char"); return 0; } void extract_data_from_string(Memory hex_dump, nbostream &data) { if ((hex_dump.size > 2) && (hex_dump.data[0] == '0') && (hex_dump.data[1] == 'x')) { for (size_t i = 2; i < (hex_dump.size - 1); i += 2) { data << uint8_t((unhex(hex_dump.data[i]) << 4) | unhex(hex_dump.data[i + 1])); } } } nbostream extract_data(const Inspector &value) { nbostream data; if (value.asString().size > 0) { extract_data_from_string(value.asString(), data); } else { Memory buf = value.asData(); data.write(buf.data, buf.size); } return data; } //----------------------------------------------------------------------------- void insert_value(Cursor &cursor, const vespalib::string &name, const TensorSpec &spec) { nbostream data; Value::UP value = SimpleTensorEngine::ref().from_spec(spec); SimpleTensorEngine::ref().encode(*value, data); cursor.setData(name, Memory(data.peek(), data.size())); } TensorSpec extract_value(const Inspector &inspector) { nbostream data = extract_data(inspector); const auto &engine = SimpleTensorEngine::ref(); return engine.to_spec(*engine.decode(data)); } //----------------------------------------------------------------------------- std::vector get_types(const std::vector ¶m_values) { std::vector param_types; for (size_t i = 0; i < param_values.size(); ++i) { param_types.emplace_back(param_values[i]->type()); } return param_types; } TensorSpec eval_expr(const Inspector &test, const TensorEngine &engine, bool typed) { Function fun = Function::parse(test["expression"].asString().make_string()); std::vector param_values; std::vector param_refs; for (size_t i = 0; i < fun.num_params(); ++i) { param_values.emplace_back(engine.from_spec(extract_value(test["inputs"][fun.param_name(i)]))); param_refs.emplace_back(*param_values.back()); } NodeTypes types = typed ? NodeTypes(fun, get_types(param_values)) : NodeTypes(); InterpretedFunction ifun(engine, fun, types); InterpretedFunction::Context ctx(ifun); InterpretedFunction::SimpleObjectParams params(param_refs); return engine.to_spec(ifun.eval(ctx, params)); } //----------------------------------------------------------------------------- std::vector extract_fields(const Inspector &object) { struct FieldExtractor : slime::ObjectTraverser { std::vector result; void field(const Memory &symbol, const Inspector &) override { result.push_back(symbol.make_string()); } } extractor; object.traverse(extractor); return std::move(extractor.result); }; //----------------------------------------------------------------------------- class MyTestBuilder : public TestBuilder { private: TestWriter _writer; void make_test(const vespalib::string &expression, const std::map &input_map, const TensorSpec *expect = nullptr) { Cursor &test = _writer.create(); test.setString("expression", expression); Cursor &inputs = test.setObject("inputs"); for (const auto &input: input_map) { insert_value(inputs, input.first, input.second); } if (expect != nullptr) { insert_value(test.setObject("result"), "expect", *expect); } else { insert_value(test.setObject("result"), "expect", eval_expr(test, SimpleTensorEngine::ref(), false)); } } public: MyTestBuilder(Output &out) : _writer(out) {} void add(const vespalib::string &expression, const std::map &inputs, const TensorSpec &expect) override { make_test(expression, inputs, &expect); } void add(const vespalib::string &expression, const std::map &inputs) override { make_test(expression, inputs); } }; void generate(Output &out) { MyTestBuilder my_test_builder(out); Generator::generate(my_test_builder); } //----------------------------------------------------------------------------- void evaluate(Input &in, Output &out) { auto handle_test = [&out](Slime &slime) { insert_value(slime["result"], "cpp_prod", eval_expr(slime.get(), DefaultTensorEngine::ref(), true)); insert_value(slime["result"], "cpp_prod_untyped", eval_expr(slime.get(), DefaultTensorEngine::ref(), false)); insert_value(slime["result"], "cpp_ref_typed", eval_expr(slime.get(), SimpleTensorEngine::ref(), true)); write_compact(slime, out); }; auto handle_summary = [&out](Slime &slime) { write_compact(slime, out); }; for_each_test(in, handle_test, handle_summary); } //----------------------------------------------------------------------------- void dump_test(const Inspector &test) { fprintf(stderr, "expression: '%s'\n", test["expression"].asString().make_string().c_str()); for (const auto &input: extract_fields(test["inputs"])) { auto value = extract_value(test["inputs"][input]); fprintf(stderr, "input '%s': %s\n", input.c_str(), value.to_string().c_str()); } } void verify(Input &in, Output &out) { std::map result_map; auto handle_test = [&out,&result_map](Slime &slime) { TensorSpec reference_result = eval_expr(slime.get(), SimpleTensorEngine::ref(), false); for (const auto &result: extract_fields(slime["result"])) { ++result_map[result]; TEST_STATE(make_string("verifying result: '%s'", result.c_str()).c_str()); if (!EXPECT_EQUAL(reference_result, extract_value(slime["result"][result]))) { dump_test(slime.get()); } } }; auto handle_summary = [&out,&result_map](Slime &slime) { Cursor &stats = slime.get().setObject("stats"); for (const auto &entry: result_map) { stats.setLong(entry.first, entry.second); } JsonFormat::encode(slime, out, false); }; for_each_test(in, handle_test, handle_summary); } //----------------------------------------------------------------------------- int usage(const char *self) { fprintf(stderr, "usage: %s \n", self); fprintf(stderr, " : which mode to activate\n"); fprintf(stderr, " 'generate': write test cases to stdout\n"); fprintf(stderr, " 'evaluate': read test cases from stdin, annotate them with\n"); fprintf(stderr, " results from various implementations and write\n"); fprintf(stderr, " them to stdout\n"); fprintf(stderr, " 'verify': read annotated test cases from stdin and verify\n"); fprintf(stderr, " that all results are as expected\n"); return 1; } int main(int argc, char **argv) { StdIn std_in; StdOut std_out; if (argc != 2) { return usage(argv[0]); } vespalib::string mode = argv[1]; TEST_MASTER.init(make_string("vespa-tensor-conformance-%s", mode.c_str()).c_str()); if (mode == "generate") { generate(std_out); } else if (mode == "evaluate") { evaluate(std_in, std_out); } else if (mode == "verify") { verify(std_in, std_out); } else { TEST_ERROR(make_string("unknown mode: %s", mode.c_str()).c_str()); } return (TEST_MASTER.fini() ? 0 : 1); }