[RFC 12/19] ktf: Main part of user land library for executing tests

From: Knut Omang
Date: Tue Aug 13 2019 - 02:12:47 EST


Implementation of the main part of the user library to communicate
with the kernel side of ktf about tests, configuration and test
results.

ktf.h: User mode side of KTF extensions to the gtest unit test framework.
ktf_int.cc: Implementation of Gtest user land test management
ktf_int.h: User mode side of extension to the gtest unit test framework:

Signed-off-by: Knut Omang <knut.omang@xxxxxxxxxx>
---
tools/testing/selftests/ktf/lib/Makefile | 21 +-
tools/testing/selftests/ktf/lib/ktf.h | 114 ++-
tools/testing/selftests/ktf/lib/ktf_int.cc | 1031 +++++++++++++++++++++-
tools/testing/selftests/ktf/lib/ktf_int.h | 84 ++-
4 files changed, 1250 insertions(+)
create mode 100644 tools/testing/selftests/ktf/lib/Makefile
create mode 100644 tools/testing/selftests/ktf/lib/ktf.h
create mode 100644 tools/testing/selftests/ktf/lib/ktf_int.cc
create mode 100644 tools/testing/selftests/ktf/lib/ktf_int.h

diff --git a/tools/testing/selftests/ktf/lib/Makefile b/tools/testing/selftests/ktf/lib/Makefile
new file mode 100644
index 0000000..c2be04b
--- /dev/null
+++ b/tools/testing/selftests/ktf/lib/Makefile
@@ -0,0 +1,21 @@
+
+GTEST_CFLAGS ?= -I$(GTEST_PATH)/include -DGTEST_HAS_PTHREAD=1 -lpthread
+GTEST_LIBS ?= -L$(GTEST_PATH)/lib64 -lgtest -lpthread
+NETLINK_CFLAGS ?= $(shell pkgconf --cflags libnl-genl-3.0)
+HOST_EXTRACFLAGS = -I$(srctree)/$(src)/.. $(NETLINK_CFLAGS) $(GTEST_CFLAGS) \
+ -Wall -Werror -Wno-packed-bitfield-compat -D_GNU_SOURCE
+HOST_EXTRACXXFLAGS = -I$(srctree)/$(src)/.. $(NETLINK_CFLAGS) $(GTEST_CFLAGS) \
+ -Wall \
+ -Wno-packed-bitfield-compat \
+ -Wno-pointer-arith -Werror \
+ -D__FILENAME__=\"`basename $<`\"
+
+hostcxxlibs-y := libktf.so
+libktf-cshobjs = ktf_unlproto.o
+libktf-cxxshobjs = ktf_int.o ktf_run.o ktf_debug.o
+
+targets := $(addprefix $(obj)/,$(libktf-cshobjs)) \
+ $(addprefix $(obj)/,$(libktf-cxxshobjs)) \
+ $(addprefix $(obj)/,$(hostcxxlibs-y))
+
+__build: $(targets)
diff --git a/tools/testing/selftests/ktf/lib/ktf.h b/tools/testing/selftests/ktf/lib/ktf.h
new file mode 100644
index 0000000..942eb28
--- /dev/null
+++ b/tools/testing/selftests/ktf/lib/ktf.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Author: Knut Omang <knut.omang@xxxxxxxxxx>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf.h: User mode side of KTF extensions to the gtest unit test framework.
+ * Include this to write hybrid tests
+ *
+ */
+#ifndef _KTF_H
+#define _KTF_H
+#include <gtest/gtest.h>
+
+namespace ktf
+{
+
+ /* Interfaces intended to be used directly by programs:
+ * ----------------------------------------------------
+ */
+ class KernelTest;
+
+ /* Invoke the kernel test - to be called directly from user mode
+ * hybrid tests:
+ */
+ void run(KernelTest* kt, std::string ctx = "");
+
+ /* Function for enabling/disabling coverage for module */
+ int set_coverage(std::string module, unsigned int opts, bool enabled);
+
+ typedef void (*configurator)(void);
+
+ // Initialize KTF:
+ // If necessary, supply a callback that uses the KTF_CONTEXT_CFG* macros below
+ // to configure any necessary contexts:
+ void setup(configurator c = NULL);
+
+} // end namespace ktf
+
+/* HTEST: Define user part of a hybrid test.
+ * Hybrid tests are tests that have a user and a kernel counterpart,
+ * to allow testing of interaction between user mode and the kernel:
+ */
+#define HTEST(__setname,__testname) \
+ class __setname ## _ ## __testname : public ktf::test_cb \
+ {\
+ public:\
+ __setname ## _ ## __testname() {\
+ ktf::add_wrapper(#__setname,#__testname,as_test_cb()); \
+ }\
+ virtual void fun(ktf::KernelTest* kt); \
+ }; \
+ __setname ## _ ## __testname \
+ __setname ## _ ## __testname ## _value;\
+ void __setname ## _ ## __testname::fun(ktf::KernelTest* self)
+
+
+/* Part of KTF support for hybrid tests: allocate/get a reference to
+ * an out-of-band user data pointer:
+ */
+#define KTF_USERDATA(__kt_ptr, __priv_datatype, __priv_data) \
+ struct __priv_datatype *__priv_data = \
+ (struct __priv_datatype *)get_priv(__kt_ptr, sizeof(struct __priv_datatype)); \
+ ASSERT_TRUE(__priv_data); \
+ ASSERT_EQ(get_priv_sz(__kt_ptr), sizeof(struct __priv_datatype))
+
+/* KTF support for configurable contexts:
+ * Send a configuation data structure to the given context name.
+ */
+#define KTF_CONTEXT_CFG(__context_name, __context_type_name, __priv_datatype, __priv_data) \
+ ktf::configure_context(__context_name, __context_type_name, \
+ (struct __priv_datatype *)__priv_data, \
+ sizeof(__priv_datatype))
+/* Alternative to KTF_CONTEXT_CFG: If there are multiple contexts with the same name
+ * (but with different handles) use a test name to identify the context to be configured
+ */
+#define KTF_CONTEXT_CFG_FOR_TEST(__test_name, __context_type_name, __priv_datatype, __priv_data) \
+ ktf::configure_context_for_test(__test_name, __context_type_name, \
+ (struct __priv_datatype *)__priv_data, \
+ sizeof(__priv_datatype))
+
+
+
+/* Private interfaces (needed by macro definitions above)
+ * ------------------------------------------------------
+ */
+
+namespace ktf {
+ class test_cb
+ {
+ public:
+ virtual ~test_cb() {}
+ virtual test_cb* as_test_cb() { return this; }
+ virtual void fun(KernelTest* kt) {}
+ };
+
+ /* Function for adding a user level test wrapper */
+ void add_wrapper(const std::string setname, const std::string testname,
+ test_cb* tcb);
+
+ /* get a priv pointer of the given size, allocate if necessary */
+ void* get_priv(KernelTest* kt, size_t priv_sz);
+
+ /* Get the size of the existing priv data */
+ size_t get_priv_sz(KernelTest *kt);
+
+ // Configure ktf context - to be used via KTF_CONTEXT_CFG*():
+ void configure_context(const std::string context, const std::string type_name,
+ void *data, size_t data_sz);
+ void configure_context_for_test(const std::string testname, const std::string type_name,
+ void *data, size_t data_sz);
+} // end namespace ktf
+
+#endif
diff --git a/tools/testing/selftests/ktf/lib/ktf_int.cc b/tools/testing/selftests/ktf/lib/ktf_int.cc
new file mode 100644
index 0000000..6ac1f54
--- /dev/null
+++ b/tools/testing/selftests/ktf/lib/ktf_int.cc
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Author: Knut Omang <knut.omang@xxxxxxxxxx>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf_int.cpp: Implementation of Gtest user land test management
+ * for kernel and hybrid test functionality provided by KTF.
+ */
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <unistd.h>
+#include "kernel/ktf_unlproto.h"
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <map>
+#include <set>
+#include <string>
+#include "ktf_int.h"
+#include "ktf_debug.h"
+
+#include <netlink/version.h>
+
+int devcnt = 0;
+
+namespace ktf
+{
+
+struct nl_sock* sock = NULL;
+int family = -1;
+
+int printed_header = 0;
+
+typedef std::map<std::string, KernelTest*> testmap;
+typedef std::map<std::string, test_cb*> wrappermap;
+
+class testset
+{
+public:
+ testset() : setnum(0)
+ { }
+
+ ~testset()
+ {
+ for (testmap::iterator it = tests.begin(); it != tests.end(); ++it)
+ delete it->second;
+ }
+
+ testmap tests;
+ stringvec test_names;
+ wrappermap wrapper;
+ int setnum;
+};
+
+/* ConfigurableContext keeps track of a ktf_context that requires configuration.
+ * Context names are unique within a handle, so a handle ID is necessary to
+ * identify the context. The actual configuration data must be agreed upon between
+ * user mode and kernel mode on a per context basis. They can use type_id
+ * to identify which type of parameter a context needs.
+ * The type_id is also used to create new contexts in the kernel.
+ * The kernel implementation must enable such dynamically extensible context sets
+ * on a per type_id basis.
+ */
+class ConfigurableContext
+{
+public:
+ ConfigurableContext(const std::string& name, const std::string& type_name,
+ unsigned int hid, int cfg_stat);
+
+ std::string str_state();
+ int Configure(void *data, size_t data_sz);
+
+ const std::string& Type()
+ {
+ return type_name;
+ }
+
+ std::string name;
+ int handle_id;
+ std::string type_name;
+ int cfg_stat;
+};
+
+typedef std::map<std::string, testset> setmap;
+typedef std::set<std::string> stringset;
+typedef std::vector<ConfigurableContext*> context_vector;
+
+struct name_iter
+{
+ setmap::iterator it;
+ std::string setname;
+};
+
+class ContextType
+{
+public:
+ ContextType(int handle_id, const std::string& type_name);
+ int handle_id;
+ std::string type_name;
+};
+
+ ContextType::ContextType(int hid, const std::string& tn)
+ : handle_id(hid),
+ type_name(tn)
+{}
+
+/* We trick the gtest template framework
+ * to get a new set of test names as a side effect of
+ * invocation of get_test_names()
+ */
+
+/* Wrap globals in an object to control init order and
+ * memory cleanup:
+ */
+class KernelTestMgr
+{
+public:
+ KernelTestMgr() : next_set(0), cur(NULL)
+ { }
+
+ ~KernelTestMgr();
+
+ testset& find_add_set(std::string& setname);
+ testset& find_add_test(std::string& setname, std::string& testname);
+ void add_test(const std::string& setname, const char* tname, unsigned int handle_id);
+ KernelTest* find_test(const std::string&setname, const std::string& testname, std::string* ctx);
+ void add_wrapper(const std::string setname, const std::string testname, test_cb* tcb);
+
+ stringvec& get_set_names() { return set_names; }
+ stringvec get_test_names();
+
+ stringvec get_testsets()
+ {
+ return set_names;
+ }
+
+ std::string get_current_setname()
+ {
+ return cur->setname;
+ }
+
+ stringvec& get_contexts(unsigned int id)
+ {
+ return handle_to_ctxvec[id];
+ }
+
+ void add_cset(unsigned int hid, stringvec& ctxs);
+ void add_ctype(unsigned int hid, const std::string& type_name);
+ std::vector<ConfigurableContext*> add_configurable_context(const std::string& ctx,
+ const std::string& type_name,
+ unsigned int hid, int cfg_stat);
+ std::vector<ConfigurableContext*> add_configurable_contexts(const std::string& ctx,
+ std::vector<ContextType*> type_vec);
+ std::vector<ConfigurableContext*> find_contexts(const std::string& ctx, const std::string& type_name);
+
+ /* Contexts may be created on the fly if the kernel supports it for this type_name: */
+ std::vector<ConfigurableContext*> maybe_create_context(const std::string& ctx,
+ const std::string& type_name);
+
+ /* Update the list of contexts returned from the kernel with a newly created one */
+ void add_context(unsigned int hid, const std::string& ctx);
+private:
+ setmap sets;
+ stringvec test_names;
+ stringvec set_names;
+ stringset kernelsets;
+ std::map<unsigned int, stringvec> handle_to_ctxvec;
+ std::map<std::string, context_vector> cfg_contexts;
+
+ // Context types that allows dynamically created contexts:
+ std::map<std::string, std::vector<ContextType*> > ctx_types;
+ int next_set;
+ name_iter* cur;
+};
+
+KernelTestMgr::~KernelTestMgr()
+{
+ std::map<std::string, context_vector>::iterator it;
+ for (it = cfg_contexts.begin(); it != cfg_contexts.end(); ++it)
+ {
+ context_vector::iterator vit;
+ for (vit = it->second.begin(); vit != it->second.end(); ++vit)
+ delete *vit;
+ }
+
+ std::map<std::string, std::vector<ContextType*> >::iterator tit;
+ for (tit = ctx_types.begin(); tit != ctx_types.end(); ++tit)
+ {
+ std::vector<ContextType*>::iterator ttit;
+ for (ttit = tit->second.begin(); ttit != tit->second.end(); ++ttit)
+ delete *ttit;
+ }
+}
+
+context_vector KernelTestMgr::find_contexts(const std::string& ctx, const std::string& type_name)
+{
+ std::map<std::string,context_vector>::iterator it;
+ it = cfg_contexts.find(ctx);
+ if (it == cfg_contexts.end())
+ return maybe_create_context(ctx, type_name);
+ else
+ return it->second;
+}
+
+context_vector KernelTestMgr::maybe_create_context(const std::string& ctx, const std::string& type_name)
+{
+ std::map<std::string, std::vector<ContextType*> >::iterator it;
+ it = ctx_types.find(type_name);
+ if (it == ctx_types.end())
+ return context_vector();
+ else
+ return add_configurable_contexts(ctx, it->second);
+}
+
+void KernelTestMgr::add_context(unsigned int hid, const std::string& ctx)
+{
+ handle_to_ctxvec[hid].push_back(ctx);
+}
+
+
+KernelTestMgr& kmgr()
+{
+ static KernelTestMgr kmgr_;
+ return kmgr_;
+}
+
+testset& KernelTestMgr::find_add_test(std::string& setname, std::string& testname)
+{
+ testset& ts(find_add_set(setname));
+ test_names.push_back(testname);
+ return ts;
+}
+
+testset& KernelTestMgr::find_add_set(std::string& setname)
+{
+ bool new_set = false;
+
+ log(KTF_DEBUG, "find_add_set(%s)\n", setname.c_str());
+
+ stringset::iterator it = kernelsets.find(setname);
+ if (it == kernelsets.end()) {
+ kernelsets.insert(setname);
+ set_names.push_back(setname);
+ new_set = true;
+ }
+
+ /* This implicitly adds a new testset to sets, if it's not there: */
+ testset& ts = sets[setname];
+ if (new_set)
+ {
+ ts.setnum = next_set++;
+ log(KTF_INFO, "added %s (set %d) total %lu sets\n", setname.c_str(), ts.setnum, sets.size());
+ }
+ return ts;
+}
+
+
+void KernelTestMgr::add_test(const std::string& setname, const char* tname,
+ unsigned int handle_id)
+{
+ log(KTF_INFO_V, "add_test: %s.%s", setname.c_str(),tname);
+ logs(KTF_INFO_V,
+ if (handle_id)
+ fprintf(stderr, " [id %d]\n", handle_id);
+ else
+ fprintf(stderr, "\n"));
+ std::string name(tname);
+ new KernelTest(setname, tname, handle_id);
+}
+
+
+/* Here we might get called with test names expanded with context names */
+KernelTest* KernelTestMgr::find_test(const std::string&setname,
+ const std::string& testname,
+ std::string* pctx)
+{
+ size_t pos;
+ log(KTF_DEBUG, "find test %s.%s\n", setname.c_str(), testname.c_str());
+
+ /* Try direct lookup first: */
+ KernelTest* kt = sets[setname].tests[testname];
+ if (kt) {
+ *pctx = std::string();
+ return kt;
+ }
+
+ /* If we don't have any contexts set, no need to parse name: */
+ if (handle_to_ctxvec.empty())
+ return NULL;
+
+ pos = testname.find_last_of('_');
+ while (pos >= 0) {
+ std::string tname = testname.substr(0,pos);
+ std::string ctx = testname.substr(pos + 1, testname.npos);
+ *pctx = ctx;
+ kt = sets[setname].tests[tname];
+ if (kt)
+ return kt;
+ /* context name might contain an '_' , iterate on: */
+ pos = tname.find_last_of('_');
+ }
+ return NULL;
+}
+
+
+void KernelTestMgr::add_cset(unsigned int hid, stringvec& ctxs)
+{
+ log(KTF_INFO, "hid %d: ", hid);
+ logs(KTF_INFO, for (stringvec::iterator it = ctxs.begin(); it != ctxs.end(); ++it)
+ fprintf(stderr, "%s ", it->c_str());
+ fprintf(stderr, "\n"));
+ handle_to_ctxvec[hid] = ctxs;
+}
+
+void KernelTestMgr::add_ctype(unsigned int hid, const std::string& type_name)
+{
+ log(KTF_INFO, "hid %d: dynamical type: %s\n", hid, type_name.c_str());
+ ctx_types[type_name].push_back(new ContextType(hid, type_name));
+}
+
+std::vector<ConfigurableContext*> KernelTestMgr::add_configurable_context(const std::string& ctx,
+ const std::string& type_name,
+ unsigned int hid, int cfg_stat)
+{
+ cfg_contexts[ctx].push_back(new ConfigurableContext(ctx, type_name, hid, cfg_stat));
+ return cfg_contexts[ctx];
+}
+
+/* Function for adding a wrapper user level test */
+void KernelTestMgr::add_wrapper(const std::string setname, const std::string testname,
+ test_cb* tcb)
+{
+ log(KTF_DEBUG, "add_wrapper: %s.%s\n", setname.c_str(),testname.c_str());
+ testset& ts = sets[setname];
+
+ /* Depending on C++ initialization order which vary between compiler version
+ * (sigh!) either the kernel tests have already been processed or we have to store
+ * this object in wrapper for later insertion:
+ */
+ KernelTest *kt = ts.tests[testname];
+ if (kt) {
+ log(KTF_DEBUG_V, "Assigning user_test for %s.%s\n",
+ setname.c_str(), testname.c_str());
+ kt->user_test = tcb;
+ } else {
+ log(KTF_DEBUG_V, "Set wrapper for %s.%s\n",
+ setname.c_str(), testname.c_str());
+ ts.wrapper[testname] = tcb;
+ }
+}
+
+std::vector<ConfigurableContext*> KernelTestMgr::add_configurable_contexts(const std::string& ctx,
+ std::vector<ContextType*> type_vec)
+{
+ std::vector<ContextType*>::iterator it = type_vec.begin();
+ for (; it != type_vec.end(); ++it) {
+ /* We use ENODEV (instead of the kernel's ENOENT to indicate to ConfigurableContext that
+ * this context was not reported in the query, and thus need to be added locally upon a
+ * successful configuration:
+ */
+ cfg_contexts[ctx].push_back(new ConfigurableContext(ctx, (*it)->type_name, (*it)->handle_id, ENODEV));
+ }
+ return cfg_contexts[ctx];
+}
+
+
+stringvec KernelTestMgr::get_test_names()
+{
+ if (!cur) {
+ cur = new name_iter();
+ cur->it = sets.begin();
+ }
+
+ /* Filter out any combined tests that do not have a kernel counterpart loaded */
+ while (cur->it->second.wrapper.size() != 0 && cur->it != sets.end()) {
+ if (cur->it->second.test_names.size() == 0)
+ log(KTF_INFO, "Note: Skipping test suite %s which has combined tests with no kernel counterpart\n",
+ cur->it->first.c_str());
+ ++(cur->it);
+ }
+
+ if (cur->it == sets.end()) {
+ delete cur;
+ cur = NULL;
+ return stringvec();
+ }
+
+ stringvec& v = cur->it->second.test_names;
+ cur->setname = cur->it->first;
+
+ ++(cur->it);
+ return v;
+}
+
+ConfigurableContext::ConfigurableContext(const std::string& name_, const std::string& type_name_,
+ unsigned int hid, int cfg_stat_)
+ : name(name_),
+ handle_id(hid),
+ type_name(type_name_),
+ cfg_stat(cfg_stat_)
+{
+ log(KTF_INFO, "%s[%s] (hid %d): state: %s\n",
+ name.c_str(), type_name.c_str(), hid, str_state().c_str());
+}
+
+std::string ConfigurableContext::str_state()
+{
+ switch (cfg_stat) {
+ case 0:
+ return std::string("READY");
+ case ENOENT:
+ return std::string("UNCONFIGURED");
+ case ENODEV:
+ return std::string("UNCREATED");
+ default:
+ char tmp[100];
+ sprintf(tmp, "ERROR(%d)", cfg_stat);
+ return std::string(tmp);
+ }
+}
+
+int ConfigurableContext::Configure(void *data, size_t data_sz)
+{
+ struct nl_msg *msg = nlmsg_alloc();
+ int err;
+
+ log(KTF_INFO, "%s, data_sz %lu\n", name.c_str(), data_sz);
+ genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST,
+ KTF_C_REQ, 1);
+ nla_put_u32(msg, KTF_A_TYPE, KTF_CT_CTX_CFG);
+ nla_put_u64(msg, KTF_A_VERSION, KTF_VERSION_LATEST);
+ nla_put_string(msg, KTF_A_STR, name.c_str());
+ nla_put_u32(msg, KTF_A_HID, handle_id);
+ nla_put_string(msg, KTF_A_FILE, type_name.c_str());
+ nla_put(msg, KTF_A_DATA, data_sz, data);
+
+ // Send message over netlink socket
+ nl_send_auto_complete(sock, msg);
+
+ // Free message
+ nlmsg_free(msg);
+
+ // Wait for acknowledgement:
+ // This function also returns error status if the message
+ // was not deemed ok by the kernel, but the error status
+ // does not resemble what the netlink recipient returned.
+ //
+ // This message receives no response beyond the error code.
+ //
+ err = nl_wait_for_ack(sock);
+
+ if (!err && cfg_stat == ENODEV) {
+ // Successfully added a new context, update it's state and
+ // tell kmgr() about it:
+ kmgr().add_context(handle_id, name);
+ cfg_stat = 0;
+ }
+ return err;
+}
+
+void *get_priv(KernelTest *kt, size_t sz)
+{
+ return kt->get_priv(sz);
+}
+
+size_t get_priv_sz(KernelTest *kt)
+{
+ return kt->user_priv_sz;
+}
+
+int set_coverage(std::string module, unsigned int opts, bool enabled)
+{
+ struct nl_msg *msg;
+ int err;
+
+ msg = nlmsg_alloc();
+ genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST,
+ KTF_C_REQ, 1);
+ nla_put_u32(msg, KTF_A_TYPE,
+ enabled ? KTF_CT_COV_ENABLE : KTF_CT_COV_DISABLE);
+ nla_put_u32(msg, KTF_A_COVOPT, opts);
+ nla_put_u64(msg, KTF_A_VERSION, KTF_VERSION_LATEST);
+ nla_put_string(msg, KTF_A_MOD, module.c_str());
+
+ // Send message over netlink socket
+ nl_send_auto_complete(sock, msg);
+
+ // Free message
+ nlmsg_free(msg);
+
+ //Wait for acknowledgement:
+ // This function also returns error status if the message
+ // was not deemed ok by the kernel.
+ //
+ err = nl_wait_for_ack(sock);
+ if (err == 0) {
+ // Then wait for the answer and receive it
+ nl_recvmsgs_default(sock);
+ }
+ return err;
+}
+
+ KernelTest::KernelTest(const std::string& sn, const char* tn, unsigned int handle_id)
+ : setname(sn),
+ testname(tn),
+ setnum(0),
+ testnum(0),
+ user_priv(NULL),
+ user_priv_sz(0),
+ user_test(NULL),
+ file(NULL),
+ line(-1)
+{
+
+ name = setname;
+ name.append(".");
+ name.append(testname);
+
+ testset& ts(kmgr().find_add_test(setname, testname));
+ setnum = ts.setnum;
+ ts.tests[testname] = this;
+
+ if (!handle_id)
+ ts.test_names.push_back(testname);
+ else {
+ stringvec& ctxv = kmgr().get_contexts(handle_id);
+ for (stringvec::iterator it = ctxv.begin(); it != ctxv.end(); ++it)
+ ts.test_names.push_back(testname + "_" + *it);
+ }
+ testnum = ts.tests.size();
+
+ wrappermap::iterator hit = ts.wrapper.find(testname);
+ if (hit != ts.wrapper.end()) {
+ log(KTF_DEBUG_V, "Assigning user_test from wrapper for %s.%s\n",
+ setname.c_str(), testname.c_str());
+ user_test = hit->second;
+ /* Clear out wrapper entry as we skip any test sets
+ * with nonempty wrapper lists during test execution:
+ */
+ ts.wrapper.erase(hit);
+ }
+}
+
+
+KernelTest::~KernelTest()
+{
+ if (user_priv)
+ free(user_priv);
+}
+
+void* KernelTest::get_priv(size_t p_sz)
+{
+ if (!user_priv) {
+ user_priv = malloc(p_sz);
+ if (user_priv)
+ user_priv_sz = p_sz;
+ }
+ return user_priv;
+}
+
+static int parse_cb(struct nl_msg *msg, void *arg);
+static int debug_cb(struct nl_msg *msg, void *arg);
+static int error_cb(struct nl_msg *msg, void *arg);
+
+int nl_connect(void)
+{
+ /* Allocate a new netlink socket */
+ sock = nl_socket_alloc();
+ if (sock == NULL){
+ fprintf(stderr, "Failed to allocate a nl socket");
+ exit(1);
+ }
+
+ /* Connect to generic netlink socket on kernel side */
+ int stat = genl_connect(sock);
+ if (stat) {
+ fprintf(stderr, "Failed to open generic netlink connection");
+ exit(1);
+ }
+
+ /* Ask kernel to resolve family name to family id */
+ family = genl_ctrl_resolve(sock, "ktf");
+ if (family <= 0) {
+ fprintf(stderr, "Netlink protocol family for ktf not found - is the ktf module loaded?\n");
+ exit(1);
+ }
+
+ /* Specify the generic callback functions for messages */
+ nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, parse_cb, NULL);
+ nl_socket_modify_cb(sock, NL_CB_INVALID, NL_CB_CUSTOM, error_cb, NULL);
+ return 0;
+}
+
+
+void default_test_handler(int result, const char* file, int line, const char* report)
+{
+ if (result >= 0) {
+ fprintf(stderr, "default_test_handler: Result %d: %s,%d\n",result,file,line);
+ } else {
+ fprintf(stderr, "default_test_handler: Result %d\n",result);
+ }
+}
+
+test_handler handle_test = default_test_handler;
+
+bool setup(test_handler ht)
+{
+ ktf_debug_init();
+ handle_test = ht;
+ return nl_connect() == 0;
+}
+
+
+configurator do_context_configure = NULL;
+
+void set_configurator(configurator c)
+{
+ do_context_configure = c;
+}
+
+/* Query kernel for available tests in index order */
+stringvec& query_testsets()
+{
+ struct nl_msg *msg;
+ int err;
+
+ msg = nlmsg_alloc();
+ genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST,
+ KTF_C_REQ, 1);
+ nla_put_u32(msg, KTF_A_TYPE, KTF_CT_QUERY);
+ nla_put_u64(msg, KTF_A_VERSION, KTF_VERSION_LATEST);
+
+ // Send message over netlink socket
+ nl_send_auto_complete(sock, msg);
+
+ // Free message
+ nlmsg_free(msg);
+
+ // Wait for acknowledgement:
+ // This function also returns error status if the message
+ // was not deemed ok by the kernel.
+ //
+ err = nl_wait_for_ack(sock);
+ if (err < 0) {
+ errno = -err;
+ return kmgr().get_set_names();
+ }
+
+ // Then wait for the answer and receive it
+ nl_recvmsgs_default(sock);
+ return kmgr().get_set_names();
+}
+
+stringvec get_test_names()
+{
+ return kmgr().get_test_names();
+}
+
+std::string get_current_setname()
+{
+ return kmgr().get_current_setname();
+}
+
+KernelTest* find_test(const std::string&setname, const std::string& testname, std::string* ctx)
+{
+ return kmgr().find_test(setname, testname, ctx);
+}
+
+void add_wrapper(const std::string setname, const std::string testname, test_cb* tcb)
+{
+ kmgr().add_wrapper(setname, testname, tcb);
+}
+
+void run_test(KernelTest* kt, std::string& ctx)
+{
+ if (kt->user_test)
+ kt->user_test->fun(kt);
+ else
+ run(kt, ctx);
+}
+
+/* Run the kernel test */
+void run(KernelTest* kt, std::string context)
+{
+ struct nl_msg *msg;
+
+ log(KTF_DEBUG_V, "START kernel test (%ld,%ld): %s\n", kt->setnum,
+ kt->testnum, kt->name.c_str());
+
+ msg = nlmsg_alloc();
+ genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST,
+ KTF_C_REQ, 1);
+ nla_put_u32(msg, KTF_A_TYPE, KTF_CT_RUN);
+ nla_put_u64(msg, KTF_A_VERSION, KTF_VERSION_LATEST);
+ nla_put_string(msg, KTF_A_SNAM, kt->setname.c_str());
+ nla_put_string(msg, KTF_A_TNAM, kt->testname.c_str());
+
+ if (!context.empty())
+ nla_put_string(msg, KTF_A_STR, context.c_str());
+
+ /* Send any test specific out-of-band data */
+ if (kt->user_priv)
+ nla_put(msg, KTF_A_DATA, kt->user_priv_sz, kt->user_priv);
+
+ // Send message over netlink socket
+ nl_send_auto_complete(sock, msg);
+
+ // Free message
+ nlmsg_free(msg);
+
+ // Wait for acknowledgement - otherwise
+ // nl_recvmsg_default will sometimes take the ack for the next message..
+ int err = nl_wait_for_ack(sock);
+ if (err < 0) {
+ errno = -err;
+ return;
+ }
+
+ // Wait for the answer and receive it
+ nl_recvmsgs_default(sock);
+
+ log(KTF_DEBUG_V, "END ktf::run_kernel_test %s\n", kt->name.c_str());
+}
+
+
+void configure_context(const std::string context, const std::string type_name, void *data, size_t data_sz)
+{
+ context_vector ct = kmgr().find_contexts(context, type_name);
+ ASSERT_GE(ct.size(), 1UL) << " - no context found named " << context;
+ ASSERT_EQ(ct.size(), 1UL) << " - More than one context named " << context
+ << " - use KTF_CONTEXT_CFG_FOR_TEST to uniquely identify context.";
+ ASSERT_EQ(type_name, ct[0]->Type());
+ ASSERT_EQ(ct[0]->Configure(data, data_sz), 0);
+}
+
+void configure_context_for_test(const std::string& setname, const std::string& testname,
+ const std::string& type_name, void *data, size_t data_sz)
+{
+ std::string context;
+ KernelTest *kt = kmgr().find_test(setname, testname, &context);
+ context_vector ct = kmgr().find_contexts(context, type_name);
+ ASSERT_TRUE(kt) << " Could not find test " << setname << "." << testname;
+ int handle_id = kt->handle_id;
+ ASSERT_NE(handle_id, 0) << " test " << setname << "." << testname << " does not have a context";
+
+ for (context_vector::iterator it = ct.begin(); it != ct.end(); ++it)
+ if ((*it)->handle_id == handle_id)
+ {
+ ASSERT_EQ(type_name, (*it)->Type());
+ ASSERT_EQ((*it)->Configure(data, data_sz), 0);
+ return;
+ }
+ ASSERT_TRUE(false) << " unconfigurable context found for test " << setname << "." << testname << "?";
+}
+
+
+static nl_cb_action parse_one_set(std::string& setname,
+ std::string& testname, struct nlattr* attr)
+{
+ int rem = 0;
+ struct nlattr *nla;
+ const char* msg;
+ unsigned int handle_id = 0;
+
+ nla_for_each_nested(nla, attr, rem) {
+ switch (nla_type(nla)) {
+ case KTF_A_HID:
+ handle_id = nla_get_u32(nla);
+ break;
+ case KTF_A_STR:
+ msg = nla_get_string(nla);
+ kmgr().add_test(setname, msg, handle_id);
+ handle_id = 0;
+ break;
+ default:
+ fprintf(stderr,"parse_result: Unexpected attribute type %d\n", nla_type(nla));
+ return NL_SKIP;
+ }
+ }
+ return NL_OK;
+}
+
+
+
+static int parse_query(struct nl_msg *msg, struct nlattr** attrs)
+{
+ int alloc = 0, rem = 0, rem2 = 0, cfg_stat;
+ nl_cb_action stat;
+ std::string setname,testname,ctx;
+
+ /* Version 0.1.0.0 did not report version back from the kernel */
+ uint64_t kernel_version = (KTF_VERSION_SET(MAJOR, 0ULL) | KTF_VERSION_SET(MINOR, 1ULL));
+
+ if (attrs[KTF_A_VERSION])
+ kernel_version = nla_get_u64(attrs[KTF_A_VERSION]);
+
+ /* We only got here if we were compatible enough, log that we had differences */
+ if (kernel_version != KTF_VERSION_LATEST)
+ {
+ const char* note = "Note";
+ bool is_compatible =
+ KTF_VERSION(MAJOR, KTF_VERSION_LATEST) == KTF_VERSION(MAJOR, kernel_version) &&
+ KTF_VERSION(MINOR, KTF_VERSION_LATEST) == KTF_VERSION(MINOR, kernel_version);
+ if (!is_compatible)
+ note = "Error";
+
+ fprintf(stderr,
+ "%s: KTF version difference - user lib %llu.%llu.%llu.%llu, kernel has %llu.%llu.%llu.%llu\n",
+ note,
+ KTF_VERSION(MAJOR, KTF_VERSION_LATEST),
+ KTF_VERSION(MINOR, KTF_VERSION_LATEST),
+ KTF_VERSION(MICRO, KTF_VERSION_LATEST),
+ KTF_VERSION(BUILD, KTF_VERSION_LATEST),
+ KTF_VERSION(MAJOR, kernel_version),
+ KTF_VERSION(MINOR, kernel_version),
+ KTF_VERSION(MICRO, kernel_version),
+ KTF_VERSION(BUILD, kernel_version));
+ if (!is_compatible)
+ return NL_SKIP;
+ }
+
+ if (attrs[KTF_A_HLIST]) {
+ struct nlattr *nla, *nla2;
+ stringvec contexts;
+ unsigned int handle_id = 0;
+ const char* type_name = NULL;
+
+ /* Parse info on handle IDs and associated contexts and/or
+ * types that allows dynamical creation of new contexts
+ * (defined here via KTF_A_FILE):
+ */
+ nla_for_each_nested(nla, attrs[KTF_A_HLIST], rem) {
+ switch (nla_type(nla)) {
+ case KTF_A_HID:
+ handle_id = nla_get_u32(nla);
+ break;
+ case KTF_A_LIST:
+ nla_for_each_nested(nla2, nla, rem2) {
+ switch (nla_type(nla2)) {
+ case KTF_A_FILE:
+ type_name = nla_get_string(nla2);
+ kmgr().add_ctype(handle_id, type_name);
+ break;
+ case KTF_A_STR:
+ ctx = nla_get_string(nla2);
+ contexts.push_back(ctx);
+ break;
+ case KTF_A_MOD:
+ type_name = nla_get_string(nla2);
+ break;
+ case KTF_A_STAT:
+ cfg_stat = nla_get_u32(nla2);
+ kmgr().add_configurable_context(ctx, type_name, handle_id, cfg_stat);
+ break;
+ }
+ }
+ /* Add this set of contexts for the handle_id */
+ kmgr().add_cset(handle_id, contexts);
+ handle_id = 0;
+ contexts.clear();
+ break;
+ default:
+ fprintf(stderr,"parse_query[HLIST]: Unexpected attribute type %d\n", nla_type(nla));
+ return NL_SKIP;
+ }
+ }
+ }
+
+ // Now we know enough about contexts and type_ids to actually configure
+ // any contexts that needs to be configured, and this must be
+ // done before the list of tests gets spanned out because addition
+ // of new contexts can lead to more tests being "generated":
+ //
+ if (do_context_configure)
+ do_context_configure();
+
+ if (attrs[KTF_A_NUM]) {
+ alloc = nla_get_u32(attrs[KTF_A_NUM]);
+ log(KTF_DEBUG, "Kernel offers %d test sets:\n", alloc);
+ } else {
+ fprintf(stderr,"No test set count in kernel response??\n");
+ return -1;
+ }
+
+ if (attrs[KTF_A_LIST]) {
+ struct nlattr *nla;
+
+ /* Parse info on test sets */
+ nla_for_each_nested(nla, attrs[KTF_A_LIST], rem) {
+ switch (nla_type(nla)) {
+ case KTF_A_STR:
+ setname = nla_get_string(nla);
+ break;
+ case KTF_A_TEST:
+ stat = parse_one_set(setname, testname, nla);
+ if (stat != NL_OK)
+ return stat;
+ break;
+ default:
+ fprintf(stderr,"parse_query[LIST]: Unexpected attribute type %d\n", nla_type(nla));
+ return NL_SKIP;
+ }
+ kmgr().find_add_set(setname); /* Just to make sure empty sets are also added */
+ }
+ }
+
+ return NL_OK;
+}
+
+
+static enum nl_cb_action parse_result(struct nl_msg *msg, struct nlattr** attrs)
+{
+ int assert_cnt = 0, fail_cnt = 0;
+ int rem = 0, stat;
+ const char *file = "no_file",*report = "no_report";
+
+ if (attrs[KTF_A_STAT]) {
+ stat = nla_get_u32(attrs[KTF_A_STAT]);
+ log(KTF_DEBUG, "parsed test status %d\n", stat);
+ if (stat) {
+ fprintf(stderr, "Failed to execute test in kernel - status %d\n", stat);
+ }
+ }
+ if (attrs[KTF_A_LIST]) {
+ /* Parse list of test results */
+ struct nlattr *nla;
+ int result = -1, line = 0;
+ nla_for_each_nested(nla, attrs[KTF_A_LIST], rem) {
+ switch (nla_type(nla)) {
+ case KTF_A_STAT:
+ /* Flush previous test, if any */
+ handle_test(result,file,line,report);
+ result = nla_get_u32(nla);
+ /* Our own count and report since check does such a lousy
+ * job in counting individual checks */
+ if (result)
+ assert_cnt += result;
+ else {
+ fail_cnt++;
+ assert_cnt++;
+ }
+ break;
+ case KTF_A_FILE:
+ file = nla_get_string(nla);
+ if (!file)
+ file = "no_file";
+ break;
+ case KTF_A_NUM:
+ line = nla_get_u32(nla);
+ break;
+ case KTF_A_STR:
+ report = nla_get_string(nla);
+ if (!report)
+ report = "no_report";
+ break;
+ default:
+ fprintf(stderr,"parse_result: Unexpected attribute type %d\n", nla_type(nla));
+ return NL_SKIP;
+ }
+ }
+ /* Handle last test */
+ handle_test(result,file,line,report);
+ }
+
+ return NL_OK;
+}
+
+static enum nl_cb_action parse_cov_endis(struct nl_msg *msg, struct nlattr** attrs)
+{
+ enum ktf_cmd_type type = (ktf_cmd_type)nla_get_u32(attrs[KTF_A_TYPE]);
+ const char *cmd = type == KTF_CT_COV_ENABLE ? "enable" : "disable";
+ int retval = nla_get_u32(attrs[KTF_A_STAT]);
+
+ if (retval)
+ fprintf(stderr, "Coverage %s operation failed with status %d\n", cmd, retval);
+ return NL_OK;
+}
+
+static int parse_cb(struct nl_msg *msg, void *arg)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+ int maxtype = KTF_A_MAX+10;
+ struct nlattr *attrs[maxtype];
+ enum ktf_cmd_type type;
+
+ // memset(attrs, 0, sizeof(attrs));
+
+ /* Validate message and parse attributes */
+ int err = genlmsg_parse(nlh, 0, attrs, KTF_A_MAX, ktf_get_gnl_policy());
+ if (err < 0) return err;
+
+ if (!attrs[KTF_A_TYPE]) {
+ fprintf(stderr, "Received kernel response without a type\n");
+ return NL_SKIP;
+ }
+
+ type = (ktf_cmd_type)nla_get_u32(attrs[KTF_A_TYPE]);
+ switch (type) {
+ case KTF_CT_QUERY:
+ return parse_query(msg, attrs);
+ case KTF_CT_RUN:
+ return parse_result(msg, attrs);
+ case KTF_CT_COV_ENABLE:
+ case KTF_CT_COV_DISABLE:
+ return parse_cov_endis(msg, attrs);
+ default:
+ debug_cb(msg, attrs);
+ }
+ return NL_SKIP;
+}
+
+
+static int error_cb(struct nl_msg *msg, void *arg)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+ fprintf(stderr, "Received invalid netlink message - type %d\n", nlh->nlmsg_type);
+ return NL_OK;
+}
+
+
+static int debug_cb(struct nl_msg *msg, void *arg)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+ fprintf(stderr, "[Received netlink message of type %d]\n", nlh->nlmsg_type);
+ nl_msg_dump(msg, stderr);
+ return NL_OK;
+}
+
+} // end namespace ktf
diff --git a/tools/testing/selftests/ktf/lib/ktf_int.h b/tools/testing/selftests/ktf/lib/ktf_int.h
new file mode 100644
index 0000000..1a06533
--- /dev/null
+++ b/tools/testing/selftests/ktf/lib/ktf_int.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Author: Knut Omang <knut.omang@xxxxxxxxxx>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf_int.h: User mode side of extension to the gtest unit test framework:
+ * 1) Kernel test support via netlink
+ * 2) Standard command line parameters
+ *
+ * This file exposes some internals - for users of hybrid tests including
+ * ktf.h should be sufficient:
+ */
+
+#ifndef KTF_INT_H
+#define KTF_INT_H
+#include <string>
+#include <vector>
+#include "ktf.h"
+
+typedef std::vector<std::string> stringvec;
+
+namespace ktf
+{
+
+ /* A callback handler to be called for each assertion result */
+ typedef void (*test_handler)(int result, const char* file, int line, const char* report);
+
+ class KernelTest
+ {
+ public:
+ KernelTest(const std::string& setname, const char* testname, unsigned int handle_id);
+ ~KernelTest();
+ void* get_priv(size_t priv_sz);
+ size_t get_priv_sz(KernelTest *kt);
+ std::string setname;
+ std::string testname;
+ unsigned int handle_id;
+ std::string name;
+ size_t setnum; /* This test belongs to this set in the kernel */
+ size_t testnum; /* This test's index (test number) in the kernel */
+ void* user_priv; /* Optional private data for the test */
+ size_t user_priv_sz; /* Size of the user_priv data if used */
+ test_cb* user_test; /* Optional user level wrapper function for the kernel test */
+ char* file;
+ int line;
+ };
+
+ void *get_priv(KernelTest *kt, size_t priv_sz);
+
+ // Set up connection to the kernel test driver:
+ // @handle_test contains the test framework's handling code for test assertions */
+ bool setup(test_handler handle_test);
+
+ void set_configurator(configurator c);
+
+ // Parse command line args (call after gtest arg parsing)
+ char** parse_opts(int argc, char** argv);
+
+ /* Query kernel for available tests in index order */
+ stringvec& query_testsets();
+
+ stringvec get_testsets();
+ std::string get_current_setname();
+ stringvec get_test_names();
+
+ KernelTest* find_test(const std::string& setname, const std::string& testname,
+ std::string* ctx);
+
+ /* "private" - only run from gtest framework */
+ void run_test(KernelTest* test, std::string& ctx);
+} // end namespace ktf
+
+
+/* Redefine for C++ until we can get it patched - type mismatch by default */
+#ifdef nla_for_each_nested
+#undef nla_for_each_nested
+#endif
+#define nla_for_each_nested(pos, nla, rem) \
+ for (pos = (struct nlattr*)nla_data(nla), rem = nla_len(nla); \
+ nla_ok(pos, rem); \
+ pos = nla_next(pos, &(rem)))
+
+#endif
--
git-series 0.9.1