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

319 lines
12 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_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);
}