/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #include /* * Mock writer to capture what gets passed through */ struct mock_log_writer_impl { struct aws_array_list log_lines; }; static int s_mock_log_writer_write(struct aws_log_writer *writer, const struct aws_string *output) { struct mock_log_writer_impl *impl = (struct mock_log_writer_impl *)writer->impl; struct aws_string *output_copy = aws_string_new_from_string(writer->allocator, output); if (output_copy == NULL) { return AWS_OP_ERR; } aws_array_list_push_back(&impl->log_lines, &output_copy); return AWS_OP_SUCCESS; } static void s_mock_log_writer_clean_up(struct aws_log_writer *writer) { struct mock_log_writer_impl *impl = (struct mock_log_writer_impl *)writer->impl; size_t line_count = aws_array_list_length(&impl->log_lines); for (size_t i = 0; i < line_count; ++i) { struct aws_string *line = NULL; if (aws_array_list_get_at(&impl->log_lines, &line, i)) { continue; } aws_string_destroy(line); } aws_array_list_clean_up(&impl->log_lines); aws_mem_release(writer->allocator, impl); } static struct aws_log_writer_vtable s_mock_writer_vtable = { .write = s_mock_log_writer_write, .clean_up = s_mock_log_writer_clean_up, }; static int s_aws_mock_log_writer_init(struct aws_log_writer *writer, struct aws_allocator *allocator) { struct mock_log_writer_impl *impl = (struct mock_log_writer_impl *)aws_mem_acquire(allocator, sizeof(struct mock_log_writer_impl)); if (impl == NULL) { return AWS_OP_ERR; } if (aws_array_list_init_dynamic(&impl->log_lines, allocator, 10, sizeof(struct aws_string *))) { aws_mem_release(allocator, impl); return AWS_OP_ERR; } writer->vtable = &s_mock_writer_vtable; writer->allocator = allocator; writer->impl = impl; return AWS_OP_SUCCESS; } /* * Test utilities */ static char s_test_error_message[4096]; static bool s_verify_mock_equal( struct aws_log_writer *writer, const struct aws_string ***test_lines, size_t array_length) { struct mock_log_writer_impl *impl = (struct mock_log_writer_impl *)writer->impl; size_t line_count = aws_array_list_length(&impl->log_lines); if (line_count != array_length) { sprintf( s_test_error_message, "Expected %" PRIu64 " lines, but received %" PRIu64 "", (uint64_t)array_length, (uint64_t)line_count); return false; } for (size_t i = 0; i < array_length; ++i) { struct aws_string *captured = NULL; if (aws_array_list_get_at(&impl->log_lines, &captured, i)) { sprintf(s_test_error_message, "Unable to fetch log line from array list"); return false; } const struct aws_string *original = *(test_lines[i]); if (!aws_string_eq(original, captured)) { sprintf( s_test_error_message, "Expected log line:\n%s\nbut received log line:\n%s", (char *)original->bytes, (char *)captured->bytes); return false; } } return true; } typedef int ( *init_channel_fn)(struct aws_log_channel *channel, struct aws_allocator *allocator, struct aws_log_writer *writer); static int s_do_channel_test( struct aws_allocator *allocator, init_channel_fn init_fn, const struct aws_string ***test_lines, size_t test_lines_length, int *sleep_times) { struct aws_log_writer mock_writer; if (s_aws_mock_log_writer_init(&mock_writer, allocator)) { return AWS_OP_ERR; } struct aws_log_channel log_channel; if (init_fn(&log_channel, allocator, &mock_writer)) { return AWS_OP_ERR; } int result = AWS_OP_SUCCESS; for (size_t i = 0; i < test_lines_length; ++i) { struct aws_string *test_line_copy = aws_string_new_from_string(log_channel.allocator, *(test_lines[i])); if ((log_channel.vtable->send)(&log_channel, test_line_copy)) { sprintf(s_test_error_message, "Failed call to log channel send"); result = AWS_OP_ERR; } /* * For background case, optionally sleep to vary the push timing */ if (sleep_times && sleep_times[i] > 0) { aws_thread_current_sleep(sleep_times[i]); } } aws_log_channel_clean_up(&log_channel); if (!s_verify_mock_equal(log_channel.writer, test_lines, test_lines_length)) { result = AWS_OP_ERR; } aws_log_writer_clean_up(&mock_writer); return result; } /* * Test body helper macros */ #define DEFINE_FOREGROUND_LOG_CHANNEL_TEST(test_name, string_array_name) \ static int s_foreground_log_channel_##test_name(struct aws_allocator *allocator, void *ctx) { \ (void)ctx; \ return s_do_channel_test( \ allocator, \ aws_log_channel_init_foreground, \ string_array_name, \ sizeof(string_array_name) / sizeof(struct aws_string **), \ NULL); \ } \ AWS_TEST_CASE(test_foreground_log_channel_##test_name, s_foreground_log_channel_##test_name); #define DEFINE_BACKGROUND_LOG_CHANNEL_TEST(test_name, string_array_name, sleep_times) \ static int s_background_log_channel_##test_name(struct aws_allocator *allocator, void *ctx) { \ (void)ctx; \ return s_do_channel_test( \ allocator, \ aws_log_channel_init_background, \ string_array_name, \ sizeof(string_array_name) / sizeof(struct aws_string **), \ sleep_times); \ } \ AWS_TEST_CASE(test_background_log_channel_##test_name, s_background_log_channel_##test_name); /* * Test data */ AWS_STATIC_STRING_FROM_LITERAL(s_log_line_1, "1"); AWS_STATIC_STRING_FROM_LITERAL(s_log_line_2, "2"); AWS_STATIC_STRING_FROM_LITERAL(s_log_line_3, "3"); AWS_STATIC_STRING_FROM_LITERAL(s_log_line_4, "4"); AWS_STATIC_STRING_FROM_LITERAL(s_log_line_simple, "A simple line.\n"); AWS_STATIC_STRING_FROM_LITERAL(s_log_line_multiline, "There's\na lot\n\tof snow outside.\n"); AWS_STATIC_STRING_FROM_LITERAL(s_log_line_fake, "[DEBUG] [??] [1234567] - Time to crash\n"); const struct aws_string **s_channel_test_one_line[] = {&s_log_line_1}; const struct aws_string **s_channel_test_numbers[] = {&s_log_line_1, &s_log_line_2, &s_log_line_3, &s_log_line_4}; const struct aws_string **s_channel_test_words[] = {&s_log_line_simple, &s_log_line_multiline, &s_log_line_fake}; const struct aws_string **s_channel_test_all[] = { &s_log_line_1, &s_log_line_2, &s_log_line_3, &s_log_line_4, &s_log_line_simple, &s_log_line_multiline, &s_log_line_fake, }; static int s_background_sleep_times_ns[] = {0, 100000, 0, 0, 1000000, 0, 1000000}; /* * Foreground channel tests */ DEFINE_FOREGROUND_LOG_CHANNEL_TEST(single_line, s_channel_test_one_line) DEFINE_FOREGROUND_LOG_CHANNEL_TEST(numbers, s_channel_test_numbers) DEFINE_FOREGROUND_LOG_CHANNEL_TEST(words, s_channel_test_words) DEFINE_FOREGROUND_LOG_CHANNEL_TEST(all, s_channel_test_all) /* * Background channel tests */ DEFINE_BACKGROUND_LOG_CHANNEL_TEST(single_line, s_channel_test_one_line, NULL) DEFINE_BACKGROUND_LOG_CHANNEL_TEST(numbers, s_channel_test_numbers, s_background_sleep_times_ns) DEFINE_BACKGROUND_LOG_CHANNEL_TEST(words, s_channel_test_words, s_background_sleep_times_ns) DEFINE_BACKGROUND_LOG_CHANNEL_TEST(all, s_channel_test_all, s_background_sleep_times_ns)