| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <cbfs.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <tests/test.h> |
| #include <ux_locales.h> |
| #include <vb2_api.h> |
| |
| #define DATA_DEFAULT \ |
| ( \ |
| "\x01" /* Version. */ \ |
| "name_1\x00" /* name_1, langs = [0, 2, 30]. */ \ |
| "0\x00translation_1_0\x00" \ |
| "2\x00translation_1_2\x00" \ |
| "30\x00translation_1_30\x00" \ |
| "\x01" \ |
| "name_15\x00" /* name_15, langs = [4, 25, 60]. */ \ |
| "4\x00translation_15_4\x00" \ |
| "25\x00translation_15_25\x00" \ |
| "60\x00translation_15_60\x00" \ |
| "\x01" \ |
| "name_20\x00" /* name_20, langs = [8]. */ \ |
| "8\x00translation_20_8\x00" \ |
| "\x01" \ |
| ) |
| const unsigned char data_default[] = DATA_DEFAULT; |
| |
| /* |
| * The data must be set in the setup function and might be used in cbfs related functions. |
| * The size of the data must be recorded because the data contains the null character \x00. |
| * |
| * Note that sizeof(DATA_DEFAULT) will count the '\0' at the end, so DATA_DEFAULT_SIZE, i.e. |
| * the real data size must be minus one. |
| */ |
| #define DATA_DEFAULT_SIZE (sizeof(DATA_DEFAULT) - 1) |
| |
| #define MAX_DATA_SIZE (DATA_DEFAULT_SIZE + 1) |
| struct { |
| unsigned char raw[MAX_DATA_SIZE]; |
| size_t size; |
| } data; |
| |
| /* Mock functions. */ |
| void cbfs_unmap(void *mapping) |
| { |
| /* No-op. */ |
| } |
| |
| /* We can't mock cbfs_ro_map() directly as it's a static inline function. */ |
| void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg, |
| size_t *size_out, bool force_ro, enum cbfs_type *type) |
| { |
| /* Mock a successful CBFS mapping. */ |
| if (mock_type(bool)) { |
| *size_out = data.size; |
| return data.raw; |
| } |
| *size_out = 0; |
| return NULL; |
| } |
| |
| uint32_t vb2api_get_locale_id(struct vb2_context *ctx) |
| { |
| return mock_type(uint32_t); |
| } |
| |
| struct vb2_context *vboot_get_context(void) |
| { |
| static struct vb2_context vboot2_ctx; |
| return &vboot2_ctx; |
| } |
| |
| /* Test states for test_ux_locales_get_text with valid CBFS data. */ |
| struct ux_locales_test_state { |
| const char *name; |
| uint32_t lang_id; |
| const char *expect; |
| }; |
| |
| /* Setup and teardown functions. */ |
| static int setup_default(void **state) |
| { |
| void *ret; |
| /* Setup the mocked cbfs region data. */ |
| data.size = DATA_DEFAULT_SIZE; |
| ret = memset(data.raw, 0xff, MAX_DATA_SIZE); |
| if (!ret) |
| return 1; |
| ret = memcpy(data.raw, data_default, data.size); |
| if (!ret) |
| return 1; |
| return 0; |
| } |
| |
| static int setup_bad_version(void **state) |
| { |
| int ret = setup_default(state); |
| if (ret) |
| return ret; |
| /* Modify the version byte. */ |
| data.raw[0] = ~data.raw[0]; |
| return 0; |
| } |
| |
| static int teardown_unmap(void **state) |
| { |
| /* We need to reset the cached_state in src/lib/ux_locales.c. */ |
| ux_locales_unmap(); |
| return 0; |
| } |
| |
| /* Test items. */ |
| static void test_ux_locales_get_text(void **state) |
| { |
| struct ux_locales_test_state *s = *state; |
| const char *ret; |
| |
| will_return(_cbfs_alloc, true); |
| will_return(vb2api_get_locale_id, s->lang_id); |
| ret = ux_locales_get_text(s->name); |
| if (s->expect) { |
| assert_non_null(ret); |
| assert_string_equal(ret, s->expect); |
| } else { |
| assert_null(ret); |
| } |
| } |
| |
| static void test_ux_locales_bad_cbfs(void **state) |
| { |
| will_return(_cbfs_alloc, false); |
| will_return_maybe(vb2api_get_locale_id, 0); |
| assert_null(ux_locales_get_text("name_1")); |
| } |
| |
| static void test_ux_locales_bad_version(void **state) |
| { |
| will_return(_cbfs_alloc, true); |
| will_return(vb2api_get_locale_id, 0); |
| assert_null(ux_locales_get_text("name_1")); |
| } |
| |
| static void test_ux_locales_two_calls(void **state) |
| { |
| const char *ret; |
| |
| /* We do not need to ensure that we cached the cbfs region. */ |
| will_return_always(_cbfs_alloc, true); |
| |
| /* Call #1: read (15, 60). */ |
| will_return(vb2api_get_locale_id, 60); |
| ret = ux_locales_get_text("name_15"); |
| assert_non_null(ret); |
| assert_string_equal(ret, "translation_15_60"); |
| |
| /* Call #2: read (1, 0). */ |
| will_return(vb2api_get_locale_id, 0); |
| ret = ux_locales_get_text("name_1"); |
| assert_non_null(ret); |
| assert_string_equal(ret, "translation_1_0"); |
| } |
| |
| static void test_ux_locales_null_terminated(void **state) |
| { |
| will_return_always(_cbfs_alloc, true); |
| will_return_always(vb2api_get_locale_id, 8); |
| |
| /* Verify the access to the very last text. */ |
| assert_non_null(ux_locales_get_text("name_20")); |
| |
| /* Modify the last 2 bytes from "\x00\x01" to "XX" and unmap, */ |
| data.raw[data.size - 1] = 'X'; |
| data.raw[data.size - 2] = 'X'; |
| ux_locales_unmap(); |
| |
| /* The last few characters are now changed so that the data is not NULL terminated. |
| This will prevent us from accessing the last text. */ |
| assert_null(ux_locales_get_text("name_20")); |
| } |
| |
| /* |
| * This macro helps test ux_locales_get_text with `_name` and `_lang_id`. |
| * If `_expect` is NULL, then the function should not find anything. |
| * Otherwise, the function should find the corresponding expect value. |
| */ |
| #define _UX_LOCALES_GET_TEXT_TEST(_test_name, _name, _lang_id, _expect) \ |
| ((struct CMUnitTest) { \ |
| .name = _test_name, \ |
| .test_func = test_ux_locales_get_text, \ |
| .setup_func = setup_default, \ |
| .teardown_func = teardown_unmap, \ |
| .initial_state = &(struct ux_locales_test_state) \ |
| { \ |
| .name = _name, \ |
| .lang_id = _lang_id, \ |
| .expect = _expect, \ |
| }, \ |
| }) |
| |
| /* |
| * When exporting test results to xml files, cmocka doesn't escape double quotes for test names. |
| * Therefore, double quotes in CMUnitTest.name will lead to invalid xml files, causing build |
| * failure (with JUNIT_OUTPUT=y). As a result, we can only use _expect for CMUnitTest.name in |
| * the macro, but not #_expect. |
| */ |
| #define UX_LOCALES_GET_TEXT_FOUND_TEST(_name, _lang_id, _expect) \ |
| (_UX_LOCALES_GET_TEXT_TEST \ |
| ( \ |
| ( \ |
| "test_ux_locales_get_text_found(name=" _name \ |
| ", lang_id=" #_lang_id ", expect=" _expect ")" \ |
| ), \ |
| _name, _lang_id, _expect \ |
| )) |
| |
| #define UX_LOCALES_GET_TEXT_NOT_FOUND_TEST(_name, _lang_id) \ |
| (_UX_LOCALES_GET_TEXT_TEST \ |
| ( \ |
| ( \ |
| "test_ux_locales_get_text_not_found(name=" _name \ |
| ", lang_id=" #_lang_id ")" \ |
| ), \ |
| _name, _lang_id, NULL \ |
| )) |
| |
| int main(void) |
| { |
| const struct CMUnitTest tests[] = { |
| /* Get text successfully. */ |
| UX_LOCALES_GET_TEXT_FOUND_TEST("name_1", 0, "translation_1_0"), |
| /* Get text with name and id both in the middle. */ |
| UX_LOCALES_GET_TEXT_FOUND_TEST("name_15", 25, "translation_15_25"), |
| /* Ensure we check the whole string of 'name'. |
| ('name_2' is the prefix of 'name_20') */ |
| UX_LOCALES_GET_TEXT_NOT_FOUND_TEST("name_2", 3), |
| /* Ensure we check the whole string of 'lang_id'. |
| (id:'2' is the prefix of id:'25' in 'name_15') */ |
| UX_LOCALES_GET_TEXT_NOT_FOUND_TEST("name_15", 2), |
| /* Ensure we will fallback to 0. */ |
| UX_LOCALES_GET_TEXT_FOUND_TEST("name_1", 7, "translation_1_0"), |
| /* Do not search for locale id with unmatched name. */ |
| UX_LOCALES_GET_TEXT_NOT_FOUND_TEST("name_15", 8), |
| /* Validity check of lang_id > 100. We will fallback to 0. */ |
| UX_LOCALES_GET_TEXT_FOUND_TEST("name_1", 100, "translation_1_0"), |
| /* cbfs not found. */ |
| cmocka_unit_test_setup_teardown(test_ux_locales_bad_cbfs, setup_default, |
| teardown_unmap), |
| /* Bad version. */ |
| cmocka_unit_test_setup_teardown(test_ux_locales_bad_version, setup_bad_version, |
| teardown_unmap), |
| /* Read multiple times. */ |
| cmocka_unit_test_setup_teardown(test_ux_locales_two_calls, setup_default, |
| teardown_unmap), |
| /* Validity check of NULL terminated. */ |
| cmocka_unit_test_setup_teardown(test_ux_locales_null_terminated, |
| setup_default, teardown_unmap), |
| }; |
| |
| return cb_run_group_tests(tests, NULL, NULL); |
| } |