Commit 62b9ef50 authored by Gabriela Bittencourt's avatar Gabriela Bittencourt Committed by Kees Cook
Browse files

unicode: kunit: refactor selftest to kunit tests



Refactoring 'test' functions into kunit tests, to test utf-8 support in
unicode subsystem.

This allows the utf8 tests to be run alongside the KUnit test suite
using kunit-tool, quickly compiling and running all desired tests as
part of the KUnit test suite, instead of compiling the selftest module
and loading it.

The refactoring kept the original testing logic intact, while adopting a
testing pattern across different kernel modules and leveraging KUnit's
benefits.

Co-developed-by: default avatarPedro Orlando <porlando@lkcamp.dev>
Signed-off-by: default avatarPedro Orlando <porlando@lkcamp.dev>
Co-developed-by: default avatarDanilo Pereira <dpereira@lkcamp.dev>
Signed-off-by: default avatarDanilo Pereira <dpereira@lkcamp.dev>
Signed-off-by: default avatarGabriela Bittencourt <gbittencourt@lkcamp.dev>
Reviewed-by: default avatarDavid Gow <davidgow@google.com>
Acked-by: default avatarGabriel Krisman Bertazi <krisman@suse.de>
Reviewed-by: default avatarRae Moar <rmoar@google.com>
Link: https://lore.kernel.org/r/20241202075545.3648096-6-davidgow@google.com


Signed-off-by: default avatarKees Cook <kees@kernel.org>
parent 4d557cb4
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
CONFIG_KUNIT=y
CONFIG_UNICODE=y
CONFIG_UNICODE_NORMALIZATION_KUNIT_TEST=y
+3 −2
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ config UNICODE
	  be a separate loadable module that gets requested only when a file
	  system actually use it.

config UNICODE_NORMALIZATION_SELFTEST
config UNICODE_NORMALIZATION_KUNIT_TEST
	tristate "Test UTF-8 normalization support"
	depends on UNICODE
	depends on UNICODE && KUNIT
	default KUNIT_ALL_TESTS
+1 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ ifneq ($(CONFIG_UNICODE),)
obj-y			+= unicode.o
endif
obj-$(CONFIG_UNICODE)	+= utf8data.o
obj-$(CONFIG_UNICODE_NORMALIZATION_SELFTEST) += utf8-selftest.o
obj-$(CONFIG_UNICODE_NORMALIZATION_KUNIT_TEST) += utf8-selftest.o

unicode-y := utf8-norm.o utf8-core.o

+1 −1
Original line number Diff line number Diff line
@@ -586,7 +586,7 @@ int utf8byte(struct utf8cursor *u8c)
	}
}

#ifdef CONFIG_UNICODE_NORMALIZATION_SELFTEST_MODULE
#if IS_MODULE(CONFIG_UNICODE_NORMALIZATION_KUNIT_TEST)
EXPORT_SYMBOL_GPL(utf8version_is_supported);
EXPORT_SYMBOL_GPL(utf8nlen);
EXPORT_SYMBOL_GPL(utf8ncursor);
+69 −80
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Kernel module for testing utf-8 support.
 * KUnit tests for utf-8 support.
 *
 * Copyright 2017 Collabora Ltd.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/unicode.h>
#include <linux/dcache.h>
#include <kunit/test.h>

#include "utf8n.h"

static unsigned int failed_tests;
static unsigned int total_tests;

#define _test(cond, func, line, fmt, ...) do {				\
		total_tests++;						\
		if (!cond) {						\
			failed_tests++;					\
			pr_err("test %s:%d Failed: %s%s",		\
			       func, line, #cond, (fmt?":":"."));	\
			if (fmt)					\
				pr_err(fmt, ##__VA_ARGS__);		\
		}							\
	} while (0)
#define test_f(cond, fmt, ...) _test(cond, __func__, __LINE__, fmt, ##__VA_ARGS__)
#define test(cond) _test(cond, __func__, __LINE__, "")

static const struct {
	/* UTF-8 strings in this vector _must_ be NULL-terminated. */
	unsigned char str[10];
@@ -167,69 +147,74 @@ static int utf8cursor(struct utf8cursor *u8c, const struct unicode_map *um,
	return utf8ncursor(u8c, um, n, s, (unsigned int)-1);
}

static void check_utf8_nfdi(struct unicode_map *um)
static void check_utf8_nfdi(struct kunit *test)
{
	int i;
	struct utf8cursor u8c;
	struct unicode_map *um = test->priv;

	for (i = 0; i < ARRAY_SIZE(nfdi_test_data); i++) {
		int len = strlen(nfdi_test_data[i].str);
		int nlen = strlen(nfdi_test_data[i].dec);
		int j = 0;
		unsigned char c;
		int ret;

		KUNIT_EXPECT_EQ(test, utf8len(um, UTF8_NFDI, nfdi_test_data[i].str), nlen);
		KUNIT_EXPECT_EQ(test, utf8nlen(um, UTF8_NFDI, nfdi_test_data[i].str, len),
				nlen);

		test((utf8len(um, UTF8_NFDI, nfdi_test_data[i].str) == nlen));
		test((utf8nlen(um, UTF8_NFDI, nfdi_test_data[i].str, len) ==
			nlen));

		if (utf8cursor(&u8c, um, UTF8_NFDI, nfdi_test_data[i].str) < 0)
			pr_err("can't create cursor\n");
		ret = utf8cursor(&u8c, um, UTF8_NFDI, nfdi_test_data[i].str);
		KUNIT_EXPECT_TRUE_MSG(test, ret >= 0, "Can't create cursor\n");

		while ((c = utf8byte(&u8c)) > 0) {
			test_f((c == nfdi_test_data[i].dec[j]),
			KUNIT_EXPECT_EQ_MSG(test, c, nfdi_test_data[i].dec[j],
					    "Unexpected byte 0x%x should be 0x%x\n",
					    c, nfdi_test_data[i].dec[j]);
			j++;
		}

		test((j == nlen));
		KUNIT_EXPECT_EQ(test, j, nlen);
	}
}

static void check_utf8_nfdicf(struct unicode_map *um)
static void check_utf8_nfdicf(struct kunit *test)
{
	int i;
	struct utf8cursor u8c;
	struct unicode_map *um = test->priv;

	for (i = 0; i < ARRAY_SIZE(nfdicf_test_data); i++) {
		int len = strlen(nfdicf_test_data[i].str);
		int nlen = strlen(nfdicf_test_data[i].ncf);
		int j = 0;
		int ret;
		unsigned char c;

		test((utf8len(um, UTF8_NFDICF, nfdicf_test_data[i].str) ==
				nlen));
		test((utf8nlen(um, UTF8_NFDICF, nfdicf_test_data[i].str, len) ==
				nlen));
		KUNIT_EXPECT_EQ(test, utf8len(um, UTF8_NFDICF, nfdicf_test_data[i].str),
				nlen);
		KUNIT_EXPECT_EQ(test, utf8nlen(um, UTF8_NFDICF, nfdicf_test_data[i].str, len),
				nlen);

		if (utf8cursor(&u8c, um, UTF8_NFDICF,
				nfdicf_test_data[i].str) < 0)
			pr_err("can't create cursor\n");
		ret = utf8cursor(&u8c, um, UTF8_NFDICF, nfdicf_test_data[i].str);
		KUNIT_EXPECT_TRUE_MSG(test, ret >= 0, "Can't create cursor\n");

		while ((c = utf8byte(&u8c)) > 0) {
			test_f((c == nfdicf_test_data[i].ncf[j]),
			KUNIT_EXPECT_EQ_MSG(test, c, nfdicf_test_data[i].ncf[j],
					    "Unexpected byte 0x%x should be 0x%x\n",
					    c, nfdicf_test_data[i].ncf[j]);
			j++;
		}

		test((j == nlen));
		KUNIT_EXPECT_EQ(test, j, nlen);
	}
}

static void check_utf8_comparisons(struct unicode_map *table)
static void check_utf8_comparisons(struct kunit *test)
{
	int i;
	struct unicode_map *um = test->priv;

	for (i = 0; i < ARRAY_SIZE(nfdi_test_data); i++) {
		const struct qstr s1 = {.name = nfdi_test_data[i].str,
@@ -237,7 +222,8 @@ static void check_utf8_comparisons(struct unicode_map *table)
		const struct qstr s2 = {.name = nfdi_test_data[i].dec,
					.len = sizeof(nfdi_test_data[i].dec)};

		test_f(!utf8_strncmp(table, &s1, &s2),
		/* strncmp returns 0 when strings are equal */
		KUNIT_EXPECT_TRUE_MSG(test, utf8_strncmp(um, &s1, &s2) == 0,
				    "%s %s comparison mismatch\n", s1.name, s2.name);
	}

@@ -247,62 +233,65 @@ static void check_utf8_comparisons(struct unicode_map *table)
		const struct qstr s2 = {.name = nfdicf_test_data[i].ncf,
					.len = sizeof(nfdicf_test_data[i].ncf)};

		test_f(!utf8_strncasecmp(table, &s1, &s2),
		/* strncasecmp returns 0 when strings are equal */
		KUNIT_EXPECT_TRUE_MSG(test, utf8_strncasecmp(um, &s1, &s2) == 0,
				    "%s %s comparison mismatch\n", s1.name, s2.name);
	}
}

static void check_supported_versions(struct unicode_map *um)
static void check_supported_versions(struct kunit *test)
{
	struct unicode_map *um = test->priv;
	/* Unicode 7.0.0 should be supported. */
	test(utf8version_is_supported(um, UNICODE_AGE(7, 0, 0)));
	KUNIT_EXPECT_TRUE(test, utf8version_is_supported(um, UNICODE_AGE(7, 0, 0)));

	/* Unicode 9.0.0 should be supported. */
	test(utf8version_is_supported(um, UNICODE_AGE(9, 0, 0)));
	KUNIT_EXPECT_TRUE(test, utf8version_is_supported(um, UNICODE_AGE(9, 0, 0)));

	/* Unicode 1x.0.0 (the latest version) should be supported. */
	test(utf8version_is_supported(um, UTF8_LATEST));
	KUNIT_EXPECT_TRUE(test, utf8version_is_supported(um, UTF8_LATEST));

	/* Next versions don't exist. */
	test(!utf8version_is_supported(um, UNICODE_AGE(13, 0, 0)));
	test(!utf8version_is_supported(um, UNICODE_AGE(0, 0, 0)));
	test(!utf8version_is_supported(um, UNICODE_AGE(-1, -1, -1)));
	KUNIT_EXPECT_FALSE(test, utf8version_is_supported(um, UNICODE_AGE(13, 0, 0)));
	KUNIT_EXPECT_FALSE(test, utf8version_is_supported(um, UNICODE_AGE(0, 0, 0)));
	KUNIT_EXPECT_FALSE(test, utf8version_is_supported(um, UNICODE_AGE(-1, -1, -1)));
}

static int __init init_test_ucd(void)
static struct kunit_case unicode_normalization_test_cases[] = {
	KUNIT_CASE(check_supported_versions),
	KUNIT_CASE(check_utf8_comparisons),
	KUNIT_CASE(check_utf8_nfdicf),
	KUNIT_CASE(check_utf8_nfdi),
	{}
};

static int init_test_ucd(struct kunit *test)
{
	struct unicode_map *um;
	struct unicode_map *um = utf8_load(UTF8_LATEST);

	failed_tests = 0;
	total_tests = 0;
	test->priv = um;

	um = utf8_load(UTF8_LATEST);
	if (IS_ERR(um)) {
		pr_err("%s: Unable to load utf8 table.\n", __func__);
		return PTR_ERR(um);
	}
	KUNIT_EXPECT_EQ_MSG(test, IS_ERR(um), 0,
			    "%s: Unable to load utf8 table.\n", __func__);

	check_supported_versions(um);
	check_utf8_nfdi(um);
	check_utf8_nfdicf(um);
	check_utf8_comparisons(um);

	if (!failed_tests)
		pr_info("All %u tests passed\n", total_tests);
	else
		pr_err("%u out of %u tests failed\n", failed_tests,
		       total_tests);
	utf8_unload(um);
	return 0;
}

static void __exit exit_test_ucd(void)
static void exit_test_ucd(struct kunit *test)
{
	utf8_unload(test->priv);
}

module_init(init_test_ucd);
module_exit(exit_test_ucd);
static struct kunit_suite unicode_normalization_test_suite = {
	.name = "unicode_normalization",
	.test_cases = unicode_normalization_test_cases,
	.init = init_test_ucd,
	.exit = exit_test_ucd,
};

kunit_test_suite(unicode_normalization_test_suite);


MODULE_AUTHOR("Gabriel Krisman Bertazi <krisman@collabora.co.uk>");
MODULE_DESCRIPTION("Kernel module for testing utf-8 support");
MODULE_DESCRIPTION("KUnit tests for utf-8 support.");
MODULE_LICENSE("GPL");