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

Message ID 45a49a1c8c826254db169e104f1d50b389e02a03.1565676440.git-series.knut.omang@oracle.com
State New
Headers show
Series
  • Integration of Kernel Test Framework (KTF) into the kernel tree
Related show

Commit Message

Knut Omang Aug. 13, 2019, 6:09 a.m. UTC
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@oracle.com>
---
 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

Patch
diff mbox series

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@oracle.com>
+ *
+ * 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@oracle.com>
+ *
+ * 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@oracle.com>
+ *
+ * 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