/* * 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_glance.h" #include "drivers/rtc.h" #include "kernel/pbl_malloc.h" #include "process_management/app_install_manager.h" #include "resource/resource_ids.auto.h" #include "resource/timeline_resource_ids.auto.h" #include "services/normal/app_glances/app_glance_service.h" #include "services/normal/blob_db/app_glance_db.h" #include "services/normal/blob_db/app_glance_db_private.h" #include "services/normal/filesystem/pfs.h" #include "services/normal/timeline/timeline_resources.h" #include "util/uuid.h" #define APP_GLANCE_TEST_UUID \ (UuidMake(0x3d, 0xc6, 0xb9, 0x4c, 0x4, 0x2, 0x48, 0xf4, \ 0xbe, 0x14, 0x81, 0x17, 0xf1, 0xa, 0xa9, 0xc4)) // Fakes //////////////////////////////////////////////////////////////// #include "fake_rtc.h" #include "fake_settings_file.h" void sys_get_app_uuid(Uuid *uuid) { if (uuid) { *uuid = APP_GLANCE_TEST_UUID; } } typedef struct AppGlanceTestState { bool resource_is_valid; void *context; bool reload_callback_was_called; } AppGlanceTestState; static AppGlanceTestState s_test_state; ResAppNum sys_get_current_resource_num(void) { return 0; } void sys_timeline_resources_get_id(const TimelineResourceInfo *timeline_res, TimelineResourceSize size, AppResourceInfo *res_info) { if (!res_info) { return; } // Just fill the output resource ID with some number so it's considered "valid" res_info->res_id = s_test_state.resource_is_valid ? 1337 : 0; } // Stubs //////////////////////////////////////////////////////////////// #include "stubs_app_cache.h" #include "stubs_app_install_manager.h" #include "stubs_app_state.h" #include "stubs_events.h" #include "stubs_event_service_client.h" #include "stubs_i18n.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" status_t pfs_remove(const char *name) { fake_settings_file_reset(); return S_SUCCESS; } // Setup //////////////////////////////////////////////////////////////// void test_app_glance__initialize(void) { fake_rtc_init(0, 1337); fake_settings_file_reset(); app_glance_db_init(); app_glance_service_init(); s_test_state = (AppGlanceTestState) {}; } void app_glance_db_deinit(void); void test_app_glance__cleanup(void) { app_glance_db_deinit(); } void prv_basic_reload_cb(AppGlanceReloadSession *session, size_t limit, void *context) { s_test_state.reload_callback_was_called = true; s_test_state.resource_is_valid = true; AppGlanceSlice slice = (AppGlanceSlice) { .expiration_time = rtc_get_time() + 10, .layout.icon = TIMELINE_RESOURCE_HOTEL_RESERVATION, .layout.subtitle_template_string = "Test subtitle", }; cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS); slice = (AppGlanceSlice) { .expiration_time = rtc_get_time() + 20, .layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON, }; cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS); } void test_app_glance__basic_reload(void) { // Reload the glance with two slices app_glance_reload(prv_basic_reload_cb, s_test_state.context); cl_assert_equal_b(s_test_state.reload_callback_was_called, true); // Read the glance back AppGlance glance = {}; cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &glance), S_SUCCESS); // Compare the glance read back with the expected glance below AppGlance expected_glance = (AppGlance) { .num_slices = 2, .slices = { { .expiration_time = rtc_get_time() + 10, .type = AppGlanceSliceType_IconAndSubtitle, .icon_and_subtitle.icon_resource_id = TIMELINE_RESOURCE_HOTEL_RESERVATION, .icon_and_subtitle.template_string = "Test subtitle", }, { .expiration_time = rtc_get_time() + 20, .type = AppGlanceSliceType_IconAndSubtitle, .icon_and_subtitle.icon_resource_id = APP_GLANCE_SLICE_DEFAULT_ICON, } }, }; cl_assert_equal_m(&glance, &expected_glance, sizeof(AppGlance)); } void prv_reload_with_validation_cb(AppGlanceReloadSession *session, size_t limit, void *context) { s_test_state.reload_callback_was_called = true; // Check that the context here is the context we passed to `app_glance_reload()` cl_assert_equal_p(context, s_test_state.context); // Check that the limit passed in matches the max slices per glance cl_assert_equal_i(limit, APP_GLANCE_DB_MAX_SLICES_PER_GLANCE); unsigned int num_slices_added = 0; AppGlanceSlice slice = {}; // Check that using a bogus session variable fails AppGlanceReloadSession bogus_session; cl_assert(app_glance_add_slice(&bogus_session, slice) & APP_GLANCE_RESULT_INVALID_SESSION); // Check that adding a slice with APP_GLANCE_SLICE_DEFAULT_ICON as the icon succeeds slice = (AppGlanceSlice) { .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION, .layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON, .layout.subtitle_template_string = "Test subtitle {time_until(500)|format('%uS')}", }; cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS); num_slices_added++; // Check that adding a slice with a NULL subtitle succeeds s_test_state.resource_is_valid = true; slice = (AppGlanceSlice) { .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION, .layout.icon = TIMELINE_RESOURCE_BIRTHDAY_EVENT, .layout.subtitle_template_string = NULL, }; cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS); num_slices_added++; // Check that adding a slice with an invalid icon fails s_test_state.resource_is_valid = false; slice = (AppGlanceSlice) { .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION, .layout.icon = RESOURCE_ID_SETTINGS_ICON_AIRPLANE, .layout.subtitle_template_string = "Test subtitle", }; cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_INVALID_ICON); // Check that adding a slice with a subtitle that's too long fails const char *really_long_subtitle = "This is a really really really really really really really " "really really really really really really really really " "really really really really really really really really " "really really really really really really really really " "really long subtitle."; slice = (AppGlanceSlice) { .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION, .layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON, .layout.subtitle_template_string = really_long_subtitle, }; cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG); // Check that adding a slice with a bad template string fails const char *invalid_template_subtitle = "How much time? {time_until(500)|format('%uS',)}"; slice = (AppGlanceSlice) { .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION, .layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON, .layout.subtitle_template_string = invalid_template_subtitle, }; cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_INVALID_TEMPLATE_STRING); // Check that adding a slice that expires in the past fails slice = (AppGlanceSlice) { .expiration_time = rtc_get_time() - 10, .layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON, .layout.subtitle_template_string = "Test subtitle", }; cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST); // At this point we've actually filled up the glance to the capacity cl_assert_equal_i(num_slices_added, limit); // So adding one more slice to the glance should fail s_test_state.resource_is_valid = true; slice = (AppGlanceSlice) { .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION, .layout.subtitle_template_string = NULL, }; cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED); // Check that we can get reports of multiple kinds of failures at the same time s_test_state.resource_is_valid = false; slice = (AppGlanceSlice) { .expiration_time = rtc_get_time() - 10, .layout.icon = RESOURCE_ID_SETTINGS_ICON_AIRPLANE, .layout.subtitle_template_string = really_long_subtitle, }; const AppGlanceResult result = app_glance_add_slice(session, slice); cl_assert(result & APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST); cl_assert(result & APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED); cl_assert(result & APP_GLANCE_RESULT_INVALID_ICON); cl_assert(result & APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG); } void test_app_glance__reload_with_validation_callback(void) { app_glance_reload(prv_reload_with_validation_cb, s_test_state.context); cl_assert_equal_b(s_test_state.reload_callback_was_called, true); } static void prv_glance_clear_test(AppGlanceReloadCallback reload_cb) { // Insert some slices for the glance const AppGlance glance = (AppGlance) { .num_slices = 2, .slices = { { .expiration_time = 1464734504, // (Tue, 31 May 2016 22:41:44 GMT) .type = AppGlanceSliceType_IconAndSubtitle, .icon_and_subtitle = { .template_string = "Test subtitle 2", }, }, { .expiration_time = 1464734484, // (Tue, 31 May 2016 22:41:24 GMT) .type = AppGlanceSliceType_IconAndSubtitle, .icon_and_subtitle = { .template_string = "Test subtitle 1", }, }, }, }; cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, &glance), S_SUCCESS); // Request the current slice for this glance; this should match the earliest-expiring slice in // the glance we just inserted above AppGlanceSliceInternal slice_out; cl_assert_equal_b(app_glance_service_get_current_slice(&APP_GLANCE_TEST_UUID, &slice_out), true); cl_assert_equal_m(&slice_out, &glance.slices[1], sizeof(slice_out)); // Let some time "pass" so that the creation time of this next reload doesn't get ignored fake_rtc_increment_time(10); // Reload the glance using the provided callback; this should empty the slices in the glance app_glance_reload(reload_cb, NULL); // Read the glance back and check that it doesn't have any slices anymore AppGlance glance_read = {}; cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &glance_read), S_SUCCESS); cl_assert_equal_i(glance_read.num_slices, 0); for (unsigned int i = 0; i < sizeof(glance_read.slices); i++) { const uint8_t byte = ((uint8_t *)glance_read.slices)[i]; cl_assert_equal_i(byte, 0); } // Request the current slice for this glance again; this should return false since there aren't // any slices in the glance anymore cl_assert_equal_b(app_glance_service_get_current_slice(&APP_GLANCE_TEST_UUID, &slice_out), false); } void test_app_glance__reload_with_null_callback_empties_slices(void) { prv_glance_clear_test(NULL); } static void prv_reload_with_no_slices_added_cb(AppGlanceReloadSession *session, size_t limit, void *context) { // We don't add any slices in this callback on purpose return; } void test_app_glance__reload_with_no_slices_added_empties_slices(void) { prv_glance_clear_test(prv_reload_with_no_slices_added_cb); }