diff mbox series

[21/29] selftests/mm: Add framework for uffd-unit-test

Message ID 20230330160818.3107545-1-peterx@redhat.com (mailing list archive)
State New
Headers show
Series selftests/mm: Split / Refactor userfault test | expand

Commit Message

Peter Xu March 30, 2023, 4:08 p.m. UTC
Add a framework to be prepared to move unit tests from uffd-stress.c into
uffd-unit-tests.c.  The goal is to allow detection of uffd features for
each test, and also loop over specified types of memory that a test support.

Signed-off-by: Peter Xu <peterx@redhat.com>
---
 tools/testing/selftests/mm/uffd-unit-tests.c | 125 +++++++++++++++++++
 tools/testing/selftests/mm/vm_util.c         |  27 ++++
 tools/testing/selftests/mm/vm_util.h         |   1 +
 3 files changed, 153 insertions(+)

Comments

Mike Rapoport April 11, 2023, 11:09 a.m. UTC | #1
On Thu, Mar 30, 2023 at 12:08:18PM -0400, Peter Xu wrote:
> Add a framework to be prepared to move unit tests from uffd-stress.c into
> uffd-unit-tests.c.  The goal is to allow detection of uffd features for
> each test, and also loop over specified types of memory that a test support.
> 
> Signed-off-by: Peter Xu <peterx@redhat.com>
> ---
>  tools/testing/selftests/mm/uffd-unit-tests.c | 125 +++++++++++++++++++
>  tools/testing/selftests/mm/vm_util.c         |  27 ++++
>  tools/testing/selftests/mm/vm_util.h         |   1 +
>  3 files changed, 153 insertions(+)
> 
> diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
> index dfb44ffad5f5..007145063363 100644
> --- a/tools/testing/selftests/mm/uffd-unit-tests.c
> +++ b/tools/testing/selftests/mm/uffd-unit-tests.c
> @@ -9,6 +9,66 @@
> 
>  #ifdef __NR_userfaultfd
> 
> +/* The unit test doesn't need a large or random size, make it 32MB for now */
> +#define  UFFD_TEST_MEM_SIZE               (32UL << 20)
> +
> +#define  MEM_ANON                         BIT_ULL(0)
> +#define  MEM_SHMEM                        BIT_ULL(1)
> +#define  MEM_SHMEM_PRIVATE                BIT_ULL(2)
> +#define  MEM_HUGETLB                      BIT_ULL(3)
> +#define  MEM_HUGETLB_PRIVATE              BIT_ULL(4)
> +
> +struct mem_type {
> +	const char *name;
> +	unsigned int mem_flag;
> +	uffd_test_ops_t *mem_ops;
> +	bool shared;
> +};
> +typedef struct mem_type mem_type_t;
> +
> +mem_type_t mem_types[] = {
> +	{
> +		.name = "anon",
> +		.mem_flag = MEM_ANON,
> +		.mem_ops = &anon_uffd_test_ops,
> +		.shared = false,
> +	},
> +	{
> +		.name = "shmem",
> +		.mem_flag = MEM_SHMEM,
> +		.mem_ops = &shmem_uffd_test_ops,
> +		.shared = true,
> +	},
> +	{
> +		.name = "shmem-private",
> +		.mem_flag = MEM_SHMEM_PRIVATE,
> +		.mem_ops = &shmem_uffd_test_ops,
> +		.shared = false,
> +	},
> +	{
> +		.name = "hugetlb",
> +		.mem_flag = MEM_HUGETLB,
> +		.mem_ops = &hugetlb_uffd_test_ops,
> +		.shared = true,
> +	},
> +	{
> +		.name = "hugetlb-private",
> +		.mem_flag = MEM_HUGETLB_PRIVATE,
> +		.mem_ops = &hugetlb_uffd_test_ops,
> +		.shared = false,
> +	},
> +};
> +
> +/* Returns: UFFD_TEST_* */
> +typedef void (*uffd_test_fn)(void);
> +
> +typedef struct {
> +	const char *name;
> +	uffd_test_fn uffd_fn;
> +	unsigned int mem_targets;
> +	uint64_t uffd_feature_required;
> +} uffd_test_case_t;
> +
>  struct {
>  	unsigned int pass, skip, fail, total;
>  } uffd_test_acct;
> @@ -108,9 +168,50 @@ static int test_uffd_api(bool use_dev)
>  	return 1;
>  }
> 
> +/*
> + * This function initializes the global variables.  TODO: remove global
> + * vars and then remove this.
> + */
> +static int uffd_setup_environment(uffd_test_case_t *test, mem_type_t *mem_type)
> +{
> +	map_shared = mem_type->shared;
> +	uffd_test_ops = mem_type->mem_ops;
> +
> +	if (mem_type->mem_flag & (MEM_HUGETLB_PRIVATE | MEM_HUGETLB))
> +		page_size = default_huge_page_size();
> +	else
> +		page_size = psize();
> +
> +	nr_pages = UFFD_TEST_MEM_SIZE / page_size;
> +	/* TODO: remove this global var.. it's so ugly */
> +	nr_cpus = 1;
> +
> +	return uffd_test_ctx_init(test->uffd_feature_required);
> +}
> +
> +static bool uffd_feature_supported(uffd_test_case_t *test)
> +{
> +	uint64_t features;
> +
> +	if (uffd_get_features(&features))
> +		return false;
> +
> +	return (features & test->uffd_feature_required) ==
> +	    test->uffd_feature_required;
> +}
> +
> +uffd_test_case_t uffd_tests[] = {
> +};
> +
>  int main(int argc, char *argv[])
>  {
> +	int n_tests = sizeof(uffd_tests) / sizeof(uffd_test_case_t);
> +	int n_mems = sizeof(mem_types) / sizeof(mem_type_t);
> +	uffd_test_case_t *test;
> +	mem_type_t *mem_type;
> +	char test_name[128];
>  	int has_uffd;
> +	int i, j;
> 
>  	has_uffd = test_uffd_api(false);
>  	has_uffd |= test_uffd_api(true);
> @@ -119,7 +220,31 @@ int main(int argc, char *argv[])
>  		printf("Userfaultfd not supported or unprivileged, skip all tests\n");
>  		exit(KSFT_SKIP);
>  	}
> +
> +	for (i = 0; i < n_tests; i++) {
> +		test = &uffd_tests[i];
> +		for (j = 0; j < n_mems; j++) {
> +			mem_type = &mem_types[j];
> +			if (!(test->mem_targets & mem_type->mem_flag))
> +				continue;
> +			snprintf(test_name, sizeof(test_name),
> +				 "%s on %s", test->name, mem_type->name);
> +
> +			uffd_test_start(test_name);
> +			if (!uffd_feature_supported(test)) {
> +				uffd_test_skip("feature missing");
> +				continue;
> +			}
> +			if (uffd_setup_environment(test, mem_type)) {
> +				uffd_test_skip("memory allocation failed");

Maybe uffd_test_skip("environment setup failed")?

> +				continue;
> +			}
> +			test->uffd_fn();
> +		}
> +	}
> +
>  	uffd_test_report();
> +
>  	return uffd_test_acct.fail ? KSFT_FAIL : KSFT_PASS;
>  }
> 
> diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
> index 62fcf039d6b7..dad1f62a7ecd 100644
> --- a/tools/testing/selftests/mm/vm_util.c
> +++ b/tools/testing/selftests/mm/vm_util.c
> @@ -264,3 +264,30 @@ int uffd_open(unsigned int flags)
> 
>  	return uffd;
>  }
> +
> +int uffd_get_features(uint64_t *features)
> +{
> +	struct uffdio_api uffdio_api = { .api = UFFD_API, .features = 0 };
> +	/*
> +	 * This should by default work in most kernels; the feature list
> +	 * will be the same no matter what we pass in here.
> +	 */
> +	int fd = uffd_open(UFFD_USER_MODE_ONLY);
> +
> +	if (fd < 0)
> +		/* Maybe the kernel is older than user-only mode? */
> +		fd = uffd_open(0);
> +
> +	if (fd < 0)
> +		return fd;
> +
> +	if (ioctl(fd, UFFDIO_API, &uffdio_api)) {
> +		close(fd);
> +		return -errno;
> +	}
> +
> +	*features = uffdio_api.features;
> +	close(fd);
> +
> +	return 0;
> +}
> diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
> index a67db8432855..2edad3256271 100644
> --- a/tools/testing/selftests/mm/vm_util.h
> +++ b/tools/testing/selftests/mm/vm_util.h
> @@ -51,6 +51,7 @@ int uffd_open(unsigned int flags);
>  int uffd_register(int uffd, void *addr, uint64_t len,
>  		  bool miss, bool wp, bool minor);
>  int uffd_unregister(int uffd, void *addr, uint64_t len);
> +int uffd_get_features(uint64_t *features);
> 
>  /*
>   * On ppc64 this will only work with radix 2M hugepage size
> -- 
> 2.39.1
>
Peter Xu April 11, 2023, 8:09 p.m. UTC | #2
On Tue, Apr 11, 2023 at 02:09:51PM +0300, Mike Rapoport wrote:
> > @@ -119,7 +220,31 @@ int main(int argc, char *argv[])
> >  		printf("Userfaultfd not supported or unprivileged, skip all tests\n");
> >  		exit(KSFT_SKIP);
> >  	}
> > +
> > +	for (i = 0; i < n_tests; i++) {
> > +		test = &uffd_tests[i];
> > +		for (j = 0; j < n_mems; j++) {
> > +			mem_type = &mem_types[j];
> > +			if (!(test->mem_targets & mem_type->mem_flag))
> > +				continue;
> > +			snprintf(test_name, sizeof(test_name),
> > +				 "%s on %s", test->name, mem_type->name);
> > +
> > +			uffd_test_start(test_name);
> > +			if (!uffd_feature_supported(test)) {
> > +				uffd_test_skip("feature missing");
> > +				continue;
> > +			}
> > +			if (uffd_setup_environment(test, mem_type)) {
> > +				uffd_test_skip("memory allocation failed");
> 
> Maybe uffd_test_skip("environment setup failed")?

Sure.

Side note: the line will be changed after "selftests/mm: Allow uffd test to
skip properly with no privilege" to contain an errmsg pointer instead, so
it won't affect the last result after series applied.

Thanks,
diff mbox series

Patch

diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
index dfb44ffad5f5..007145063363 100644
--- a/tools/testing/selftests/mm/uffd-unit-tests.c
+++ b/tools/testing/selftests/mm/uffd-unit-tests.c
@@ -9,6 +9,66 @@ 
 
 #ifdef __NR_userfaultfd
 
+/* The unit test doesn't need a large or random size, make it 32MB for now */
+#define  UFFD_TEST_MEM_SIZE               (32UL << 20)
+
+#define  MEM_ANON                         BIT_ULL(0)
+#define  MEM_SHMEM                        BIT_ULL(1)
+#define  MEM_SHMEM_PRIVATE                BIT_ULL(2)
+#define  MEM_HUGETLB                      BIT_ULL(3)
+#define  MEM_HUGETLB_PRIVATE              BIT_ULL(4)
+
+struct mem_type {
+	const char *name;
+	unsigned int mem_flag;
+	uffd_test_ops_t *mem_ops;
+	bool shared;
+};
+typedef struct mem_type mem_type_t;
+
+mem_type_t mem_types[] = {
+	{
+		.name = "anon",
+		.mem_flag = MEM_ANON,
+		.mem_ops = &anon_uffd_test_ops,
+		.shared = false,
+	},
+	{
+		.name = "shmem",
+		.mem_flag = MEM_SHMEM,
+		.mem_ops = &shmem_uffd_test_ops,
+		.shared = true,
+	},
+	{
+		.name = "shmem-private",
+		.mem_flag = MEM_SHMEM_PRIVATE,
+		.mem_ops = &shmem_uffd_test_ops,
+		.shared = false,
+	},
+	{
+		.name = "hugetlb",
+		.mem_flag = MEM_HUGETLB,
+		.mem_ops = &hugetlb_uffd_test_ops,
+		.shared = true,
+	},
+	{
+		.name = "hugetlb-private",
+		.mem_flag = MEM_HUGETLB_PRIVATE,
+		.mem_ops = &hugetlb_uffd_test_ops,
+		.shared = false,
+	},
+};
+
+/* Returns: UFFD_TEST_* */
+typedef void (*uffd_test_fn)(void);
+
+typedef struct {
+	const char *name;
+	uffd_test_fn uffd_fn;
+	unsigned int mem_targets;
+	uint64_t uffd_feature_required;
+} uffd_test_case_t;
+
 struct {
 	unsigned int pass, skip, fail, total;
 } uffd_test_acct;
@@ -108,9 +168,50 @@  static int test_uffd_api(bool use_dev)
 	return 1;
 }
 
+/*
+ * This function initializes the global variables.  TODO: remove global
+ * vars and then remove this.
+ */
+static int uffd_setup_environment(uffd_test_case_t *test, mem_type_t *mem_type)
+{
+	map_shared = mem_type->shared;
+	uffd_test_ops = mem_type->mem_ops;
+
+	if (mem_type->mem_flag & (MEM_HUGETLB_PRIVATE | MEM_HUGETLB))
+		page_size = default_huge_page_size();
+	else
+		page_size = psize();
+
+	nr_pages = UFFD_TEST_MEM_SIZE / page_size;
+	/* TODO: remove this global var.. it's so ugly */
+	nr_cpus = 1;
+
+	return uffd_test_ctx_init(test->uffd_feature_required);
+}
+
+static bool uffd_feature_supported(uffd_test_case_t *test)
+{
+	uint64_t features;
+
+	if (uffd_get_features(&features))
+		return false;
+
+	return (features & test->uffd_feature_required) ==
+	    test->uffd_feature_required;
+}
+
+uffd_test_case_t uffd_tests[] = {
+};
+
 int main(int argc, char *argv[])
 {
+	int n_tests = sizeof(uffd_tests) / sizeof(uffd_test_case_t);
+	int n_mems = sizeof(mem_types) / sizeof(mem_type_t);
+	uffd_test_case_t *test;
+	mem_type_t *mem_type;
+	char test_name[128];
 	int has_uffd;
+	int i, j;
 
 	has_uffd = test_uffd_api(false);
 	has_uffd |= test_uffd_api(true);
@@ -119,7 +220,31 @@  int main(int argc, char *argv[])
 		printf("Userfaultfd not supported or unprivileged, skip all tests\n");
 		exit(KSFT_SKIP);
 	}
+
+	for (i = 0; i < n_tests; i++) {
+		test = &uffd_tests[i];
+		for (j = 0; j < n_mems; j++) {
+			mem_type = &mem_types[j];
+			if (!(test->mem_targets & mem_type->mem_flag))
+				continue;
+			snprintf(test_name, sizeof(test_name),
+				 "%s on %s", test->name, mem_type->name);
+
+			uffd_test_start(test_name);
+			if (!uffd_feature_supported(test)) {
+				uffd_test_skip("feature missing");
+				continue;
+			}
+			if (uffd_setup_environment(test, mem_type)) {
+				uffd_test_skip("memory allocation failed");
+				continue;
+			}
+			test->uffd_fn();
+		}
+	}
+
 	uffd_test_report();
+
 	return uffd_test_acct.fail ? KSFT_FAIL : KSFT_PASS;
 }
 
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 62fcf039d6b7..dad1f62a7ecd 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -264,3 +264,30 @@  int uffd_open(unsigned int flags)
 
 	return uffd;
 }
+
+int uffd_get_features(uint64_t *features)
+{
+	struct uffdio_api uffdio_api = { .api = UFFD_API, .features = 0 };
+	/*
+	 * This should by default work in most kernels; the feature list
+	 * will be the same no matter what we pass in here.
+	 */
+	int fd = uffd_open(UFFD_USER_MODE_ONLY);
+
+	if (fd < 0)
+		/* Maybe the kernel is older than user-only mode? */
+		fd = uffd_open(0);
+
+	if (fd < 0)
+		return fd;
+
+	if (ioctl(fd, UFFDIO_API, &uffdio_api)) {
+		close(fd);
+		return -errno;
+	}
+
+	*features = uffdio_api.features;
+	close(fd);
+
+	return 0;
+}
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index a67db8432855..2edad3256271 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -51,6 +51,7 @@  int uffd_open(unsigned int flags);
 int uffd_register(int uffd, void *addr, uint64_t len,
 		  bool miss, bool wp, bool minor);
 int uffd_unregister(int uffd, void *addr, uint64_t len);
+int uffd_get_features(uint64_t *features);
 
 /*
  * On ppc64 this will only work with radix 2M hugepage size