/* * 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 #include "services/normal/wakeup.h" #include "syscall/syscall.h" #include "flash_region/flash_region.h" #include "services/normal/filesystem/pfs.h" #include "services/normal/settings/settings_file.h" #include "services/common/event_service.h" #include "process_management/app_install_manager.h" #include "clar.h" // Fakes ////////////////////////////////////////////////////////// #include "fake_app_manager.h" #include "fake_rtc.h" #include "fake_new_timer.h" #include "fake_pbl_malloc.h" #include "fake_spi_flash.h" #include "fake_system_task.h" #include "fake_time.h" #include "stubs_analytics.h" #include "stubs_events.h" #include "stubs_language_ui.h" #include "stubs_logging.h" #include "stubs_print.h" #include "stubs_prompt.h" #include "stubs_serial.h" #include "stubs_passert.h" #include "stubs_pebble_process_md.h" #include "stubs_rand_ptr.h" #include "stubs_sleep.h" #include "stubs_mutex.h" #include "stubs_hexdump.h" #include "stubs_task_watchdog.h" #include "stubs_compiled_with_legacy2_sdk.h" #include "stubs_memory_layout.h" #define TEST_UUID UuidMake(0xF9, 0xC6, 0xEB, 0xE4, 0x06, 0xCD, 0x46, 0xF1, 0xB1, 0x51, 0x24, 0x08, 0x74, 0xD2, 0x07, 0x73) // Stubs //////////////////////////////////// //int g_pbl_log_level = 0; //void pbl_log(uint8_t level, const char* src_filename, int src_line_number, const char* fmt, ...) {} int time_util_get_num_hours(int hours, bool is24h) {return 0;} bool sys_clock_is_24h_style(void) {return false;} void event_service_init(PebbleEventType type, EventServiceAddSubscriberCallback start_cb, EventServiceRemoveSubscriberCallback stop_cb) {} static bool s_popup_occurred; void wakeup_popup_window(uint8_t missed_apps_count, uint8_t *missed_apps_banks) { s_popup_occurred = true; } static PebbleProcessMd s_test_app_md = { .uuid = TEST_UUID }; bool clock_is_timezone_set(void) { return false; } // Tests /////////////////////////////////////////////////////////// void test_wakeup__initialize(void) { // Wednesday (the 1st) at 00:00 // date -d "2014/01/01 00:00:00" "+%s" ==> 1388563200 fake_rtc_init(0, 1388563200); // Init fake filesystem used to load/store wakeup events fake_spi_flash_init(0, 0x1000000); //from test_settings_file.c pfs_init(false); stub_pebble_tasks_set_current(PebbleTask_KernelBackground); // Reset variable due to previous callbacks s_popup_occurred = false; wakeup_init(); wakeup_enable(true); } void test_wakeup__cleanup(void) {} void test_wakeup__basic_checks(void) { WakeupId wakeup_id = 0; cl_assert_equal_i(sys_get_time(), 1388563200); sys_wakeup_cancel_all_for_app(); // Schedule a wakeup in 10 seconds wakeup_id = sys_wakeup_schedule(sys_get_time() + 10, 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), sys_get_time() + 10); // Cancel wakeup event sys_wakeup_delete(wakeup_id); cl_assert_equal_i(sys_wakeup_query(wakeup_id), E_DOES_NOT_EXIST); // Schedule again wakeup_id = sys_wakeup_schedule(sys_get_time() + 10, 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), sys_get_time() + 10); // Cancel all wakeup events sys_wakeup_cancel_all_for_app(); cl_assert_equal_i(sys_wakeup_query(wakeup_id), E_DOES_NOT_EXIST); } void test_wakeup__max_events(void) { WakeupId wakeup_id = 0; sys_wakeup_cancel_all_for_app(); // Schedule 8 (max), at 1 minute offsets, then fail on 9th for (int i = 1; i <= MAX_WAKEUP_EVENTS_PER_APP; i++) { wakeup_id = sys_wakeup_schedule(sys_get_time() + (i * WAKEUP_EVENT_WINDOW), 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), sys_get_time() + (i * WAKEUP_EVENT_WINDOW)); } // Test that the 9th wakeup event fails to schedule (E_DOES_NOT_EXIST) wakeup_id = sys_wakeup_schedule(sys_get_time() + ((MAX_WAKEUP_EVENTS_PER_APP + 1) * WAKEUP_EVENT_WINDOW), 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), E_DOES_NOT_EXIST); } void test_wakeup__gap(void) { WakeupId wakeup_id = 0; sys_wakeup_cancel_all_for_app(); // Schedule 1 event in a minute wakeup_id = sys_wakeup_schedule(sys_get_time() + WAKEUP_EVENT_WINDOW, 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), sys_get_time() + WAKEUP_EVENT_WINDOW); // Test that another event < 1 minute away fails to schedule (E_DOES_NOT_EXIST) wakeup_id = sys_wakeup_schedule(sys_get_time() + WAKEUP_EVENT_WINDOW + 59, 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), E_DOES_NOT_EXIST); // Test that another event < 1 minute away fails to schedule (E_DOES_NOT_EXIST) wakeup_id = sys_wakeup_schedule(sys_get_time() + 1, 0, false); cl_assert_equal_i(sys_wakeup_query(wakeup_id), E_DOES_NOT_EXIST); } // work around system_task_add_callback extern void wakeup_dispatcher_system_task(void *data); void test_wakeup__out_of_order_schedule(void) { const time_t start_time = sys_get_time(); sys_wakeup_cancel_all_for_app(); // Schedule a wakeup for 10 windows into the future time_t late_event = start_time + WAKEUP_EVENT_WINDOW * 10; WakeupId late_wakeup_id = sys_wakeup_schedule(late_event, 0, false); cl_assert_equal_i(sys_wakeup_query(late_wakeup_id), late_event); // Schedule a wakeup for 5 windows into the future time_t early_event = start_time + WAKEUP_EVENT_WINDOW * 5; WakeupId early_wakeup_id = sys_wakeup_schedule(early_event, 0, false); cl_assert_equal_i(sys_wakeup_query(early_wakeup_id), early_event); cl_assert_equal_i(early_wakeup_id, wakeup_get_next_scheduled()); // Set time 5 minutes into the future, early_event should fire rtc_set_time(early_event); // Force wakeup to check for current wakeup event. wakeup_enable(false); wakeup_enable(true); // Simulate the firing of the early event stub_new_timer_fire(wakeup_get_current()); wakeup_dispatcher_system_task((void *)(uintptr_t)early_wakeup_id); // Make sure early_wakeup_id not scheduled cl_assert_equal_i(sys_wakeup_query(early_wakeup_id), E_DOES_NOT_EXIST); cl_assert_equal_i(sys_wakeup_query(late_wakeup_id), late_event); // Make sure that the next scheduled timer is now the late wakeup id. cl_assert_equal_i(late_wakeup_id, wakeup_get_next_scheduled()); // Set time 10 minutes into the future, late_event should fire rtc_set_time(late_event); // Force wakeup to check for current wakeup event. wakeup_enable(false); wakeup_enable(true); // Simulate the firing of the late event stub_new_timer_fire(wakeup_get_current()); wakeup_dispatcher_system_task((void *)(uintptr_t)late_wakeup_id); // There should now be no scheduled wakeups cl_assert_equal_i(sys_wakeup_query(late_wakeup_id), E_DOES_NOT_EXIST); } void test_wakeup__time_jump(void) { sys_wakeup_cancel_all_for_app(); // Schedule 1 event in a minute time_t first_event = sys_get_time() + WAKEUP_EVENT_WINDOW; WakeupId first_wakeup_id = sys_wakeup_schedule(first_event, 0, false); cl_assert_equal_i(sys_wakeup_query(first_wakeup_id), first_event); TimerID first_timer = wakeup_get_current(); // Schedule another a minute away time_t second_event = sys_get_time() + WAKEUP_EVENT_WINDOW * 2; WakeupId second_wakeup_id = sys_wakeup_schedule(second_event, 0, false); cl_assert_equal_i(sys_wakeup_query(second_wakeup_id), second_event); TimerID test_timer = wakeup_get_current(); // Wakeup should still return the first event as scheduled cl_assert_equal_i(first_timer, test_timer); // Schedule another in the future time_t third_event = sys_get_time() + WAKEUP_EVENT_WINDOW * 3; WakeupId third_wakeup_id = sys_wakeup_schedule(third_event, 0, false); cl_assert_equal_i(sys_wakeup_query(third_wakeup_id), third_event); // Schedule another in the future time_t fourth_event = sys_get_time() + WAKEUP_EVENT_WINDOW * 4; WakeupId fourth_wakeup_id = sys_wakeup_schedule(fourth_event, 0, false); cl_assert_equal_i(sys_wakeup_query(fourth_wakeup_id), fourth_event); // Jump to the future right before the 3rd event rtc_set_time(sys_get_time() + 170); // Force wakeup to check for current wakeup event wakeup_enable(false); wakeup_enable(true); // fire the first wakeup event, as it is still current stub_new_timer_fire(wakeup_get_current()); wakeup_dispatcher_system_task((void *)(uintptr_t)first_wakeup_id); // The current timer should be the second event, even though it is in the past, and should // have a WAKEUP_CATCHUP_WINDOW second gap scheduled TimerID gap_timer = wakeup_get_current(); cl_assert_equal_i(stub_new_timer_timeout(gap_timer) / 1000, WAKEUP_CATCHUP_WINDOW); stub_new_timer_fire(wakeup_get_current()); wakeup_dispatcher_system_task((void *)(uintptr_t)second_wakeup_id); // The current timer should be the third event, with a WAKEUP_CATCHUP_WINDOW second gap again (catchup) gap_timer = wakeup_get_current(); cl_assert_equal_i(stub_new_timer_timeout(gap_timer) / 1000, WAKEUP_CATCHUP_WINDOW); rtc_set_time(third_event); // manually move time forward to after third event stub_new_timer_fire(wakeup_get_current()); wakeup_dispatcher_system_task((void *)(uintptr_t)third_wakeup_id); // Catchup should be finished, gap should be back to >= WAKEUP_CATCHUP_WINDOW seconds gap_timer = wakeup_get_current(); cl_assert_equal_b((stub_new_timer_timeout(gap_timer) / 1000) > WAKEUP_CATCHUP_WINDOW, true); } void test_wakeup__handle_clock_change_not_scheduled(void) { // Test clock change without wakeup event scheduled wakeup_handle_clock_change(); // Make sure no wakeup event is scheduled cl_assert_equal_i(sys_wakeup_query(wakeup_get_next_scheduled()), E_DOES_NOT_EXIST); // There should be no wakeup event missed or popup displayed cl_assert_equal_b(s_popup_occurred, false); } void test_wakeup__handle_clock_change_scheduled_jump(void) { // Schedule event timer 1 minute away with notifying on missed event time_t first_event = sys_get_time() + WAKEUP_EVENT_WINDOW; WakeupId first_wakeup_id = sys_wakeup_schedule(first_event, 0, true); cl_assert_equal_i(sys_wakeup_query(first_wakeup_id), first_event); TimerID first_timer = wakeup_get_current(); // Jump 30 seconds in the future uint32_t initial_timeout = stub_new_timer_timeout(first_timer); uint32_t time_jump_seconds = 30; rtc_set_time(sys_get_time() + time_jump_seconds); // Notify clock change and record change in new timer wakeup_handle_clock_change(); uint32_t final_timeout = stub_new_timer_timeout(first_timer); // Compare to expected value for new timer cl_assert_equal_i(final_timeout, initial_timeout - time_jump_seconds * 1000); // There should be no wakeup event missed or popup displayed cl_assert_equal_b(s_popup_occurred, false); // Jump the remainder plus an offset (missing the event) rtc_set_time(sys_get_time() + final_timeout / 1000 + time_jump_seconds); wakeup_handle_clock_change(); // There should be a missed wakeup event and a popup displayed cl_assert_equal_b(s_popup_occurred, true); // Make sure the wakeup event is no longer scheduled cl_assert_equal_i(sys_wakeup_query(first_timer), E_DOES_NOT_EXIST); }