pebble/tests/fw/applib/test_app_inbox.c
2025-01-27 11:38:16 -08:00

599 lines
23 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "clar.h"
#include "applib/app_inbox.h"
#include "kernel/events.h"
#include "services/normal/app_inbox_service.h"
#include "util/list.h"
extern bool app_inbox_service_has_inbox_for_tag(AppInboxServiceTag tag);
extern bool app_inbox_service_has_inbox_for_storage(uint8_t *storage);
extern bool app_inbox_service_is_being_written_for_tag(AppInboxServiceTag tag);
extern size_t app_inbox_service_num_failed_for_tag(AppInboxServiceTag tag);
extern size_t app_inbox_service_num_success_for_tag(AppInboxServiceTag tag);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fakes & Stubs
#include "fake_kernel_malloc.h"
#include "fake_pebble_tasks.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_syscall_internal.h"
#define BUFFER_SIZE (32)
#define NOT_PERMITTED_MSG_HANDLER ((AppInboxMessageHandler)~0)
#define NOT_PERMITTED_DROP_HANDLER ((AppInboxDroppedHandler)~0)
#define TEST_TARGET_TASK (PebbleTask_App)
typedef struct {
ListNode node;
PebbleEvent event;
} EventNode;
static EventNode *s_event_head;
static bool s_can_send_event;
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) {
cl_assert_equal_i(PEBBLE_CALLBACK_EVENT, e->type);
cl_assert_equal_i(task, TEST_TARGET_TASK);
if (s_can_send_event) {
EventNode *node = (EventNode *)malloc(sizeof(EventNode));
*node = (const EventNode) {
.event = *e,
};
s_event_head = (EventNode *)list_prepend((ListNode *)s_event_head, (ListNode *)node);
}
return s_can_send_event;
}
static void prv_process_callback_events_alt(bool should_execute_callback) {
EventNode *node = s_event_head;
while (node) {
EventNode *next = (EventNode *) node->node.next;
if (should_execute_callback) {
node->event.callback.callback(node->event.callback.data);
}
free(node);
node = next;
}
s_event_head = NULL;
}
static void prv_process_callback_events(void) {
prv_process_callback_events_alt(true /* should_execute_callback */);
}
static void prv_cleanup_callback_events(void) {
prv_process_callback_events_alt(false /* should_execute_callback */);
}
#define assert_num_callback_events(num) \
cl_assert_equal_i(list_count((ListNode *)s_event_head), num);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Inbox Service Stubs
void app_message_receiver_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {
}
void app_message_receiver_dropped_handler(uint32_t num_dropped_messages) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test Inbox Service Handlers
#define TEST_ARRAY_SIZE (4)
static int s_message_idx;
static struct {
uint8_t data[BUFFER_SIZE];
size_t length;
} s_messages[TEST_ARRAY_SIZE];
static int s_num_messages_to_consume_from_handler;
void test_message_handler(const uint8_t *data, size_t length, AppInboxConsumerInfo *consumer_info) {
cl_assert(s_message_idx < TEST_ARRAY_SIZE);
s_messages[s_message_idx].length = length;
memcpy(s_messages[s_message_idx].data, data, length);
++s_message_idx;
if (s_num_messages_to_consume_from_handler--) {
app_inbox_consume(consumer_info);
}
}
#define assert_message(idx, dd, ll) \
{ \
cl_assert(idx <= s_message_idx); \
cl_assert_equal_i(ll, s_messages[idx].length); \
cl_assert_equal_m(dd, s_messages[idx].data, ll); \
}
#define assert_num_message_callbacks(num_cbs) \
{ \
cl_assert_equal_i(num_cbs, s_message_idx); \
}
static int s_dropped_idx;
static uint32_t s_dropped_messages[TEST_ARRAY_SIZE];
void test_dropped_handler(uint32_t num_dropped_messages) {
cl_assert(s_dropped_idx < TEST_ARRAY_SIZE);
s_dropped_messages[s_dropped_idx++] = num_dropped_messages;
}
#define assert_dropped(idx, num) \
{ \
cl_assert(idx <= s_dropped_idx); \
cl_assert_equal_i(num, s_dropped_messages[idx]); \
}
#define assert_num_dropped_callbacks(num_cbs) \
{ \
cl_assert_equal_i(num_cbs, s_dropped_idx); \
}
void test_alt_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {
cl_assert(false);
}
void test_alt_dropped_handler(uint32_t num_dropped_messages) {
cl_assert(false);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Tests
void test_app_inbox__initialize(void) {
fake_kernel_malloc_init();
fake_kernel_malloc_enable_stats(true);
stub_pebble_tasks_set_current(TEST_TARGET_TASK);
s_num_messages_to_consume_from_handler = 0;
s_can_send_event = true;
s_dropped_idx = 0;
memset(s_dropped_messages, 0, sizeof(s_dropped_messages));
s_message_idx = 0;
memset(s_messages, 0, sizeof(s_messages));
}
void test_app_inbox__cleanup(void) {
app_inbox_service_unregister_all();
fake_kernel_malloc_deinit();
prv_cleanup_callback_events();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_create_and_register
void test_app_inbox__app_inbox_create_and_register_zero_buffer_size(void) {
void *result = app_inbox_create_and_register(0, 1, test_message_handler, test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_zero_min_num_messages(void) {
void *result = app_inbox_create_and_register(BUFFER_SIZE, 0,
test_message_handler, test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_null_message_handler(void) {
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1, NULL, test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_oom(void) {
// FIXME: No support for OOM simulation in applib_.. stub/fake
return;
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler,
test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_msg_handler_not_permitted(void) {
// The syscall_failed() fake will trigger passert:
cl_assert_passert(app_inbox_create_and_register(BUFFER_SIZE, 1,
NOT_PERMITTED_MSG_HANDLER,
test_dropped_handler));
}
void test_app_inbox__app_inbox_create_and_register_drop_handler_not_permitted(void) {
// The syscall_failed() fake will trigger passert:
cl_assert_passert(app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler,
NOT_PERMITTED_DROP_HANDLER));
}
void test_app_inbox__app_inbox_create_and_register_happy_case(void) {
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler, test_dropped_handler);
cl_assert(result != NULL);
cl_assert_equal_b(true, app_inbox_service_has_inbox_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_create_and_register_kernel_oom(void) {
fake_kernel_malloc_set_largest_free_block(0);
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler,
test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_register
void test_app_inbox__app_inbox_create_and_register_storage_already_associated(void) {
bool success;
uint8_t storage[BUFFER_SIZE];
success = app_inbox_service_register(storage, sizeof(storage),
test_message_handler, test_dropped_handler,
AppInboxServiceTagUnitTest);
cl_assert_equal_b(success, true);
cl_assert_equal_b(true, app_inbox_service_has_inbox_for_storage(storage));
cl_assert_equal_b(true, app_inbox_service_has_inbox_for_tag(AppInboxServiceTagUnitTest));
fake_kernel_malloc_mark();
success = app_inbox_service_register(storage, sizeof(storage),
test_alt_message_handler, test_alt_dropped_handler,
AppInboxServiceTagUnitTestAlt);
cl_assert_equal_b(success, false);
cl_assert_equal_b(false, app_inbox_service_has_inbox_for_tag(AppInboxServiceTagUnitTestAlt));
fake_kernel_malloc_mark_assert_equal();
}
void test_app_inbox__app_inbox_create_and_register_tag_already_associated(void) {
bool success;
uint8_t storage[BUFFER_SIZE];
success = app_inbox_service_register(storage, sizeof(storage),
test_message_handler, test_dropped_handler,
AppInboxServiceTagUnitTest);
cl_assert_equal_b(success, true);
fake_kernel_malloc_mark();
uint8_t storage_alt[BUFFER_SIZE];
success = app_inbox_service_register(storage_alt, sizeof(storage_alt),
test_alt_message_handler, test_alt_dropped_handler,
AppInboxServiceTagUnitTest /* same tag! */);
cl_assert_equal_b(success, false);
cl_assert_equal_b(false, app_inbox_service_has_inbox_for_storage(storage_alt));
fake_kernel_malloc_mark_assert_equal();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_begin
static void *s_writer = (void *) 0xaabbccdd;
static void *s_inbox;
static void prv_create_test_inbox(void) {
s_inbox = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler, test_dropped_handler);
cl_assert(s_inbox != NULL);
}
void test_app_inbox__app_inbox_service_begin_null_writer(void) {
prv_create_test_inbox();
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, NULL));
cl_assert_equal_b(false, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_begin_no_inbox(void) {
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(false, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_begin_already_being_written(void) {
prv_create_test_inbox();
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
// Call ...begin() again:
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(1, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_begin_not_enough_storage_space(void) {
prv_create_test_inbox();
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE + 1, s_writer));
cl_assert_equal_b(false, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(1, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
// Drop should be reported immediately (not after the next write finishes):
prv_process_callback_events();
assert_dropped(0, 1);
}
void test_app_inbox__app_inbox_service_begin_happy_case(void) {
prv_create_test_inbox();
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_write / app_inbox_service_end
static const uint8_t s_test_data[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09,
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19
};
static void prv_create_test_inbox_and_begin_write(void) {
prv_create_test_inbox();
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
}
void test_app_inbox__app_inbox_service_write_inbox_closed_in_mean_time(void) {
prv_create_test_inbox_and_begin_write();
app_inbox_destroy_and_deregister(s_inbox);
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE));
}
void test_app_inbox__app_inbox_service_write_not_enough_space(void) {
prv_create_test_inbox_and_begin_write();
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE + 1));
// A continuation should also fail, even though there is enough space for it:
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, 1));
// After ending the write, expect num_failed to be incremented by one:
app_inbox_service_end(AppInboxServiceTagUnitTest);
cl_assert_equal_i(1, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(0, app_inbox_service_num_success_for_tag(AppInboxServiceTagUnitTest));
prv_process_callback_events();
assert_num_dropped_callbacks(1);
assert_num_message_callbacks(0);
}
void test_app_inbox__app_inbox_service_write_happy_case(void) {
prv_create_test_inbox_and_begin_write();
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE));
// After ending the write, expect num_success to be incremented by one:
app_inbox_service_end(AppInboxServiceTagUnitTest);
cl_assert_equal_i(1, app_inbox_service_num_success_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(0, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
prv_process_callback_events();
assert_message(0, s_test_data, BUFFER_SIZE);
assert_num_message_callbacks(1);
assert_num_dropped_callbacks(0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_cancel
void test_app_inbox__app_inbox_service_cancel(void) {
prv_create_test_inbox_and_begin_write();
// Start writing a message that occupies the complete buffer, then cancel it:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE));
app_inbox_service_cancel(AppInboxServiceTagUnitTest);
// No events expected:
assert_num_callback_events(0);
// The buffer should be completely available again:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
}
void test_app_inbox__app_inbox_service_cancel_non_existing_inbox(void) {
app_inbox_service_cancel(AppInboxServiceTagUnitTest);
// No events expected:
assert_num_callback_events(0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Consuming writes
void test_app_inbox__multiple_writes_while_consuming(void) {
prv_create_test_inbox_and_begin_write();
// Message 1:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Message 2:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest, 1, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// No space:
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE + 1, s_writer));
// Shouldn't call ..._end() here because ..._begin() failed.
// Message 3:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest, 1, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
// ... still writing when event gets processed below
// Only one callback event scheduled:
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_callback_events(0);
// Expect 2 message callbacks and 1 drop callback:
assert_num_message_callbacks(2);
assert_message(0, s_test_data, 1);
assert_message(1, s_test_data, 1);
assert_num_dropped_callbacks(1);
assert_dropped(0, 1);
// Finish message 3, should be able to write (BUFFER_SIZE - 1) again,
// because the message 1 and 2 are consumed now:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data + 1,
BUFFER_SIZE - 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// One callback event scheduled:
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_callback_events(0);
// Expect 3rd message callbacks and still 1 drop callback (same as before):
assert_num_message_callbacks(3);
assert_message(2, s_test_data, BUFFER_SIZE);
assert_num_dropped_callbacks(1);
}
void test_app_inbox__multiple_writes_consume_from_message_handler(void) {
prv_create_test_inbox_and_begin_write();
// Message 1:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Message 2:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest, 1, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Only one callback event scheduled:
assert_num_callback_events(1);
s_num_messages_to_consume_from_handler = 1;
prv_process_callback_events();
assert_num_callback_events(0);
// Expect 2 message callbacks and 1 drop callback:
assert_num_message_callbacks(2);
assert_message(0, s_test_data, 1);
assert_message(1, s_test_data, 1);
// Should be able to write (BUFFER_SIZE) again,
// because the message 1 and 2 are consumed now:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data,
BUFFER_SIZE));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// One callback event scheduled:
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_callback_events(0);
}
void test_app_inbox__consume_inbox_closed_in_mean_time(void) {
prv_create_test_inbox_and_begin_write();
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
cl_assert_equal_i(1, app_inbox_destroy_and_deregister(s_inbox));
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_dropped_callbacks(0);
assert_num_message_callbacks(0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_destroy_and_deregister / app_inbox_service_unregister_by_storage
void test_app_inbox__app_inbox_destroy_and_deregister_cleans_up_kernel_heap(void) {
fake_kernel_malloc_mark();
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler, test_dropped_handler);
cl_assert_equal_i(app_inbox_destroy_and_deregister(result), 0);
fake_kernel_malloc_mark_assert_equal();
}
void test_app_inbox__app_inbox_destroy_and_deregister_cleans_up_app_heap(void) {
// TODO: No allocation tracking ability in applib_... stub/fake :(
}
void test_app_inbox__app_inbox_service_end_inbox_closed_in_mean_time(void) {
prv_create_test_inbox_and_begin_write();
// Expect to return 1, because one message is being dropped, the currently written one:
cl_assert_equal_i(1, app_inbox_destroy_and_deregister(s_inbox));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_end_inbox_closed_in_mean_time_with_pending_success(void) {
prv_create_test_inbox_and_begin_write();
// One message:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Begin another one:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
1, s_writer));
// Expect to return 2, because two messages are being dropped, the successful one that was not
// yet processed and the currently written one:
cl_assert_equal_i(2, app_inbox_destroy_and_deregister(s_inbox));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_end_inbox_closed_in_mean_time_with_pending_failure(void) {
prv_create_test_inbox_and_begin_write();
// One message, too large, so it should get dropped:
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE + 1));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Begin another one:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
1, s_writer));
// Expect to return 2, because two messages are being dropped, the failed one that was not
// yet processed and the currently written one:
cl_assert_equal_i(2, app_inbox_destroy_and_deregister(s_inbox));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_unregister_by_storage_unknown_storage(void) {
uint8_t storage[BUFFER_SIZE];
cl_assert_equal_i(app_inbox_service_unregister_by_storage(storage), 0);
}