537 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * KUnit test for struct string_stream.4 *5 * Copyright (C) 2019, Google LLC.6 * Author: Brendan Higgins <brendanhiggins@google.com>7 */8 9#include <kunit/static_stub.h>10#include <kunit/test.h>11#include <linux/ktime.h>12#include <linux/slab.h>13#include <linux/timekeeping.h>14 15#include "string-stream.h"16 17struct string_stream_test_priv {18 /* For testing resource-managed free. */19 struct string_stream *expected_free_stream;20 bool stream_was_freed;21 bool stream_free_again;22};23 24/* Avoids a cast warning if kfree() is passed direct to kunit_add_action(). */25KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);26 27/* Avoids a cast warning if string_stream_destroy() is passed direct to kunit_add_action(). */28KUNIT_DEFINE_ACTION_WRAPPER(cleanup_raw_stream, string_stream_destroy, struct string_stream *);29 30static char *get_concatenated_string(struct kunit *test, struct string_stream *stream)31{32 char *str = string_stream_get_string(stream);33 34 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, str);35 kunit_add_action(test, kfree_wrapper, (void *)str);36 37 return str;38}39 40/* Managed string_stream object is initialized correctly. */41static void string_stream_managed_init_test(struct kunit *test)42{43 struct string_stream *stream;44 45 /* Resource-managed initialization. */46 stream = kunit_alloc_string_stream(test, GFP_KERNEL);47 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);48 49 KUNIT_EXPECT_EQ(test, stream->length, 0);50 KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments));51 KUNIT_EXPECT_TRUE(test, (stream->gfp == GFP_KERNEL));52 KUNIT_EXPECT_FALSE(test, stream->append_newlines);53 KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream));54}55 56/* Unmanaged string_stream object is initialized correctly. */57static void string_stream_unmanaged_init_test(struct kunit *test)58{59 struct string_stream *stream;60 61 stream = alloc_string_stream(GFP_KERNEL);62 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);63 kunit_add_action(test, cleanup_raw_stream, stream);64 65 KUNIT_EXPECT_EQ(test, stream->length, 0);66 KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments));67 KUNIT_EXPECT_TRUE(test, (stream->gfp == GFP_KERNEL));68 KUNIT_EXPECT_FALSE(test, stream->append_newlines);69 70 KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream));71}72 73static void string_stream_destroy_stub(struct string_stream *stream)74{75 struct kunit *fake_test = kunit_get_current_test();76 struct string_stream_test_priv *priv = fake_test->priv;77 78 /* The kunit could own string_streams other than the one we are testing. */79 if (stream == priv->expected_free_stream) {80 if (priv->stream_was_freed)81 priv->stream_free_again = true;82 else83 priv->stream_was_freed = true;84 }85 86 /*87 * Calling string_stream_destroy() will only call this function again88 * because the redirection stub is still active.89 * Avoid calling deactivate_static_stub() or changing current->kunit_test90 * during cleanup.91 */92 string_stream_clear(stream);93 kfree(stream);94}95 96/* kunit_free_string_stream() calls string_stream_desrtoy() */97static void string_stream_managed_free_test(struct kunit *test)98{99 struct string_stream_test_priv *priv = test->priv;100 101 priv->expected_free_stream = NULL;102 priv->stream_was_freed = false;103 priv->stream_free_again = false;104 105 kunit_activate_static_stub(test,106 string_stream_destroy,107 string_stream_destroy_stub);108 109 priv->expected_free_stream = kunit_alloc_string_stream(test, GFP_KERNEL);110 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->expected_free_stream);111 112 /* This should call the stub function. */113 kunit_free_string_stream(test, priv->expected_free_stream);114 115 KUNIT_EXPECT_TRUE(test, priv->stream_was_freed);116 KUNIT_EXPECT_FALSE(test, priv->stream_free_again);117}118 119/* string_stream object is freed when test is cleaned up. */120static void string_stream_resource_free_test(struct kunit *test)121{122 struct string_stream_test_priv *priv = test->priv;123 struct kunit *fake_test;124 125 fake_test = kunit_kzalloc(test, sizeof(*fake_test), GFP_KERNEL);126 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_test);127 128 kunit_init_test(fake_test, "string_stream_fake_test", NULL);129 fake_test->priv = priv;130 131 /*132 * Activate stub before creating string_stream so the133 * string_stream will be cleaned up first.134 */135 priv->expected_free_stream = NULL;136 priv->stream_was_freed = false;137 priv->stream_free_again = false;138 139 kunit_activate_static_stub(fake_test,140 string_stream_destroy,141 string_stream_destroy_stub);142 143 priv->expected_free_stream = kunit_alloc_string_stream(fake_test, GFP_KERNEL);144 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->expected_free_stream);145 146 /* Set current->kunit_test to fake_test so the static stub will be called. */147 current->kunit_test = fake_test;148 149 /* Cleanup test - the stub function should be called */150 kunit_cleanup(fake_test);151 152 /* Set current->kunit_test back to current test. */153 current->kunit_test = test;154 155 KUNIT_EXPECT_TRUE(test, priv->stream_was_freed);156 KUNIT_EXPECT_FALSE(test, priv->stream_free_again);157}158 159/*160 * Add a series of lines to a string_stream. Check that all lines161 * appear in the correct order and no characters are dropped.162 */163static void string_stream_line_add_test(struct kunit *test)164{165 struct string_stream *stream;166 char line[60];167 char *concat_string, *pos, *string_end;168 size_t len, total_len;169 int num_lines, i;170 171 stream = kunit_alloc_string_stream(test, GFP_KERNEL);172 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);173 174 /* Add series of sequence numbered lines */175 total_len = 0;176 for (i = 0; i < 100; ++i) {177 len = snprintf(line, sizeof(line),178 "The quick brown fox jumps over the lazy penguin %d\n", i);179 180 /* Sanity-check that our test string isn't truncated */181 KUNIT_ASSERT_LT(test, len, sizeof(line));182 183 string_stream_add(stream, line);184 total_len += len;185 }186 num_lines = i;187 188 concat_string = get_concatenated_string(test, stream);189 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, concat_string);190 KUNIT_EXPECT_EQ(test, strlen(concat_string), total_len);191 192 /*193 * Split the concatenated string at the newlines and check that194 * all the original added strings are present.195 */196 pos = concat_string;197 for (i = 0; i < num_lines; ++i) {198 string_end = strchr(pos, '\n');199 KUNIT_EXPECT_NOT_NULL(test, string_end);200 201 /* Convert to NULL-terminated string */202 *string_end = '\0';203 204 snprintf(line, sizeof(line),205 "The quick brown fox jumps over the lazy penguin %d", i);206 KUNIT_EXPECT_STREQ(test, pos, line);207 208 pos = string_end + 1;209 }210 211 /* There shouldn't be any more data after this */212 KUNIT_EXPECT_EQ(test, strlen(pos), 0);213}214 215/* Add a series of lines of variable length to a string_stream. */216static void string_stream_variable_length_line_test(struct kunit *test)217{218 static const char line[] =219 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"220 " 0123456789!$%^&*()_-+={}[]:;@'~#<>,.?/|";221 struct string_stream *stream;222 struct rnd_state rnd;223 char *concat_string, *pos, *string_end;224 size_t offset, total_len;225 int num_lines, i;226 227 stream = kunit_alloc_string_stream(test, GFP_KERNEL);228 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);229 230 /*231 * Log many lines of varying lengths until we have created232 * many fragments.233 * The "randomness" must be repeatable.234 */235 prandom_seed_state(&rnd, 3141592653589793238ULL);236 total_len = 0;237 for (i = 0; i < 100; ++i) {238 offset = prandom_u32_state(&rnd) % (sizeof(line) - 1);239 string_stream_add(stream, "%s\n", &line[offset]);240 total_len += sizeof(line) - offset;241 }242 num_lines = i;243 244 concat_string = get_concatenated_string(test, stream);245 KUNIT_EXPECT_NOT_ERR_OR_NULL(test, concat_string);246 KUNIT_EXPECT_EQ(test, strlen(concat_string), total_len);247 248 /*249 * Split the concatenated string at the newlines and check that250 * all the original added strings are present.251 */252 prandom_seed_state(&rnd, 3141592653589793238ULL);253 pos = concat_string;254 for (i = 0; i < num_lines; ++i) {255 string_end = strchr(pos, '\n');256 KUNIT_EXPECT_NOT_NULL(test, string_end);257 258 /* Convert to NULL-terminated string */259 *string_end = '\0';260 261 offset = prandom_u32_state(&rnd) % (sizeof(line) - 1);262 KUNIT_EXPECT_STREQ(test, pos, &line[offset]);263 264 pos = string_end + 1;265 }266 267 /* There shouldn't be any more data after this */268 KUNIT_EXPECT_EQ(test, strlen(pos), 0);269}270 271/* Appending the content of one string stream to another. */272static void string_stream_append_test(struct kunit *test)273{274 static const char * const strings_1[] = {275 "one", "two", "three", "four", "five", "six",276 "seven", "eight", "nine", "ten",277 };278 static const char * const strings_2[] = {279 "Apple", "Pear", "Orange", "Banana", "Grape", "Apricot",280 };281 struct string_stream *stream_1, *stream_2;282 const char *stream1_content_before_append, *stream_2_content;283 char *combined_content;284 size_t combined_length;285 int i;286 287 stream_1 = kunit_alloc_string_stream(test, GFP_KERNEL);288 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_1);289 290 stream_2 = kunit_alloc_string_stream(test, GFP_KERNEL);291 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_2);292 293 /* Append content of empty stream to empty stream */294 string_stream_append(stream_1, stream_2);295 KUNIT_EXPECT_EQ(test, strlen(get_concatenated_string(test, stream_1)), 0);296 297 /* Add some data to stream_1 */298 for (i = 0; i < ARRAY_SIZE(strings_1); ++i)299 string_stream_add(stream_1, "%s\n", strings_1[i]);300 301 stream1_content_before_append = get_concatenated_string(test, stream_1);302 303 /* Append content of empty stream to non-empty stream */304 string_stream_append(stream_1, stream_2);305 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1),306 stream1_content_before_append);307 308 /* Add some data to stream_2 */309 for (i = 0; i < ARRAY_SIZE(strings_2); ++i)310 string_stream_add(stream_2, "%s\n", strings_2[i]);311 312 /* Append content of non-empty stream to non-empty stream */313 string_stream_append(stream_1, stream_2);314 315 /*316 * End result should be the original content of stream_1 plus317 * the content of stream_2.318 */319 stream_2_content = get_concatenated_string(test, stream_2);320 combined_length = strlen(stream1_content_before_append) + strlen(stream_2_content);321 combined_length++; /* for terminating \0 */322 combined_content = kunit_kmalloc(test, combined_length, GFP_KERNEL);323 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, combined_content);324 snprintf(combined_content, combined_length, "%s%s",325 stream1_content_before_append, stream_2_content);326 327 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), combined_content);328 329 /* Append content of non-empty stream to empty stream */330 kunit_free_string_stream(test, stream_1);331 332 stream_1 = kunit_alloc_string_stream(test, GFP_KERNEL);333 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_1);334 335 string_stream_append(stream_1, stream_2);336 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), stream_2_content);337}338 339/* Appending the content of one string stream to one with auto-newlining. */340static void string_stream_append_auto_newline_test(struct kunit *test)341{342 struct string_stream *stream_1, *stream_2;343 344 /* Stream 1 has newline appending enabled */345 stream_1 = kunit_alloc_string_stream(test, GFP_KERNEL);346 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_1);347 string_stream_set_append_newlines(stream_1, true);348 KUNIT_EXPECT_TRUE(test, stream_1->append_newlines);349 350 /* Stream 2 does not append newlines */351 stream_2 = kunit_alloc_string_stream(test, GFP_KERNEL);352 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_2);353 354 /* Appending a stream with a newline should not add another newline */355 string_stream_add(stream_1, "Original string\n");356 string_stream_add(stream_2, "Appended content\n");357 string_stream_add(stream_2, "More stuff\n");358 string_stream_append(stream_1, stream_2);359 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1),360 "Original string\nAppended content\nMore stuff\n");361 362 kunit_free_string_stream(test, stream_2);363 stream_2 = kunit_alloc_string_stream(test, GFP_KERNEL);364 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_2);365 366 /*367 * Appending a stream without newline should add a final newline.368 * The appended string_stream is treated as a single string so newlines369 * should not be inserted between fragments.370 */371 string_stream_add(stream_2, "Another");372 string_stream_add(stream_2, "And again");373 string_stream_append(stream_1, stream_2);374 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1),375 "Original string\nAppended content\nMore stuff\nAnotherAnd again\n");376}377 378/* Adding an empty string should not create a fragment. */379static void string_stream_append_empty_string_test(struct kunit *test)380{381 struct string_stream *stream;382 int original_frag_count;383 384 stream = kunit_alloc_string_stream(test, GFP_KERNEL);385 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);386 387 /* Formatted empty string */388 string_stream_add(stream, "%s", "");389 KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream));390 KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments));391 392 /* Adding an empty string to a non-empty stream */393 string_stream_add(stream, "Add this line");394 original_frag_count = list_count_nodes(&stream->fragments);395 396 string_stream_add(stream, "%s", "");397 KUNIT_EXPECT_EQ(test, list_count_nodes(&stream->fragments), original_frag_count);398 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream), "Add this line");399}400 401/* Adding strings without automatic newline appending */402static void string_stream_no_auto_newline_test(struct kunit *test)403{404 struct string_stream *stream;405 406 stream = kunit_alloc_string_stream(test, GFP_KERNEL);407 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);408 409 /*410 * Add some strings with and without newlines. All formatted newlines411 * should be preserved. It should not add any extra newlines.412 */413 string_stream_add(stream, "One");414 string_stream_add(stream, "Two\n");415 string_stream_add(stream, "%s\n", "Three");416 string_stream_add(stream, "%s", "Four\n");417 string_stream_add(stream, "Five\n%s", "Six");418 string_stream_add(stream, "Seven\n\n");419 string_stream_add(stream, "Eight");420 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream),421 "OneTwo\nThree\nFour\nFive\nSixSeven\n\nEight");422}423 424/* Adding strings with automatic newline appending */425static void string_stream_auto_newline_test(struct kunit *test)426{427 struct string_stream *stream;428 429 stream = kunit_alloc_string_stream(test, GFP_KERNEL);430 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);431 432 string_stream_set_append_newlines(stream, true);433 KUNIT_EXPECT_TRUE(test, stream->append_newlines);434 435 /*436 * Add some strings with and without newlines. Newlines should437 * be appended to lines that do not end with \n, but newlines438 * resulting from the formatting should not be changed.439 */440 string_stream_add(stream, "One");441 string_stream_add(stream, "Two\n");442 string_stream_add(stream, "%s\n", "Three");443 string_stream_add(stream, "%s", "Four\n");444 string_stream_add(stream, "Five\n%s", "Six");445 string_stream_add(stream, "Seven\n\n");446 string_stream_add(stream, "Eight");447 KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream),448 "One\nTwo\nThree\nFour\nFive\nSix\nSeven\n\nEight\n");449}450 451/*452 * This doesn't actually "test" anything. It reports time taken453 * and memory used for logging a large number of lines.454 */455static void string_stream_performance_test(struct kunit *test)456{457 struct string_stream_fragment *frag_container;458 struct string_stream *stream;459 char test_line[101];460 ktime_t start_time, end_time;461 size_t len, bytes_requested, actual_bytes_used, total_string_length;462 int offset, i;463 464 stream = kunit_alloc_string_stream(test, GFP_KERNEL);465 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);466 467 memset(test_line, 'x', sizeof(test_line) - 1);468 test_line[sizeof(test_line) - 1] = '\0';469 470 start_time = ktime_get();471 for (i = 0; i < 10000; i++) {472 offset = i % (sizeof(test_line) - 1);473 string_stream_add(stream, "%s: %d\n", &test_line[offset], i);474 }475 end_time = ktime_get();476 477 /*478 * Calculate memory used. This doesn't include invisible479 * overhead due to kernel allocator fragment size rounding.480 */481 bytes_requested = sizeof(*stream);482 actual_bytes_used = ksize(stream);483 total_string_length = 0;484 485 list_for_each_entry(frag_container, &stream->fragments, node) {486 bytes_requested += sizeof(*frag_container);487 actual_bytes_used += ksize(frag_container);488 489 len = strlen(frag_container->fragment);490 total_string_length += len;491 bytes_requested += len + 1; /* +1 for '\0' */492 actual_bytes_used += ksize(frag_container->fragment);493 }494 495 kunit_info(test, "Time elapsed: %lld us\n",496 ktime_us_delta(end_time, start_time));497 kunit_info(test, "Total string length: %zu\n", total_string_length);498 kunit_info(test, "Bytes requested: %zu\n", bytes_requested);499 kunit_info(test, "Actual bytes allocated: %zu\n", actual_bytes_used);500}501 502static int string_stream_test_init(struct kunit *test)503{504 struct string_stream_test_priv *priv;505 506 priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);507 if (!priv)508 return -ENOMEM;509 510 test->priv = priv;511 512 return 0;513}514 515static struct kunit_case string_stream_test_cases[] = {516 KUNIT_CASE(string_stream_managed_init_test),517 KUNIT_CASE(string_stream_unmanaged_init_test),518 KUNIT_CASE(string_stream_managed_free_test),519 KUNIT_CASE(string_stream_resource_free_test),520 KUNIT_CASE(string_stream_line_add_test),521 KUNIT_CASE(string_stream_variable_length_line_test),522 KUNIT_CASE(string_stream_append_test),523 KUNIT_CASE(string_stream_append_auto_newline_test),524 KUNIT_CASE(string_stream_append_empty_string_test),525 KUNIT_CASE(string_stream_no_auto_newline_test),526 KUNIT_CASE(string_stream_auto_newline_test),527 KUNIT_CASE(string_stream_performance_test),528 {}529};530 531static struct kunit_suite string_stream_test_suite = {532 .name = "string-stream-test",533 .test_cases = string_stream_test_cases,534 .init = string_stream_test_init,535};536kunit_test_suites(&string_stream_test_suite);537