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

632 lines
21 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 "services/normal/app_cache.h"
#include "process_management/app_install_manager.h"
#include "resource/resource_storage.h"
#include "process_management/app_install_types.h"
#include "services/normal/filesystem/app_file.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/process_management/app_storage.h"
#include "services/normal/settings/settings_file.h"
#include "shell/normal/quick_launch.h"
#include <util/size.h>
#include "system/logging.h"
#include "util/attributes.h"
#include <stdio.h>
// Fakes
////////////////////////////////////
#include "fake_spi_flash.h"
#include "fake_system_task.h"
#include "fake_events.h"
// Stubs
////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_pbl_malloc.h"
#include "stubs_prompt.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_passert.h"
#include "stubs_task_watchdog.h"
void app_storage_delete_app(AppInstallId id) {
char buffer[30];
itoa_int(id, buffer, 10);
pfs_remove(buffer);
}
bool app_storage_app_exists(AppInstallId id) {
return true;
}
AppInstallId s_test_id_ql_up;
AppInstallId s_test_id_ql_down;
AppInstallId s_test_id_watchface;
AppInstallId s_test_id_worker;
AppInstallId quick_launch_get_app(ButtonId button) {
if (button == BUTTON_ID_UP) {
return s_test_id_ql_up;
} else {
return s_test_id_ql_down;
}
}
AppInstallId watchface_get_default_install_id(void) {
return s_test_id_watchface;
}
AppInstallId worker_preferences_get_default_worker(void) {
return s_test_id_worker;
}
extern AppInstallId app_cache_get_next_eviction(void);
/* Start of test */
typedef struct {
AppInstallId id;
uint32_t size;
uint32_t priority;
} AppData;
static const AppData app1 = {
.id = 1,
.size = 1000,
};
static const AppData app2 = {
.id = 2,
.size = 1000,
};
static const AppData app3 = {
.id = 3,
.size = 1000,
};
void test_app_cache__initialize(void) {
rtc_set_time(1478397600);
fake_spi_flash_init(0, 0x1000000);
fake_event_init();
pfs_init(false);
app_cache_init();
app_cache_flush();
}
void test_app_cache__cleanup(void) {
fake_system_task_callbacks_cleanup();
s_test_id_ql_up = 0;
s_test_id_ql_down = 0;
s_test_id_watchface = 0;
s_test_id_worker = 0;
}
/*************************************
* Add one and evict it *
*************************************/
void test_app_cache__easy_evict(void) {
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app1.id, app1.size));
cl_assert_equal_i(app1.id, app_cache_get_next_eviction());
}
/*************************************
* Add 3, remove 2, evict one *
*************************************/
void test_app_cache__add_remove_evict(void) {
// add all three
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app1.id, app1.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app2.id, app2.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app3.id, app3.size));
// remove 2
cl_assert_equal_i(S_SUCCESS, app_cache_remove_entry(app1.id));
PebbleEvent e = fake_event_get_last();
cl_assert_equal_i(e.type, PEBBLE_APP_CACHE_EVENT);
cl_assert_equal_i(e.app_cache_event.cache_event_type, PebbleAppCacheEvent_Removed);
cl_assert_equal_i(e.app_cache_event.install_id, app1.id);
cl_assert_equal_i(S_SUCCESS, app_cache_remove_entry(app3.id));
e = fake_event_get_last();
cl_assert_equal_i(e.type, PEBBLE_APP_CACHE_EVENT);
cl_assert_equal_i(e.app_cache_event.cache_event_type, PebbleAppCacheEvent_Removed);
cl_assert_equal_i(e.app_cache_event.install_id, app3.id);
cl_assert_equal_i(fake_event_get_count(), 2);
// ensure the only one remaining is the one evicted
cl_assert_equal_i(app2.id, app_cache_get_next_eviction());
cl_assert_equal_i(fake_event_get_count(), 2);
}
/*************************************
* Add 3, update 2, evict 1 *
*************************************/
void test_app_cache__add_update_evict(void) {
// add all three
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app1.id, app1.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app2.id, app2.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app3.id, app3.size));
time_t now = rtc_get_time();
rtc_set_time(now + 2);
// remove 2
cl_assert_equal_i(S_SUCCESS, app_cache_app_launched(app1.id));
cl_assert_equal_i(S_SUCCESS, app_cache_app_launched(app3.id));
// ensure the one that has the lowest priority is evicted
cl_assert_equal_i(app2.id, app_cache_get_next_eviction());
}
/*************************************
* Add 3, remove 3, evict INVALID_ID *
*************************************/
void test_app_cache__add_remove_all_evict(void) {
// add all three
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app1.id, app1.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app2.id, app2.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app3.id, app3.size));
// remove 3
cl_assert_equal_i(S_SUCCESS, app_cache_remove_entry(app1.id));
cl_assert_equal_i(S_SUCCESS, app_cache_remove_entry(app2.id));
cl_assert_equal_i(S_SUCCESS, app_cache_remove_entry(app3.id));
cl_assert_equal_i(fake_event_get_count(), 3);
// ensure the one that is evicted is INVALID_ID
cl_assert_equal_i(INSTALL_ID_INVALID, app_cache_get_next_eviction());
}
/*************************************
* Add lots, update lots, update one a little less *
*************************************/
void test_app_cache__update_all_lots_evict_one(void) {
static const uint8_t DESIRED_EVICT_ID = 5;
static const uint8_t NUM_ITEMS = 10;
static const uint16_t NUM_UPDATES = 10;
for (int i = 1; i <= NUM_ITEMS; i++) {
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(i, 0));
}
// repeat LOOPS times
for (int i = 1; i <= NUM_UPDATES; i++) {
// go through list and launch each app once.
for (int j = 1; j <= NUM_ITEMS; j++) {
if ((i == NUM_UPDATES) && (j == DESIRED_EVICT_ID)) {
continue;
}
cl_assert_equal_i(S_SUCCESS, app_cache_app_launched(j));
}
// increment time so everything won't happen in the same second
time_t now = rtc_get_time();
rtc_set_time(now + 2);
}
// ensure the one that is evicted is INVALID_ID
cl_assert_equal_i(DESIRED_EVICT_ID, app_cache_get_next_eviction());
}
void test_app_cache__clear(void) {
// add all three
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app1.id, app1.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app2.id, app2.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app3.id, app3.size));
app_cache_flush();
cl_assert_equal_b(false, app_cache_entry_exists(app1.id));
cl_assert_equal_b(false, app_cache_entry_exists(app2.id));
cl_assert_equal_b(false, app_cache_entry_exists(app3.id));
}
#define APP_CACHE_FILE_NAME "appcache"
#define APP_CACHE_MAX_SIZE 4000
typedef struct PACKED {
time_t install_date;
time_t last_launch;
uint32_t total_size;
uint16_t launch_count;
} AppCacheEntry;
void test_app_cache__corrupt_key(void) {
// add three cache entries
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app1.id, app1.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app2.id, app2.size));
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(app3.id, app3.size));
// add one with a key value of length 3
// Raw SettingsFile calls
SettingsFile file;
status_t rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
cl_assert_equal_i(S_SUCCESS, rv);
AppCacheEntry entry = {
.install_date = rtc_get_time(),
.last_launch = 0,
.launch_count = 0,
.total_size = 17,
};
int rand_id = 1717;
rv = settings_file_set(&file, (uint8_t *)&rand_id, (sizeof(AppInstallId) - 1),
(uint8_t *)&entry, sizeof(AppCacheEntry));
settings_file_close(&file);
// End Raw SettingsFile calls
// force iteration on the app_cache. This will find the corrupted entry, and delete the app cache
app_cache_free_up_space(1);
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_b(false, app_cache_entry_exists(app1.id));
cl_assert_equal_b(false, app_cache_entry_exists(app2.id));
cl_assert_equal_b(false, app_cache_entry_exists(app3.id));
}
static const uint32_t SIZE_SUM = 7210515;
static const AppData t_data[] = { // higher rank = should keep around
{ .id = 1, .priority = 40, .size = 131798 }, // priority rank 28
{ .id = 2, .priority = 60, .size = 194327 }, // priority rank 48
{ .id = 3, .priority = 23, .size = 195131 }, // priority rank 15
{ .id = 4, .priority = 21, .size = 16438 }, // priority rank 13
{ .id = 5, .priority = 58, .size = 88644 }, // priority rank 45
{ .id = 6, .priority = 57, .size = 269063 }, // priority rank 43
{ .id = 7, .priority = 43, .size = 83456 }, // priority rank 32
{ .id = 8, .priority = 29, .size = 233211 }, // priority rank 20
{ .id = 9, .priority = 38, .size = 55766 }, // priority rank 26
{ .id = 10, .priority = 19, .size = 28359 }, // priority rank 12
{ .id = 11, .priority = 29, .size = 82909 }, // priority rank 21
{ .id = 12, .priority = 53, .size = 132316 }, // priority rank 41
{ .id = 13, .priority = 45, .size = 214356 }, // priority rank 35
{ .id = 14, .priority = 47, .size = 258908 }, // priority rank 36
{ .id = 15, .priority = 19, .size = 117885 }, // priority rank 11
{ .id = 16, .priority = 42, .size = 167427 }, // priority rank 31
{ .id = 17, .priority = 1, .size = 22644 }, // priority rank 2
{ .id = 18, .priority = 30, .size = 33202 }, // priority rank 22
{ .id = 19, .priority = 25, .size = 151434 }, // priority rank 18
{ .id = 20, .priority = 33, .size = 102321 }, // priority rank 24
{ .id = 21, .priority = 19, .size = 223352 }, // priority rank 9
{ .id = 22, .priority = 36, .size = 133221 }, // priority rank 25
{ .id = 23, .priority = 51, .size = 169128 }, // priority rank 39
{ .id = 24, .priority = 22, .size = 103055 }, // priority rank 14
{ .id = 25, .priority = 44, .size = 182304 }, // priority rank 33
{ .id = 26, .priority = 2, .size = 177430 }, // priority rank 3
{ .id = 27, .priority = 5, .size = 248430 }, // priority rank 4
{ .id = 28, .priority = 44, .size = 168622 }, // priority rank 34
{ .id = 29, .priority = 6, .size = 192857 }, // priority rank 5
{ .id = 30, .priority = 19, .size = 183331 }, // priority rank 10
{ .id = 31, .priority = 61, .size = 111155 }, // priority rank 50
{ .id = 32, .priority = 42, .size = 211695 }, // priority rank 30
{ .id = 33, .priority = 49, .size = 35653 }, // priority rank 38
{ .id = 34, .priority = 57, .size = 11541 }, // priority rank 44
{ .id = 35, .priority = 40, .size = 49368 }, // priority rank 29
{ .id = 36, .priority = 25, .size = 230982 }, // priority rank 17
{ .id = 37, .priority = 32, .size = 185018 }, // priority rank 23
{ .id = 38, .priority = 39, .size = 163897 }, // priority rank 27
{ .id = 39, .priority = 24, .size = 233217 }, // priority rank 16
{ .id = 40, .priority = 8, .size = 23717 }, // priority rank 6
{ .id = 41, .priority = 61, .size = 266668 }, // priority rank 49
{ .id = 42, .priority = 58, .size = 61228 }, // priority rank 46
{ .id = 43, .priority = 12, .size = 23513 }, // priority rank 7
{ .id = 44, .priority = 60, .size = 267049 }, // priority rank 47
{ .id = 45, .priority = 52, .size = 240086 }, // priority rank 40
{ .id = 46, .priority = 14, .size = 194481 }, // priority rank 8
{ .id = 47, .priority = 27, .size = 42163 }, // priority rank 19
{ .id = 48, .priority = 56, .size = 72854 }, // priority rank 42
{ .id = 49, .priority = 49, .size = 217548 }, // priority rank 37
{ .id = 50, .priority = 1, .size = 207357 }, // priority rank 1
};
extern uint32_t app_cache_get_size(void);
void prv_load_lotta_apps(void) {
for (int i = 0; i < 50; i++) {
// time is the basis of the priority. Set the time so we know what priority.
rtc_set_time(t_data[i].priority);
cl_assert_equal_i(S_SUCCESS, app_cache_add_entry(t_data[i].id, t_data[i].size));
cl_assert_equal_i(S_SUCCESS, app_cache_app_launched(t_data[i].id));
// increment time so everything won't happen in the same second
}
for (int i = 0; i < 50; i++) {
cl_assert_equal_b(true, app_cache_entry_exists(t_data[i].id));
}
cl_assert_equal_i(SIZE_SUM, app_cache_get_size());
}
void prv_cleanup(void) {
app_cache_flush();
prv_load_lotta_apps();
}
void test_app_cache__free_up_space_lots_apps(void) {
uint32_t to_free;
uint32_t before_size;
uint32_t after_size;
// test random number
prv_cleanup();
to_free = 150000;
before_size = app_cache_get_size();
cl_assert(before_size >= to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert(after_size <= (before_size - to_free));
// test lowest priority's size
prv_cleanup();
to_free = 207357;
before_size = app_cache_get_size();
cl_assert(before_size >= to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert_equal_i(after_size, (before_size - to_free));
// test lowest priority's size
prv_cleanup();
to_free = 1; // should remove 207357
before_size = app_cache_get_size();
cl_assert(before_size >= to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert_equal_i(after_size, (before_size - 207357));
// test two lowest priority's size
prv_cleanup();
to_free = (207357 + 22644);
before_size = app_cache_get_size();
cl_assert(before_size >= to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert_equal_i(after_size, (before_size - to_free));
// test removing all binaries
prv_cleanup();
to_free = SIZE_SUM;
before_size = app_cache_get_size();
cl_assert(before_size >= to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert_equal_i(after_size, 0);
// test removing 0 bytes
prv_cleanup();
to_free = 0;
before_size = app_cache_get_size();
cl_assert(before_size >= to_free);
cl_assert_equal_i(E_INVALID_ARGUMENT, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert_equal_i(after_size, before_size);
// test removing all and more bytes
prv_cleanup();
to_free = SIZE_SUM + 1;
before_size = app_cache_get_size();
cl_assert(before_size < to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert_equal_i(after_size, 0);
}
static bool prv_file_for_id_exists(AppInstallId id) {
char buffer[30];
itoa_int(id, buffer, 10);
int fd = pfs_open(buffer, OP_FLAG_READ, FILE_TYPE_STATIC, 0);
if (fd < 0) {
return false;
}
pfs_close(fd);
return true;
}
static void prv_create_file_for_id(AppInstallId id) {
char buffer[30];
itoa_int(id, buffer, 10);
int fd = pfs_open(buffer, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10);
pfs_close(fd);
}
void test_app_cache__delete_binaries_for_id_with_no_entry(void) {
// confirm binaries get created
prv_create_file_for_id(17);
cl_assert_equal_b(true, prv_file_for_id_exists(17));
// confirm binaries are deleted
app_cache_remove_entry(17);
cl_assert_equal_b(false, prv_file_for_id_exists(17));
}
void test_app_cache__free_up_space_save_defaults(void) {
uint32_t to_free;
uint32_t before_size;
uint32_t after_size;
// lets save ids 17, 25, 42, and 47 because they are my favorite numbers
uint32_t after_free = t_data[16].size + t_data[24].size + t_data[41].size + t_data[46].size;
s_test_id_ql_up = 17;
s_test_id_ql_down = 25;
s_test_id_watchface = 42;
s_test_id_worker = 47;
// test removing all and more bytes
prv_cleanup();
to_free = SIZE_SUM - after_free;
before_size = app_cache_get_size();
PBL_LOG(LOG_LEVEL_DEBUG, "%d %d %d", to_free, after_free, before_size);
cl_assert(before_size >= to_free);
cl_assert_equal_i(S_SUCCESS, app_cache_free_up_space(to_free));
fake_system_task_callbacks_invoke_pending();
after_size = app_cache_get_size();
cl_assert(after_size == (before_size - to_free));
}
struct file_description {
const char *name;
size_t size;
};
static struct file_description descriptions[] = {
// this first set of files match some I found on my snowy bb2
{"gap_bonding_db", 8102},
{"pmap", 5632},
{"pindb", 57095},
{"appdb", 32603},
{"reminderdb", 57090},
{"appcache", 8108},
{"alarms", 8110},
{"notifpref", 8107},
{"activity", 24436},
{"insights", 8108},
{"shellpref", 8107},
{"dls_storage_33", 4096},
{"dls_storage_122", 4096},
{"dls_storage_84", 4096},
{"dls_storage_71", 4096},
{"dls_storage_107", 4096},
{"dls_storage_176", 12555},
{"dls_storage_161", 4096},
{"dls_storage_110", 4096},
{"dls_storage_142", 4096},
{"dls_storage_197", 4096},
{"dls_storage_218", 4096},
{"dls_storage_145", 4096},
{"app_comm", 8108},
{"wakeup", 16274},
{"notifstr", 30720},
{"dls_storage_238", 4096},
{"dls_storage_116", 4096},
{"dls_storage_199", 4096},
// throw in a few almost-but-not-quite look like resource files
{"@0123ABCD/res", 1024},
{"@01234567/ress", 1024},
{"@01234567/re", 1024},
{"!01234567/res", 1024}
};
static void prv_file_create(const char *name, size_t size) {
int fd = pfs_open(name, OP_FLAG_WRITE, FILE_TYPE_STATIC, size);
cl_assert(fd >= 0);
pfs_close(fd);
}
static void prv_app_filename(char *filename, size_t size, int id) {
app_file_name_make(filename, size, id, APP_FILE_NAME_SUFFIX, strlen(APP_FILE_NAME_SUFFIX));
}
static void prv_res_filename(char *filename, size_t size, int id) {
app_file_name_make(filename, size, id, APP_RESOURCES_FILENAME_SUFFIX,
strlen(APP_RESOURCES_FILENAME_SUFFIX));
}
// create app and res files (and add to the app cache)
static void prv_app_files_create(int id) {
char filename[15];
// @xxxxxxxx/app
prv_app_filename(filename, sizeof(filename), id);
prv_file_create(filename, 64738);
// @xxxxxxxx/res
prv_res_filename(filename, sizeof(filename), id);
prv_file_create(filename, 788);
app_cache_add_entry(id, 64738);
}
static void prv_check_file_exists(const char *filename) {
int fd = pfs_open(filename, OP_FLAG_READ, FILE_TYPE_STATIC, 0);
cl_assert(fd >= 0);
pfs_close(fd);
}
void test_app_cache__purge_orphaned_files(void) {
// create some 'standard' files
for (uint32_t i = 0; i < ARRAY_LENGTH(descriptions); ++i) {
prv_file_create(descriptions[i].name, descriptions[i].size);
}
// create some app and res files and list them in the app cache
for (uint32_t i = 0; i < 15; ++i) {
prv_app_files_create((i + 1) * 257);
}
// create some res files that we expect to get purged
prv_file_create("@00000000/res", 1024);
prv_file_create("@00000001/res", 1024);
prv_file_create("@ffffffff/res", 1024);
// re-initialize the app-cache (which should purge the above 3 files)
app_cache_init();
// let's see if the three files we should have deleted are indeed gone
cl_assert(pfs_open("@00000000/res", OP_FLAG_READ, FILE_TYPE_STATIC, 0) < 0);
cl_assert(pfs_open("@00000001/res", OP_FLAG_READ, FILE_TYPE_STATIC, 0) < 0);
cl_assert(pfs_open("@ffffffff/res", OP_FLAG_READ, FILE_TYPE_STATIC, 0) < 0);
// let's make sure the app and res files in the cache weren't deleted
for (uint32_t i = 0; i < 15; ++i) {
char filename[15];
int id = (i + 1) * 257;
prv_app_filename(filename, sizeof(filename), id);
prv_check_file_exists(filename);
prv_res_filename(filename, sizeof(filename), id);
prv_check_file_exists(filename);
}
// finally, make sure the 'standard' files are all there
for (uint32_t i = 0; i < ARRAY_LENGTH(descriptions); ++i) {
prv_check_file_exists(descriptions[i].name);
}
}
void test_app_cache__purge_orphaned_files_no_apps(void) {
// make sure nothing goes awry if there are no installed apps
// create some 'standard' files
for (uint32_t i = 0; i < ARRAY_LENGTH(descriptions); ++i) {
prv_file_create(descriptions[i].name, descriptions[i].size);
}
// re-initialize the app-cache (which will attempt to purge resource files too)
app_cache_init();
// make sure the 'standard' files are all there
for (uint32_t i = 0; i < ARRAY_LENGTH(descriptions); ++i) {
prv_check_file_exists(descriptions[i].name);
}
}