diff mbox series

[RFCv1,6/6] selftests/page_detective: Introduce self tests for Page Detective

Message ID 20241116175922.3265872-7-pasha.tatashin@soleen.com (mailing list archive)
State New
Headers show
Series Page Detective | expand

Commit Message

Pasha Tatashin Nov. 16, 2024, 5:59 p.m. UTC
Add self tests for Page Detective, it contains testing of several memory
types, and also some negative/bad input tests.

Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 MAINTAINERS                                   |   1 +
 tools/testing/selftests/Makefile              |   1 +
 .../selftests/page_detective/.gitignore       |   1 +
 .../testing/selftests/page_detective/Makefile |   7 +
 tools/testing/selftests/page_detective/config |   4 +
 .../page_detective/page_detective_test.c      | 727 ++++++++++++++++++
 6 files changed, 741 insertions(+)
 create mode 100644 tools/testing/selftests/page_detective/.gitignore
 create mode 100644 tools/testing/selftests/page_detective/Makefile
 create mode 100644 tools/testing/selftests/page_detective/config
 create mode 100644 tools/testing/selftests/page_detective/page_detective_test.c
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 654d4650670d..ec09b28776b0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17456,6 +17456,7 @@  L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/misc-devices/page_detective.rst
 F:	drivers/misc/page_detective.c
+F:	tools/testing/selftests/page_detective/
 
 PAGE POOL
 M:	Jesper Dangaard Brouer <hawk@kernel.org>
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 363d031a16f7..9c828025fdfa 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -72,6 +72,7 @@  TARGETS += net/packetdrill
 TARGETS += net/rds
 TARGETS += net/tcp_ao
 TARGETS += nsfs
+TARGETS += page_detective
 TARGETS += perf_events
 TARGETS += pidfd
 TARGETS += pid_namespace
diff --git a/tools/testing/selftests/page_detective/.gitignore b/tools/testing/selftests/page_detective/.gitignore
new file mode 100644
index 000000000000..21a78bee7b4a
--- /dev/null
+++ b/tools/testing/selftests/page_detective/.gitignore
@@ -0,0 +1 @@ 
+page_detective_test
diff --git a/tools/testing/selftests/page_detective/Makefile b/tools/testing/selftests/page_detective/Makefile
new file mode 100644
index 000000000000..43c4dccb6a13
--- /dev/null
+++ b/tools/testing/selftests/page_detective/Makefile
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+CFLAGS += -g -Wall
+
+TEST_GEN_PROGS := page_detective_test
+
+include ../lib.mk
+
diff --git a/tools/testing/selftests/page_detective/config b/tools/testing/selftests/page_detective/config
new file mode 100644
index 000000000000..ddfeed4ddf13
--- /dev/null
+++ b/tools/testing/selftests/page_detective/config
@@ -0,0 +1,4 @@ 
+CONFIG_PAGE_TABLE_CHECK=y
+CONFIG_MEMCG=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_PAGE_DETECTIVE=y
diff --git a/tools/testing/selftests/page_detective/page_detective_test.c b/tools/testing/selftests/page_detective/page_detective_test.c
new file mode 100644
index 000000000000..f86cf0fdd8fc
--- /dev/null
+++ b/tools/testing/selftests/page_detective/page_detective_test.c
@@ -0,0 +1,727 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#define _GNU_SOURCE
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <alloca.h>
+#include "../kselftest.h"
+
+#define OPT_STR "hpvaAsnHtbBS"
+#define HELP_STR							\
+"Usage: %s [-h] [-p] [-v] [-a] [-A] [-s] [-n] [-H] [-t] [-b] [-S]\n"	\
+"-h\tshow this help\n"							\
+"Interfaces:\n"								\
+"-p\tphysical address page detective interface\n"			\
+"-v\tvirtual address page detective  interface\n"			\
+"Tests:\n"								\
+"-a\ttest anonymous page\n"						\
+"-A\ttest anonymous huge page\n"					\
+"-s\ttest anonymous shared page\n"					\
+"-n\ttest named shared page\n"						\
+"-H\ttest HugeTLB shared page\n"					\
+"-t\ttest tmpfs page\n"							\
+"-b\ttest bad/fail input cases\n"					\
+"-S\ttest stack page\n"							\
+"If no arguments specified all tests are performed\n"			\
+
+#define FIRST_LINE_PREFIX	"Page Detective: Investigating "
+#define LAST_LINE_PREFIX	"Page Detective: Finished investigation of "
+
+#define TMP_FILE		"/tmp/page_detective_test.out"
+
+#define NR_HUGEPAGES		"/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages"
+
+#define NANO			1000000000ul
+#define MICRO			1000000ul
+#define MILLI			1000ul
+#define BIT(nr)			(UL(1) << (nr))
+
+#define ARG_INTERFACE_PHYS	BIT(0)
+#define ARG_INTERFACE_VIRT	BIT(1)
+
+#define ARG_TEST_ANON		BIT(2)
+#define ARG_TEST_ANON_HUGE	BIT(3)
+#define ARG_TEST_ANON_SHARED	BIT(4)
+#define ARG_TEST_NAMED_SHARED	BIT(5)
+#define ARG_TEST_HUGETLB_SHARED	BIT(6)
+#define ARG_TEST_TMPFS		BIT(7)
+#define ARG_TEST_FAIL_CASES	BIT(8)
+#define ARG_TEST_STACK		BIT(9)
+
+#define ARG_DEFAULT		(~0)		/* Run verything by default */
+
+#define ARG_INTERFACE_MASK	(ARG_INTERFACE_PHYS | ARG_INTERFACE_VIRT)
+#define ARG_TEST_MASK		(~ARG_INTERFACE_MASK)
+
+#define INTERFACE_NAME(in) (((in) == ARG_INTERFACE_PHYS) ?		\
+	"/sys/kernel/debug/page_detective/phys" :			\
+	"/sys/kernel/debug/page_detective/virt")
+
+#define PAGE_SIZE		((unsigned long)sysconf(_SC_PAGESIZE))
+#define HUGE_PAGE_SIZE		(PAGE_SIZE * PAGE_SIZE / sizeof(uint64_t))
+
+#ifndef MAP_HUGE_2MB
+#define MAP_HUGE_2MB		(21 << MAP_HUGE_SHIFT)
+#endif
+
+#ifndef MFD_HUGEPAGE
+#define MFD_HUGEPAGE (MFD_GOOGLE_SPECIFIC_BASE << 0)
+#endif
+
+#ifndef MFD_GOOGLE_SPECIFIC_BASE
+#define MFD_GOOGLE_SPECIFIC_BASE 0x0200U
+#endif
+
+static int old_dmesg;
+
+static uint64_t virt_to_phys(uint64_t virt, uint64_t *physp)
+{
+	uint64_t tbloff, offset, tblen, pfn;
+	int fd, nr;
+
+	fd = open("/proc/self/pagemap", O_RDONLY);
+	if (fd < 0) {
+		ksft_print_msg("%s open(/proc/self/pagemap): %s\n", __func__,
+			       strerror(errno));
+		return 1;
+	}
+
+	/* see: Documentation/admin-guide/mm/pagemap.rst */
+	tbloff = virt / PAGE_SIZE * sizeof(uint64_t);
+	offset = lseek(fd, tbloff, SEEK_SET);
+	if (offset == (off_t)-1) {
+		ksft_print_msg("%s lseek: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	if (offset != tbloff) {
+		ksft_print_msg("%s: cannot find virtual address\n", __func__);
+		return 1;
+	}
+
+	nr = read(fd, &tblen, sizeof(uint64_t));
+	if (nr != sizeof(uint64_t)) {
+		ksft_print_msg("%s: read\n", __func__);
+		return 1;
+	}
+	close(fd);
+
+	if (!(tblen & (1ul << 63))) {
+		ksft_print_msg("%s: present bit is not set\n", __func__);
+		return 1;
+	}
+
+	/* Bits 0-54  page frame number (PFN) if present */
+	pfn = tblen & 0x7fffffffffffffULL;
+
+	*physp =  PAGE_SIZE * pfn | (virt & (PAGE_SIZE - 1));
+
+	return 0;
+}
+
+static int __phys_test(unsigned long long phys)
+{
+	char phys_str[128];
+	int fd, nr;
+
+	fd = open("/sys/kernel/debug/page_detective/phys", O_WRONLY);
+	if (fd < 0) {
+		ksft_print_msg("%s open: %s\n", __func__, strerror(errno));
+		return 4;
+	}
+
+	sprintf(phys_str, "%llu", phys);
+
+	nr = write(fd, phys_str, strlen(phys_str));
+	if (nr != strlen(phys_str)) {
+		ksft_print_msg("%s write failed\n", __func__);
+		return 1;
+	}
+	close(fd);
+
+	return 0;
+}
+
+static int phys_test(char *mem)
+{
+	uint64_t phys;
+
+	if (virt_to_phys((uint64_t)mem, &phys))
+		return 1;
+
+	return __phys_test(phys);
+}
+
+static int __virt_test(int pid, unsigned long virt)
+{
+	char virt_str[128];
+	int fd, nr;
+
+	fd = open("/sys/kernel/debug/page_detective/virt", O_WRONLY);
+	if (fd < 0) {
+		ksft_print_msg("%s open: %s\n", __func__, strerror(errno));
+		return 4;
+	}
+
+	sprintf(virt_str, "%d %#lx", pid, virt);
+	nr = write(fd, virt_str, strlen(virt_str));
+	if (nr != strlen(virt_str)) {
+		ksft_print_msg("%s: write(%s): %s\n", __func__, virt_str,
+			       strerror(errno));
+		close(fd);
+
+		return 1;
+	}
+	close(fd);
+
+	return 0;
+}
+
+static int virt_test(char *mem)
+{
+	return __virt_test(getpid(), (unsigned long)mem);
+}
+
+static int test_anon(int in)
+{
+	char *mem;
+	int rv;
+
+	mem = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
+		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+	if (mem == MAP_FAILED) {
+		ksft_print_msg("%s mmap: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	if (madvise(mem, PAGE_SIZE, MADV_NOHUGEPAGE)) {
+		ksft_print_msg("%s madvise: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	mem[0] = 0;
+
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem);
+	else
+		rv = virt_test(mem);
+
+	munmap(mem, PAGE_SIZE);
+
+	return rv;
+}
+
+static int test_anon_huge(int in)
+{
+	uint64_t i;
+	char *mem;
+	int rv;
+
+	mem = mmap(NULL, HUGE_PAGE_SIZE * 8, PROT_READ | PROT_WRITE,
+		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+	if (mem == MAP_FAILED) {
+		ksft_print_msg("%s mmap: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	if (madvise(mem, HUGE_PAGE_SIZE * 8, MADV_HUGEPAGE)) {
+		ksft_print_msg("%s madvise: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	/* Fault huge pages */
+	for (i = 0; i < HUGE_PAGE_SIZE * 8; i += HUGE_PAGE_SIZE)
+		mem[i] = 0;
+
+	/* In case huge pages were not used for some reason */
+	mem[HUGE_PAGE_SIZE * 7 + 101 * PAGE_SIZE] = 0;
+
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem + HUGE_PAGE_SIZE * 7 + 101 * PAGE_SIZE);
+	else
+		rv = virt_test(mem + HUGE_PAGE_SIZE * 7 + 101 * PAGE_SIZE);
+
+	munmap(mem, HUGE_PAGE_SIZE * 8);
+
+	return rv;
+}
+
+static int test_anon_shared(int in)
+{
+	char *mem;
+	int rv;
+
+	mem = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
+		   MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+	if (mem == MAP_FAILED) {
+		ksft_print_msg("%s mmap: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	if (madvise(mem, PAGE_SIZE, MADV_NOHUGEPAGE)) {
+		ksft_print_msg("%s madvise: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	mem[0] = 0;
+
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem);
+	else
+		rv = virt_test(mem);
+
+	munmap(mem, PAGE_SIZE);
+
+	return rv;
+}
+
+static int test_named_shared(int in)
+{
+	char *mem;
+	int rv;
+
+	mem = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
+		   MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+	if (mem == MAP_FAILED) {
+		ksft_print_msg("%s mmap: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	if (madvise(mem, PAGE_SIZE, MADV_NOHUGEPAGE)) {
+		ksft_print_msg("%s madvise: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	mem[0] = 0;
+
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem);
+	else
+		rv = virt_test(mem);
+
+	munmap(mem, PAGE_SIZE);
+
+	return rv;
+}
+
+static int test_hugetlb_shared(int in)
+{
+	char hugepg_add_cmd[256], hugepg_rm_cmd[256];
+	unsigned long nr_hugepages;
+	char *mem;
+	FILE *f;
+	int rv;
+
+	f = fopen(NR_HUGEPAGES, "r");
+	if (!f) {
+		ksft_print_msg("%s fopen: %s\n", __func__, strerror(errno));
+		return 4;
+	}
+
+	fscanf(f, "%lu", &nr_hugepages);
+	fclose(f);
+	sprintf(hugepg_add_cmd, "echo %lu > " NR_HUGEPAGES, nr_hugepages + 1);
+	sprintf(hugepg_rm_cmd, "echo %lu > " NR_HUGEPAGES, nr_hugepages);
+
+	if (system(hugepg_add_cmd)) {
+		ksft_print_msg("%s system(hugepg_add_cmd): %s\n", __func__,
+			       strerror(errno));
+		return 4;
+	}
+
+	mem = mmap(NULL, HUGE_PAGE_SIZE, PROT_READ | PROT_WRITE,
+		   MAP_HUGETLB | MAP_HUGE_2MB | MAP_ANONYMOUS | MAP_SHARED,
+		   -1, 0);
+	if (mem == MAP_FAILED) {
+		ksft_print_msg("%s mmap: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	mem[0] = 0;
+
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem);
+	else
+		rv = virt_test(mem);
+
+	munmap(mem, HUGE_PAGE_SIZE);
+
+	if (system(hugepg_rm_cmd)) {
+		ksft_print_msg("%s system(hugepg_rm_cmd): %s\n", __func__,
+			       strerror(errno));
+		return 1;
+	}
+
+	return rv;
+}
+
+static int test_tmpfs(int in)
+{
+	char *mem;
+	int fd;
+	int rv;
+
+	fd = memfd_create("tmpfs_page",
+			  MFD_CLOEXEC | MFD_ALLOW_SEALING);
+	if (fd < 0) {
+		ksft_print_msg("%s memfd_create: %s\n", __func__,
+			       strerror(errno));
+		return 1;
+	}
+
+	if (ftruncate(fd, PAGE_SIZE) == -1) {
+		ksft_print_msg("%s ftruncate: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	mem = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
+		   MAP_SHARED, fd, 0);
+	if (mem == MAP_FAILED) {
+		ksft_print_msg("%s mmap: %s\n", __func__, strerror(errno));
+		return 1;
+	}
+
+	mem[0] = 0;
+
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem);
+	else
+		rv = virt_test(mem);
+
+	munmap(mem, PAGE_SIZE);
+	close(fd);
+
+	return rv;
+}
+
+static int test_stack(int in)
+{
+	char *mem = alloca(PAGE_SIZE);
+	int rv;
+
+	mem[0] = 0;
+	if (in == ARG_INTERFACE_PHYS)
+		rv = phys_test(mem);
+	else
+		rv = virt_test(mem);
+
+	return rv;
+}
+
+static int test_bad_phys(void)
+{
+	int rv;
+
+	rv = __phys_test(0);
+	if (rv)
+		return rv;
+
+	rv = __phys_test(1);
+	if (rv)
+		return rv;
+
+	rv = __phys_test(~0);
+
+	return rv;
+}
+
+static int test_bad_virt(void)
+{
+	int rv;
+
+	rv = __virt_test(0, 0);
+	if (rv == 4)
+		return rv;
+
+	if (!rv) {
+		ksft_print_msg("%s: write(0, 0) did not fail\n", __func__);
+		return 1;
+	}
+
+	if (!__virt_test(0, -1)) {
+		ksft_print_msg("%s: write(0, -1) did not fail\n", __func__);
+		return 1;
+	}
+
+	if (!__virt_test(0, -1)) {
+		ksft_print_msg("%s: write(0, -1) did not fail\n", __func__);
+		return 1;
+	}
+
+	if (!__virt_test(-1, 0)) {
+		ksft_print_msg("%s: write(-1, 0) did not fail\n", __func__);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int test_fail_cases(int in)
+{
+	if (in == ARG_INTERFACE_VIRT)
+		return test_bad_virt();
+	else
+		return test_bad_phys();
+}
+
+/* Return time in nanosecond since epoch */
+static uint64_t gethrtime(void)
+{
+	struct timespec ts;
+
+	clock_gettime(CLOCK_REALTIME, &ts);
+
+	return (ts.tv_sec * NANO) + ts.tv_nsec;
+}
+
+static int sanity_check_fail_cases(int in, int test_ttpe, FILE *f)
+{
+	char *line;
+	size_t len;
+	ssize_t nr;
+
+	line = NULL;
+	len = 0;
+	while ((nr = getline(&line, &len, f)) != -1) {
+		char *l = strchr(line, ']') + 2;	/* skip time stamp */
+
+		if (l == (char *)2)
+			continue;
+
+		ksft_print_msg("%s", l);
+	}
+
+	free(line);
+
+	return 0;
+}
+
+static int sanity_check_test_result(int in, int test_type, uint64_t start)
+{
+	uint64_t end = gethrtime() + NANO;
+	char dmesg[256], *line;
+	int first_line, last_line;
+	size_t len;
+	ssize_t nr;
+	FILE *f;
+
+	sprintf(dmesg, "dmesg --since=@%ld.%06ld --until=@%ld.%06ld > "
+		TMP_FILE, start / NANO, (start / MILLI) % MICRO, end / NANO,
+		(end / MILLI) % MICRO);
+
+	if (old_dmesg)
+		system("dmesg > " TMP_FILE);
+	else
+		system(dmesg);
+	f = fopen(TMP_FILE, "r");
+	if (!f) {
+		ksft_print_msg("%s: %s", __func__, strerror(errno));
+		return 1;
+	}
+
+	if (test_type == ARG_TEST_FAIL_CASES) {
+		sanity_check_fail_cases(in, test_type, f);
+		fclose(f);
+		unlink(TMP_FILE);
+		return 0;
+	}
+
+	line = NULL;
+	len = 0;
+	first_line = 0;
+	last_line = 0;
+	while ((nr = getline(&line, &len, f)) != -1) {
+		char *l = strchr(line, ']') + 2;	/* skip time stamp */
+
+		if (l == (char *)2)
+			continue;
+
+		if (!first_line) {
+			first_line = !strncmp(l, FIRST_LINE_PREFIX,
+					     strlen(FIRST_LINE_PREFIX));
+		} else if (!last_line) {
+			last_line = !strncmp(l, LAST_LINE_PREFIX,
+					     strlen(LAST_LINE_PREFIX));
+			if (last_line)
+				ksft_print_msg("%s", l);
+		}
+
+		/*
+		 * Output everything between the first and last line of page
+		 * detective output
+		 */
+		if (first_line && !last_line)
+			ksft_print_msg("%s", l);
+	}
+	ksft_print_msg("\n");
+
+	free(line);
+	fclose(f);
+	unlink(TMP_FILE);
+
+	if (!first_line) {
+		ksft_print_msg("Test failed, bad first line\n");
+		return 1;
+	}
+	if (!last_line) {
+		ksft_print_msg("Test failed, bad last line\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * interface	ARG_INTERFACE_VIRT or ARG_INTERFACE_PHYS
+ * test_arg	one of the ARG_TEST_*
+ * test_f	function for this test
+ * arg		the provided user arguments.
+ *
+ * Run the test using the provided interface if it is requested interface arg.
+ */
+#define TEST(interface, test_type, test_f, arg)					\
+	do {									\
+		int __in = (interface);						\
+		int __tt = (test_type);						\
+		int __rv;							\
+										\
+		if ((arg) & __tt) {						\
+			uint64_t start = gethrtime();				\
+										\
+			if (old_dmesg)						\
+				system("dmesg -C");				\
+			else							\
+				usleep(100000);					\
+										\
+			__rv = test_f(__in);					\
+			if (__rv == 4) {					\
+				ksft_test_result_skip(#test_f " via [%s]\n",	\
+						      INTERFACE_NAME(__in));	\
+				break;						\
+			}							\
+										\
+			if (__rv) {						\
+				ksft_test_result_fail(#test_f " via [%s]\n",	\
+						      INTERFACE_NAME(__in));	\
+				break;						\
+			}							\
+										\
+			if (sanity_check_test_result(__in, __tt,		\
+						     start)) {			\
+				ksft_test_result_fail(#test_f " via [%s]\n",	\
+						      INTERFACE_NAME(__in));	\
+				break;						\
+			}							\
+			ksft_test_result_pass(#test_f " via [%s]\n",		\
+					      INTERFACE_NAME(__in));		\
+		}								\
+	} while (0)
+
+static void run_tests(int in, int arg)
+{
+	if (in & arg) {
+		TEST(in, ARG_TEST_ANON, test_anon, arg);
+		TEST(in, ARG_TEST_ANON_HUGE, test_anon_huge, arg);
+		TEST(in, ARG_TEST_ANON_SHARED, test_anon_shared, arg);
+		TEST(in, ARG_TEST_NAMED_SHARED, test_named_shared, arg);
+		TEST(in, ARG_TEST_HUGETLB_SHARED, test_hugetlb_shared, arg);
+		TEST(in, ARG_TEST_TMPFS, test_tmpfs, arg);
+		TEST(in, ARG_TEST_STACK, test_stack, arg);
+		TEST(in, ARG_TEST_FAIL_CASES, test_fail_cases, arg);
+	}
+}
+
+static int count_tests(int in, int arg)
+{
+	int tests = 0;
+
+	if (in & arg) {
+		tests += (arg & ARG_TEST_ANON) ? 1 : 0;
+		tests += (arg & ARG_TEST_ANON_HUGE) ? 1 : 0;
+		tests += (arg & ARG_TEST_ANON_SHARED) ? 1 : 0;
+		tests += (arg & ARG_TEST_NAMED_SHARED) ? 1 : 0;
+		tests += (arg & ARG_TEST_HUGETLB_SHARED) ? 1 : 0;
+		tests += (arg & ARG_TEST_TMPFS) ? 1 : 0;
+		tests += (arg & ARG_TEST_STACK) ? 1 : 0;
+		tests += (arg & ARG_TEST_FAIL_CASES) ? 1 : 0;
+	}
+
+	return tests;
+}
+
+int main(int argc, char **argv)
+{
+	int arg = 0;
+	int opt;
+
+	while ((opt = getopt(argc, argv, OPT_STR)) != -1) {
+		switch (opt) {
+		case 'h':
+			printf(HELP_STR, argv[0]);
+			exit(EXIT_SUCCESS);
+		case 'v':
+			arg |= ARG_INTERFACE_VIRT;
+			break;
+		case 'p':
+			arg |= ARG_INTERFACE_PHYS;
+			break;
+		case 'a':
+			arg |= ARG_TEST_ANON;
+			break;
+		case 'A':
+			arg |= ARG_TEST_ANON_HUGE;
+			break;
+		case 's':
+			arg |= ARG_TEST_ANON_SHARED;
+			break;
+		case 'n':
+			arg |= ARG_TEST_NAMED_SHARED;
+			break;
+		case 'H':
+			arg |= ARG_TEST_HUGETLB_SHARED;
+			break;
+		case 't':
+			arg |= ARG_TEST_TMPFS;
+			break;
+		case 'S':
+			arg |= ARG_TEST_STACK;
+			break;
+		case 'b':
+			arg |= ARG_TEST_FAIL_CASES;
+			break;
+		default:
+			errx(EXIT_FAILURE, HELP_STR, argv[0]);
+		}
+	}
+
+	if (arg == 0)
+		arg = ARG_DEFAULT;
+	if (!(arg & ARG_INTERFACE_MASK))
+		errx(EXIT_FAILURE, "No page detective interface specified");
+
+	if (!(arg & ARG_TEST_MASK))
+		errx(EXIT_FAILURE, "No tests specified");
+
+	/* Return 1 when dmesg does not have --since and --until arguments */
+	old_dmesg = system("dmesg --since @0 > /dev/null 2>&1");
+
+	ksft_print_header();
+	ksft_set_plan(count_tests(ARG_INTERFACE_VIRT, arg)
+		      + count_tests(ARG_INTERFACE_PHYS, arg));
+
+	run_tests(ARG_INTERFACE_VIRT, arg);
+	run_tests(ARG_INTERFACE_PHYS, arg);
+	ksft_finished();
+}