blob: fe10b48e935bcaeafba86c79e93f0ff3b315533f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#ifndef IMPALA_UDF_TEST_HARNESS_H
#define IMPALA_UDF_TEST_HARNESS_H
// THIS FILE IS USED BY THE STANDALONE IMPALA UDF DEVELOPMENT KIT.
// IT MUST BE BUILDABLE WITH C++98 AND WITHOUT ANY INTERNAL IMPALA HEADERS.
#include <iostream>
#include <vector>
#include <boost/function.hpp>
#include <boost/scoped_ptr.hpp>
#include "udf/udf.h"
#include "udf/udf-debug.h"
namespace impala {
class MemPool;
class RuntimeState;
}
namespace impala_udf {
/// Utility class to help test UDFs.
class UdfTestHarness {
public:
/// Create a test FunctionContext object. 'arg_types' should contain a TypeDesc for each
/// argument of the UDF not including the FunctionContext*. The caller is responsible
/// for calling delete on it. This context has additional debugging validation enabled.
static FunctionContext* CreateTestContext(const FunctionContext::TypeDesc& return_type,
const std::vector<FunctionContext::TypeDesc>& arg_types,
impala::RuntimeState* state = NULL, impala::MemPool* pool = NULL);
/// Use with test contexts to test use of IsArgConstant() and GetConstantArg().
/// constant_args should contain an AnyVal* for each argument of the UDF not including
/// the FunctionContext*; constant_args[i] corresponds to the i-th argument.
/// Non-constant arguments should be set to NULL, and constant arguments should be set
/// to the constant value.
//
/// The AnyVal* values are owned by the caller.
//
/// Can only be called on contexts created by CreateTestContext().
static void SetConstantArgs(
FunctionContext* context, const std::vector<AnyVal*>& constant_args);
/// Test contexts should be closed in order to check for UDF memory leaks. Leaks cause
/// the error to be set on context.
static void CloseContext(FunctionContext* context);
/// Template function to execute a UDF and validate the result. They should be
/// used like:
/// ValidateUdf(udf_fn, arg1, arg2, ..., expected_result);
/// Only functions with up to 8 arguments are supported
//
/// For variable argument udfs, the variable arguments should be passed as
/// a std::vector:
/// ValidateUdf(udf_fn, arg1, arg2, const vector<arg3>& args, expected_result);
template<typename RET>
static bool ValidateUdf(boost::function<RET(FunctionContext*)> fn,
const RET& expected, UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types;
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get());
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1>
static bool ValidateUdf(boost::function<RET(FunctionContext*, const A1&)> fn,
const A1& a1, const RET& expected, UdfPrepare init_fn = NULL,
UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1>
static bool ValidateUdf(boost::function<RET(FunctionContext*, int, const A1*)> fn,
const std::vector<A1>& a1, const RET& expected, UdfPrepare init_fn = NULL,
UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1.size(), a1.data());
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&)> fn,
const A1& a1, const A2& a2, const RET& expected, UdfPrepare init_fn = NULL,
UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, int, const A2*)> fn,
const A1& a1, const std::vector<A2>& a2, const RET& expected,
UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2.size(), a2.data());
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&)> fn,
const A1& a1, const A2& a2, const A3& a3, const RET& expected,
UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, int, const A3*)> fn,
const A1& a1, const A2& a2, const std::vector<A3>& a3, const RET& expected,
UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3.size(), a3.data());
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3, typename A4>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&,
const A4&)> fn,
const A1& a1, const A2& a2, const A3& a3, const A4& a4, const RET& expected,
UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3, a4);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3, typename A4>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&,
int, const A4*)> fn,
const A1& a1, const A2& a2, const A3& a3, const std::vector<A4>& a4,
const RET& expected, UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3, a4.size(), a4.data());
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3, typename A4,
typename A5>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&,
const A4&, const A5&)> fn,
const A1& a1, const A2& a2, const A3& a3, const A4& a4,const A5& a5,
const RET& expected, UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3, a4, a5);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3, typename A4,
typename A5, typename A6>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&,
const A4&, const A5&, const A6&)> fn,
const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5,
const A6& a6, const RET& expected, UdfPrepare init_fn = NULL,
UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3, a4, a5, a6);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3, typename A4,
typename A5, typename A6, typename A7>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&,
const A4&, const A5&, const A6&, const A7&)> fn,
const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5,
const A6& a6, const A7& a7, const RET& expected, UdfPrepare init_fn = NULL,
UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3, a4, a5, a6, a7);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
template<typename RET, typename A1, typename A2, typename A3, typename A4,
typename A5, typename A6, typename A7, typename A8>
static bool ValidateUdf(
boost::function<RET(FunctionContext*, const A1&, const A2&, const A3&,
const A4&, const A5&, const A6&, const A7&)> fn,
const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5,
const A6& a6, const A7& a7, const A8& a8, const RET& expected,
UdfPrepare init_fn = NULL, UdfClose close_fn = NULL,
const std::vector<AnyVal*>& constant_args = std::vector<AnyVal*>()) {
FunctionContext::TypeDesc return_type; // TODO
std::vector<FunctionContext::TypeDesc> arg_types; // TODO
boost::scoped_ptr<FunctionContext> context(CreateTestContext(return_type, arg_types));
SetConstantArgs(context.get(), constant_args);
if (!RunPrepareFn(init_fn, context.get())) return false;
RET ret = fn(context.get(), a1, a2, a3, a4, a5, a6, a7, a8);
RunCloseFn(close_fn, context.get());
return Validate(context.get(), expected, ret);
}
private:
static bool ValidateError(FunctionContext* context) {
if (context->has_error()) {
std::cerr << "Udf Failed: " << context->error_msg() << std::endl;
return false;
}
return true;
}
template<typename RET>
static bool Validate(FunctionContext* context, const RET& expected, const RET& actual) {
bool valid = true;
if (!context->has_error() && actual != expected) {
std::cerr << "UDF did not return the correct result:" << std::endl
<< " Expected: " << DebugString(expected) << std::endl
<< " Actual: " << DebugString(actual) << std::endl;
valid = false;
}
CloseContext(context);
if (!ValidateError(context)) valid = false;
return valid;
}
static bool RunPrepareFn(UdfPrepare prepare_fn, FunctionContext* context) {
if (prepare_fn != NULL) {
// TODO: FRAGMENT_LOCAL
prepare_fn(context, FunctionContext::THREAD_LOCAL);
if (!ValidateError(context)) return false;
}
return true;
}
static void RunCloseFn(UdfClose close_fn, FunctionContext* context) {
if (close_fn != NULL) {
// TODO: FRAGMENT_LOCAL
close_fn(context, FunctionContext::THREAD_LOCAL);
}
}
};
}
#endif