diff --git a/Makefile.am b/Makefile.am
index 99ddfaff8ed9..d3b0c5274d94 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -63,7 +63,10 @@ ndctl_SOURCES = ndctl.c \
 		util/wrapper.c
 
 if ENABLE_TEST
-ndctl_SOURCES += lib/test-libndctl.c lib/test-dpa-alloc.c lib/test-parent-uuid.c
+ndctl_SOURCES += lib/test-libndctl.c \
+		 lib/test-dpa-alloc.c \
+		 lib/test-parent-uuid.c \
+		 lib/test-core.c
 endif
 
 if ENABLE_DESTRUCTIVE
@@ -110,7 +113,7 @@ TESTS += lib/test-blk-ns lib/test-pmem-ns
 check_PROGRAMS += lib/test-blk-ns lib/test-pmem-ns
 endif
 
-lib_test_libndctl_SOURCES = lib/test-libndctl.c
+lib_test_libndctl_SOURCES = lib/test-libndctl.c lib/test-core.c
 lib_test_libndctl_LDADD = lib/libndctl.la $(UUID_LIBS) $(KMOD_LIBS)
 
 lib_test_blk_ns_SOURCES = lib/blk_namespaces.c
@@ -119,8 +122,8 @@ lib_test_blk_ns_LDADD = lib/libndctl.la $(KMOD_LIBS)
 lib_test_pmem_ns_SOURCES = lib/pmem_namespaces.c
 lib_test_pmem_ns_LDADD = lib/libndctl.la $(KMOD_LIBS)
 
-lib_test_dpa_alloc_SOURCES = lib/test-dpa-alloc.c
+lib_test_dpa_alloc_SOURCES = lib/test-dpa-alloc.c lib/test-core.c
 lib_test_dpa_alloc_LDADD = lib/libndctl.la $(UUID_LIBS) $(KMOD_LIBS)
 
-lib_test_parent_uuid_SOURCES = lib/test-parent-uuid.c
+lib_test_parent_uuid_SOURCES = lib/test-parent-uuid.c lib/test-core.c
 lib_test_parent_uuid_LDADD = lib/libndctl.la $(UUID_LIBS) $(KMOD_LIBS)
diff --git a/builtin-test.c b/builtin-test.c
index 73a24e0b3c75..edb6e072174e 100644
--- a/builtin-test.c
+++ b/builtin-test.c
@@ -1,20 +1,36 @@
 #include <stdio.h>
+#include <limits.h>
 #include <syslog.h>
+#include <test-core.h>
 #include <test-libndctl.h>
 #include <test-dpa-alloc.h>
 #include <test-parent-uuid.h>
 #include <util/parse-options.h>
 
+static char *result(int rc)
+{
+	if (rc == 77)
+		return "SKIP";
+	else if (rc)
+		return "FAIL";
+	else
+		return "PASS";
+}
+
 int cmd_test(int argc, const char **argv)
 {
+	struct ndctl_test *test;
 	int loglevel = LOG_DEBUG, i, rc;
 	const char * const u[] = {
 		"ndctl test [<options>]",
 		NULL
 	};
+	bool force = false;
 	const struct option options[] = {
 	OPT_INTEGER('l', "loglevel", &loglevel,
 		"set the log level (default LOG_DEBUG)"),
+	OPT_BOOLEAN('f', "force", &force,
+		"force run all tests regardless of required kernel"),
 	OPT_END(),
 	};
 
@@ -26,17 +42,23 @@ int cmd_test(int argc, const char **argv)
 	if (argc)
 		usage_with_options(u, options);
 
-	rc = test_libndctl(loglevel);
-	fprintf(stderr, "test-libndctl: %s\n", rc ? "FAIL" : "PASS");
-	if (rc)
+	if (force)
+		test = ndctl_test_new(UINT_MAX);
+	else
+		test = ndctl_test_new(0);
+
+	rc = test_libndctl(loglevel, test);
+	fprintf(stderr, "test-libndctl: %s\n", result(rc));
+	if (rc && rc != 77)
 		return rc;
 
-	rc = test_dpa_alloc(loglevel);
-	fprintf(stderr, "test-dpa-alloc: %s\n", rc ? "FAIL" : "PASS");
-	if (rc)
+	rc = test_dpa_alloc(loglevel, test);
+	fprintf(stderr, "test-dpa-alloc: %s\n", result(rc));
+	if (rc && rc != 77)
 		return rc;
 
-	rc = test_parent_uuid(loglevel);
-	fprintf(stderr, "test-parent-uuid: %s\n", rc ? "FAIL" : "PASS");
-	return rc;
+	rc = test_parent_uuid(loglevel, test);
+	fprintf(stderr, "test-parent-uuid: %s\n", result(rc));
+
+	return ndctl_test_result(test, rc);
 }
diff --git a/configure.ac b/configure.ac
index 8162b723ecab..4b83e7444770 100644
--- a/configure.ac
+++ b/configure.ac
@@ -76,7 +76,7 @@ AC_CHECK_HEADER([linux/ndctl.h],
 		[AC_DEFINE([HAVE_NDCTL_H], [1],
 			[Define to 1 if you have <linux/ndctl.h>.])],
 		[])
-
+AC_CHECK_HEADERS_ONCE([linux/version.h])
 
 AC_CHECK_FUNCS([ \
 	__secure_getenv \
diff --git a/lib/blk_namespaces.c b/lib/blk_namespaces.c
index 40928579573f..040da41a64e3 100644
--- a/lib/blk_namespaces.c
+++ b/lib/blk_namespaces.c
@@ -228,7 +228,7 @@ int test_blk_namespaces(int log_level)
 	bus = ndctl_bus_get_by_provider(ctx, provider);
 	if (!bus) {
 		fprintf(stderr, "%s: failed to find NFIT-provider\n", comm);
-		rc = ENODEV;
+		rc = 77;
 		goto err_nobus;
 	} else
 		fprintf(stderr, "%s: found provider: %s\n", comm,
diff --git a/lib/pmem_namespaces.c b/lib/pmem_namespaces.c
index 38d0a00ca537..124900269a8b 100644
--- a/lib/pmem_namespaces.c
+++ b/lib/pmem_namespaces.c
@@ -188,7 +188,7 @@ int test_pmem_namespaces(int log_level)
 	bus = ndctl_bus_get_by_provider(ctx, provider);
 	if (!bus) {
 		fprintf(stderr, "%s: failed to find NFIT-provider\n", comm);
-		rc = ENODEV;
+		rc = 77;
 		goto err;
 	} else
 		fprintf(stderr, "%s: found provider: %s\n", comm,
diff --git a/lib/test-core.c b/lib/test-core.c
new file mode 100644
index 000000000000..932c2dd246f5
--- /dev/null
+++ b/lib/test-core.c
@@ -0,0 +1,73 @@
+#include <linux/version.h>
+#include <test-core.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define KVER_STRLEN 20
+
+struct ndctl_test {
+	unsigned int kver;
+	int attempt;
+	int skip;
+};
+
+struct ndctl_test *ndctl_test_new(unsigned int kver)
+{
+	struct ndctl_test *test = calloc(1, sizeof(*test));
+
+	if (!test)
+		return NULL;
+
+	if (!kver)
+		test->kver = LINUX_VERSION_CODE;
+	else
+		test->kver = kver;
+
+	return test;
+}
+
+int ndctl_test_result(struct ndctl_test *test, int rc)
+{
+	if (ndctl_test_get_skipped(test))
+		fprintf(stderr, "attempted: %d skipped: %d\n",
+				ndctl_test_get_attempted(test),
+				ndctl_test_get_skipped(test));
+	if (rc && rc != 77)
+		return rc;
+	if (ndctl_test_get_skipped(test) >= ndctl_test_get_attempted(test))
+		return 77;
+	/* return success if no failures and at least one test not skipped */
+	return 0;
+}
+
+static char *kver_str(char *buf, unsigned int kver)
+{
+	snprintf(buf, KVER_STRLEN, "%d.%d.%d",  (kver >> 16) & 0xffff,
+			(kver >> 8) & 0xff, kver & 0xff);
+	return buf;
+}
+
+int __ndctl_test_attempt(struct ndctl_test *test, unsigned int kver,
+		const char *caller, int line)
+{
+	char requires[KVER_STRLEN], current[KVER_STRLEN];
+
+	test->attempt++;
+	if (kver <= test->kver)
+		return 1;
+	fprintf(stderr, "%s: skip %s:%d requires: %s current: %s\n",
+			__func__, caller, line, kver_str(requires, kver),
+			kver_str(current, test->kver));
+	test->skip++;
+	return 0;
+}
+
+int ndctl_test_get_attempted(struct ndctl_test *test)
+{
+	return test->attempt;
+}
+
+int ndctl_test_get_skipped(struct ndctl_test *test)
+{
+	return test->skip;
+}
diff --git a/lib/test-core.h b/lib/test-core.h
new file mode 100644
index 000000000000..7f13bc28441b
--- /dev/null
+++ b/lib/test-core.h
@@ -0,0 +1,8 @@
+struct ndctl_test;
+struct ndctl_test *ndctl_test_new(unsigned int kver);
+int ndctl_test_result(struct ndctl_test *test, int rc);
+int ndctl_test_get_skipped(struct ndctl_test *test);
+int ndctl_test_get_attempted(struct ndctl_test *test);
+int __ndctl_test_attempt(struct ndctl_test *test, unsigned int kver,
+		const char *caller, int line);
+#define ndctl_test_attempt(t, v) __ndctl_test_attempt(t, v, __func__, __LINE__)
diff --git a/lib/test-dpa-alloc.c b/lib/test-dpa-alloc.c
index 9a881f301c7f..c5d536df3881 100644
--- a/lib/test-dpa-alloc.c
+++ b/lib/test-dpa-alloc.c
@@ -26,6 +26,8 @@
 #include <libkmod.h>
 #include <uuid/uuid.h>
 
+#include <test-core.h>
+#include <linux/version.h>
 #include <ndctl/libndctl.h>
 #include <test-dpa-alloc.h>
 #include <ccan/array_size/array_size.h>
@@ -49,7 +51,7 @@ struct test_dpa_namespace {
 	uuid_t uuid;
 } namespaces[NUM_NAMESPACES];
 
-static int do_test(struct ndctl_ctx *ctx)
+static int do_test(struct ndctl_ctx *ctx, struct ndctl_test *test)
 {
 	struct ndctl_region *region, *blk_region = NULL;
 	unsigned int available_slots, i;
@@ -292,13 +294,16 @@ static int do_test(struct ndctl_ctx *ctx)
 	return 0;
 }
 
-int test_dpa_alloc(int loglevel)
+int test_dpa_alloc(int loglevel, struct ndctl_test *test)
 {
 	struct ndctl_ctx *ctx;
 	struct kmod_module *mod;
 	struct kmod_ctx *kmod_ctx;
 	int err, result = EXIT_FAILURE;
 
+	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0)))
+		return 77;
+
 	err = ndctl_new(&ctx);
 	if (err < 0)
 		exit(EXIT_FAILURE);
@@ -315,10 +320,14 @@ int test_dpa_alloc(int loglevel)
 
 	err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST,
 			NULL, NULL, NULL, NULL);
-	if (err < 0)
+	if (err < 0) {
+		result = 77;
+		fprintf(stderr, "%s unavailable skipping tests\n",
+				NFIT_TEST_MODULE);
 		goto err_module;
+	}
 
-	err = do_test(ctx);
+	err = do_test(ctx, test);
 	if (err == 0)
 		result = EXIT_SUCCESS;
 	kmod_module_remove_module(mod, 0);
@@ -332,5 +341,14 @@ int test_dpa_alloc(int loglevel)
 
 int __attribute__((weak)) main(int argc, char *argv[])
 {
-	return test_dpa_alloc(LOG_DEBUG);
+	struct ndctl_test *test = ndctl_test_new(0);
+	int rc;
+
+	if (!test) {
+		fprintf(stderr, "failed to initialize test\n");
+		return EXIT_FAILURE;
+	}
+
+	rc = test_dpa_alloc(LOG_DEBUG, test);
+	return ndctl_test_result(test, rc);
 }
diff --git a/lib/test-libndctl.c b/lib/test-libndctl.c
index ec03ad439d55..274c0096ec40 100644
--- a/lib/test-libndctl.c
+++ b/lib/test-libndctl.c
@@ -26,6 +26,7 @@
 #include <libkmod.h>
 #include <uuid/uuid.h>
 #include <sys/ioctl.h>
+#include <linux/version.h>
 
 #include <ccan/array_size/array_size.h>
 #include <ndctl/libndctl.h>
@@ -35,6 +36,7 @@
 #include <ndctl.h>
 #endif
 #include <test-libndctl.h>
+#include <test-core.h>
 
 #define BLKROGET _IO(0x12,94) /* get read-only status (0 = read_write) */
 #define BLKROSET _IO(0x12,93) /* set device read-only (0 = read-write) */
@@ -1362,7 +1364,8 @@ static int check_ars_status(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
 
 #define BITS_PER_LONG 32
 static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
-		unsigned long bus_commands, unsigned long dimm_commands)
+		unsigned long bus_commands, unsigned long dimm_commands,
+		struct ndctl_test *test)
 {
 	/*
 	 * For now, by coincidence, these are indexed in test execution
@@ -1416,6 +1419,9 @@ static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
 	if (rc)
 		goto out;
 
+	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 3, 0)))
+		goto out;
+
 	/* Check Bus commands */
 	check_cmds = __check_bus_cmds;
 	for (i = 1; i < BITS_PER_LONG; i++) {
@@ -1449,7 +1455,8 @@ static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
 }
 
 static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n,
-		unsigned long bus_commands, unsigned long dimm_commands)
+		unsigned long bus_commands, unsigned long dimm_commands,
+		struct ndctl_test *test)
 {
 	int i, rc;
 
@@ -1495,7 +1502,7 @@ static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n,
 			return -ENXIO;
 		}
 
-		rc = check_commands(bus, dimm, bus_commands, dimm_commands);
+		rc = check_commands(bus, dimm, bus_commands, dimm_commands, test);
 		if (rc)
 			return rc;
 	}
@@ -1503,7 +1510,7 @@ static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n,
 	return 0;
 }
 
-static int do_test0(struct ndctl_ctx *ctx)
+static int do_test0(struct ndctl_ctx *ctx, struct ndctl_test *test)
 {
 	struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0);
 	struct ndctl_region *region;
@@ -1517,7 +1524,7 @@ static int do_test0(struct ndctl_ctx *ctx)
 		ndctl_region_disable_invalidate(region);
 
 	rc = check_dimms(bus, dimms0, ARRAY_SIZE(dimms0), bus_commands0,
-			dimm_commands0);
+			dimm_commands0, test);
 	if (rc)
 		return rc;
 
@@ -1528,7 +1535,7 @@ static int do_test0(struct ndctl_ctx *ctx)
 	return check_regions(bus, regions0, ARRAY_SIZE(regions0));
 }
 
-static int do_test1(struct ndctl_ctx *ctx)
+static int do_test1(struct ndctl_ctx *ctx, struct ndctl_test *test)
 {
 	struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER1);
 	int rc;
@@ -1536,20 +1543,20 @@ static int do_test1(struct ndctl_ctx *ctx)
 	if (!bus)
 		return -ENXIO;
 
-	rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0, 0);
+	rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0, 0, test);
 	if (rc)
 		return rc;
 
 	return check_regions(bus, regions1, ARRAY_SIZE(regions1));
 }
 
-typedef int (*do_test_fn)(struct ndctl_ctx *ctx);
+typedef int (*do_test_fn)(struct ndctl_ctx *ctx, struct ndctl_test *test);
 static do_test_fn do_test[] = {
 	do_test0,
 	do_test1,
 };
 
-int test_libndctl(int loglevel)
+int test_libndctl(int loglevel, struct ndctl_test *test)
 {
 	unsigned int i;
 	struct ndctl_ctx *ctx;
@@ -1557,6 +1564,9 @@ int test_libndctl(int loglevel)
 	struct kmod_ctx *kmod_ctx;
 	int err, result = EXIT_FAILURE;
 
+	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0)))
+		return 77;
+
 	err = ndctl_new(&ctx);
 	if (err < 0)
 		exit(EXIT_FAILURE);
@@ -1574,11 +1584,15 @@ int test_libndctl(int loglevel)
 
 	err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST,
 			NULL, NULL, NULL, NULL);
-	if (err < 0)
+	if (err < 0) {
+		result = 77;
+		fprintf(stderr, "%s unavailable skipping tests\n",
+				NFIT_TEST_MODULE);
 		goto err_module;
+	}
 
 	for (i = 0; i < ARRAY_SIZE(do_test); i++) {
-		err = do_test[i](ctx);
+		err = do_test[i](ctx, test);
 		if (err < 0) {
 			fprintf(stderr, "ndctl-test%d failed: %d\n", i, err);
 			break;
@@ -1598,5 +1612,14 @@ int test_libndctl(int loglevel)
 
 int __attribute__((weak)) main(int argc, char *argv[])
 {
-	return test_libndctl(LOG_DEBUG);
+	struct ndctl_test *test = ndctl_test_new(0);
+	int rc;
+
+	if (!test) {
+		fprintf(stderr, "failed to initialize test\n");
+		return EXIT_FAILURE;
+	}
+
+	rc = test_libndctl(LOG_DEBUG, test);
+	return ndctl_test_result(test, rc);
 }
diff --git a/lib/test-parent-uuid.c b/lib/test-parent-uuid.c
index 9681a99364b8..cf48a8c4a58b 100644
--- a/lib/test-parent-uuid.c
+++ b/lib/test-parent-uuid.c
@@ -24,6 +24,8 @@
 #include <syslog.h>
 #include <libkmod.h>
 #include <uuid/uuid.h>
+#include <linux/version.h>
+#include <test-core.h>
 #include <test-parent-uuid.h>
 
 #include <ndctl/libndctl.h>
@@ -129,7 +131,6 @@ static struct ndctl_btt *check_valid_btt(struct ndctl_region *region,
 	return NULL;
 }
 
-
 static int do_test(struct ndctl_ctx *ctx)
 {
 	int rc;
@@ -230,13 +231,16 @@ static int do_test(struct ndctl_ctx *ctx)
 	return 0;
 }
 
-int test_parent_uuid(int loglevel)
+int test_parent_uuid(int loglevel, struct ndctl_test *test)
 {
 	struct ndctl_ctx *ctx;
 	struct kmod_module *mod;
 	struct kmod_ctx *kmod_ctx;
 	int err, result = EXIT_FAILURE;
 
+	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 3, 0)))
+		return 77;
+
 	err = ndctl_new(&ctx);
 	if (err < 0)
 		exit(EXIT_FAILURE);
@@ -253,8 +257,12 @@ int test_parent_uuid(int loglevel)
 
 	err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST,
 			NULL, NULL, NULL, NULL);
-	if (err < 0)
+	if (err < 0) {
+		result = 77;
+		fprintf(stderr, "%s unavailable skipping tests\n",
+				NFIT_TEST_MODULE);
 		goto err_module;
+	}
 
 	err = do_test(ctx);
 	if (err == 0)
@@ -270,5 +278,14 @@ int test_parent_uuid(int loglevel)
 
 int __attribute__((weak)) main(int argc, char *argv[])
 {
-	return test_parent_uuid(LOG_DEBUG);
+	struct ndctl_test *test = ndctl_test_new(0);
+	int rc;
+
+	if (!test) {
+		fprintf(stderr, "failed to initialize test\n");
+		return EXIT_FAILURE;
+	}
+
+	rc = test_parent_uuid(LOG_DEBUG, test);
+	return ndctl_test_result(test, rc);
 }
diff --git a/test-dpa-alloc.h b/test-dpa-alloc.h
index 966db9108e95..f1145f4d278d 100644
--- a/test-dpa-alloc.h
+++ b/test-dpa-alloc.h
@@ -1,4 +1,5 @@
 #ifndef __TEST_DPA_ALLOC__
 #define __TEST_DPA_ALLOC__
-int test_dpa_alloc(int loglevel);
+struct ndctl_test;
+int test_dpa_alloc(int loglevel, struct ndctl_test *test);
 #endif
diff --git a/test-libndctl.h b/test-libndctl.h
index 53c3272a4068..20c5db816a29 100644
--- a/test-libndctl.h
+++ b/test-libndctl.h
@@ -1,4 +1,5 @@
 #ifndef __TEST_LIBNDCTL__
 #define __TEST_LIBNDCTL__
-int test_libndctl(int loglevel);
+struct ndctl_test;
+int test_libndctl(int loglevel, struct ndctl_test *test);
 #endif
diff --git a/test-parent-uuid.h b/test-parent-uuid.h
index 57a5ff75071b..585f1825ee27 100644
--- a/test-parent-uuid.h
+++ b/test-parent-uuid.h
@@ -1,4 +1,5 @@
 #ifndef __TEST_PARENT_UUID__
 #define __TEST_PARENT_UUID__
-int test_parent_uuid(int loglevel);
+struct ndctl_test;
+int test_parent_uuid(int loglevel, struct ndctl_test *test);
 #endif
