diff --git a/components/esp_timer/test_apps/esp_timer_tests/main/test_esp_timer.c b/components/esp_timer/test_apps/esp_timer_tests/main/test_esp_timer.c index bc1bf7b0fd8..b5e979d67f0 100644 --- a/components/esp_timer/test_apps/esp_timer_tests/main/test_esp_timer.c +++ b/components/esp_timer/test_apps/esp_timer_tests/main/test_esp_timer.c @@ -1692,3 +1692,284 @@ TEST_CASE("esp_timer_stop_blocking from timer callback and does not block", "[es vSemaphoreDelete(state.callback_can_finish); vSemaphoreDelete(state.callback_finished); } + +typedef struct { + volatile int fire_count; + volatile int64_t fire_time_us; +} timer_observation_t; + +static void record_timer_fire(void* arg) +{ + timer_observation_t* observation = (timer_observation_t*) arg; + observation->fire_time_us = esp_timer_get_time(); + observation->fire_count++; +} + +static bool time_is_in_range(uint64_t time_us, uint64_t earliest_us, uint64_t latest_us) +{ + return time_us >= earliest_us && time_us <= latest_us; +} + +static bool time_is_within(int64_t actual_us, int64_t expected_us, int64_t tolerance_us) +{ + return actual_us >= expected_us - tolerance_us && actual_us <= expected_us + tolerance_us; +} + +TEST_CASE("esp_timer_get_period reports period and rejects invalid arguments", "[esp_timer]") +{ + esp_timer_handle_t timer; + const esp_timer_create_args_t args = { + .callback = &dummy_cb, + .name = "period", + }; + TEST_ESP_OK(esp_timer_create(&args, &timer)); + + uint64_t period_us = UINT64_MAX; + TEST_ESP_OK(esp_timer_start_once(timer, 400 * 1000)); + TEST_ESP_OK(esp_timer_get_period(timer, &period_us)); + TEST_ASSERT_TRUE(period_us == 0); + TEST_ESP_OK(esp_timer_stop(timer)); + + const uint64_t expected_period_us = 250 * 1000; + TEST_ESP_OK(esp_timer_start_periodic(timer, expected_period_us)); + TEST_ESP_OK(esp_timer_get_period(timer, &period_us)); + TEST_ASSERT_TRUE(period_us == expected_period_us); + TEST_ESP_OK(esp_timer_stop(timer)); + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_get_period(NULL, &period_us)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_get_period(timer, NULL)); + + TEST_ESP_OK(esp_timer_delete(timer)); + vTaskDelay(3); +} + +TEST_CASE("esp_timer_get_expiry_time returns the one-shot deadline and validates arguments", "[esp_timer]") +{ + esp_timer_handle_t timer; + const esp_timer_create_args_t args = { + .callback = &dummy_cb, + .name = "expiry", + }; + TEST_ESP_OK(esp_timer_create(&args, &timer)); + + const uint64_t timeout_us = 500 * 1000; + int64_t before_start_us = esp_timer_get_time(); + TEST_ESP_OK(esp_timer_start_once(timer, timeout_us)); + int64_t after_start_us = esp_timer_get_time(); + uint64_t expiry_us = 0; + TEST_ESP_OK(esp_timer_get_expiry_time(timer, &expiry_us)); + TEST_ASSERT_TRUE(time_is_in_range(expiry_us, + before_start_us + timeout_us, + after_start_us + timeout_us)); + TEST_ESP_OK(esp_timer_stop(timer)); + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_get_expiry_time(NULL, &expiry_us)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_get_expiry_time(timer, NULL)); + + TEST_ESP_OK(esp_timer_start_periodic(timer, 200 * 1000)); + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, esp_timer_get_expiry_time(timer, &expiry_us)); + TEST_ESP_OK(esp_timer_stop(timer)); + + TEST_ESP_OK(esp_timer_delete(timer)); + vTaskDelay(3); +} + +TEST_CASE("absolute one-shot fires at its deadline and start APIs reject past deadlines", "[esp_timer]") +{ + const uint64_t deadline_delay_us = 300 * 1000; + const uint64_t expired_deadline_us = 1; + volatile int callback_count = 0; + esp_timer_handle_t timer; + const esp_timer_create_args_t args = { + .callback = &test_timer_triggered, + .arg = (void*) &callback_count, + .name = "once_at", + }; + TEST_ESP_OK(esp_timer_create(&args, &timer)); + + uint64_t deadline_us = esp_timer_get_time() + deadline_delay_us; + TEST_ESP_OK(esp_timer_start_once_at(timer, deadline_us)); + + vTaskDelay(pdMS_TO_TICKS(80)); + TEST_ASSERT_EQUAL(0, callback_count); + vTaskDelay(pdMS_TO_TICKS(400)); + TEST_ASSERT_EQUAL(1, callback_count); + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_start_once_at(timer, expired_deadline_us)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_start_periodic_at(timer, 100 * 1000, expired_deadline_us)); + + TEST_ESP_OK(esp_timer_delete(timer)); + vTaskDelay(3); +} + +TEST_CASE("esp_timer_restart_at reschedules a one-shot and rejects a past deadline", "[esp_timer]") +{ + const uint64_t initial_timeout_us = 2 * 1000 * 1000; + const uint64_t deadline_delay_us = 300 * 1000; + const uint64_t expired_deadline_us = 1; + volatile int callback_count = 0; + esp_timer_handle_t timer; + const esp_timer_create_args_t args = { + .callback = &test_timer_triggered, + .arg = (void*) &callback_count, + .name = "restart_at", + }; + TEST_ESP_OK(esp_timer_create(&args, &timer)); + + TEST_ESP_OK(esp_timer_start_once(timer, initial_timeout_us)); + uint64_t deadline_us = esp_timer_get_time() + deadline_delay_us; + TEST_ESP_OK(esp_timer_restart_at(timer, 0, deadline_us)); + + vTaskDelay(pdMS_TO_TICKS(80)); + TEST_ASSERT_EQUAL(0, callback_count); + vTaskDelay(pdMS_TO_TICKS(400)); + TEST_ASSERT_EQUAL(1, callback_count); + + TEST_ESP_OK(esp_timer_start_once(timer, initial_timeout_us)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_timer_restart_at(timer, 0, expired_deadline_us)); + TEST_ESP_OK(esp_timer_stop(timer)); + + TEST_ESP_OK(esp_timer_delete(timer)); + vTaskDelay(3); +} + +TEST_CASE("wake-up alarm skips timers configured to skip unhandled events", "[esp_timer]") +{ + const uint64_t short_delay_us = 200 * 1000; + const uint64_t long_delay_us = 800 * 1000; + const int64_t alarm_tolerance_us = 80 * 1000; + esp_timer_handle_t non_wake_timer; + esp_timer_handle_t wake_timer; + const esp_timer_create_args_t non_wake_args = { + .callback = &dummy_cb, + .name = "non_wake", + .skip_unhandled_events = true, + }; + const esp_timer_create_args_t wake_args = { + .callback = &dummy_cb, + .name = "wake", + }; + TEST_ESP_OK(esp_timer_create(&non_wake_args, &non_wake_timer)); + TEST_ESP_OK(esp_timer_create(&wake_args, &wake_timer)); + + int64_t start_time_us = esp_timer_get_time(); + TEST_ESP_OK(esp_timer_start_periodic(non_wake_timer, short_delay_us)); + TEST_ESP_OK(esp_timer_start_once(wake_timer, long_delay_us)); + TEST_ASSERT_TRUE(time_is_within(esp_timer_get_next_alarm_for_wake_up(), + start_time_us + long_delay_us, + alarm_tolerance_us)); + + TEST_ESP_OK(esp_timer_stop(non_wake_timer)); + TEST_ESP_OK(esp_timer_stop(wake_timer)); + + start_time_us = esp_timer_get_time(); + TEST_ESP_OK(esp_timer_start_once(wake_timer, short_delay_us)); + TEST_ESP_OK(esp_timer_start_periodic(non_wake_timer, long_delay_us)); + TEST_ASSERT_TRUE(time_is_within(esp_timer_get_next_alarm_for_wake_up(), + start_time_us + short_delay_us, + alarm_tolerance_us)); + + TEST_ESP_OK(esp_timer_stop(non_wake_timer)); + TEST_ESP_OK(esp_timer_stop(wake_timer)); + TEST_ESP_OK(esp_timer_delete(non_wake_timer)); + TEST_ESP_OK(esp_timer_delete(wake_timer)); + vTaskDelay(3); +} + +TEST_CASE("stopping the earliest timer rearms the next timer", "[esp_timer]") +{ + const int64_t stopped_timer_delay_us = 300 * 1000; + const int64_t remaining_timer_delay_us = 900 * 1000; + const int64_t fire_time_tolerance_us = 150 * 1000; + timer_observation_t stopped = { 0 }; + timer_observation_t remaining = { 0 }; + esp_timer_handle_t stopped_timer; + esp_timer_handle_t remaining_timer; + const esp_timer_create_args_t stopped_args = { + .callback = &record_timer_fire, + .arg = &stopped, + .name = "stopped_early", + }; + const esp_timer_create_args_t remaining_args = { + .callback = &record_timer_fire, + .arg = &remaining, + .name = "remaining_late", + }; + TEST_ESP_OK(esp_timer_create(&stopped_args, &stopped_timer)); + TEST_ESP_OK(esp_timer_create(&remaining_args, &remaining_timer)); + + int64_t start_time_us = esp_timer_get_time(); + TEST_ESP_OK(esp_timer_start_once(stopped_timer, stopped_timer_delay_us)); + TEST_ESP_OK(esp_timer_start_once(remaining_timer, remaining_timer_delay_us)); +#if !CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + uint64_t alarm_before_stop = esp_timer_impl_get_alarm_reg(); +#endif + TEST_ESP_OK(esp_timer_stop(stopped_timer)); +#if !CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + TEST_ASSERT_TRUE(alarm_before_stop != esp_timer_impl_get_alarm_reg()); +#endif + + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ASSERT_EQUAL(0, stopped.fire_count); + TEST_ASSERT_EQUAL(0, remaining.fire_count); + + vTaskDelay(pdMS_TO_TICKS(600)); + TEST_ASSERT_EQUAL(0, stopped.fire_count); + TEST_ASSERT_EQUAL(1, remaining.fire_count); + TEST_ASSERT_TRUE(time_is_within(remaining.fire_time_us, + start_time_us + remaining_timer_delay_us, + fire_time_tolerance_us)); + + TEST_ESP_OK(esp_timer_delete(stopped_timer)); + TEST_ESP_OK(esp_timer_delete(remaining_timer)); + vTaskDelay(3); +} + +TEST_CASE("stopping a later timer keeps the earliest timer armed", "[esp_timer]") +{ + const int64_t remaining_timer_delay_us = 300 * 1000; + const int64_t stopped_timer_delay_us = 900 * 1000; + const int64_t fire_time_tolerance_us = 150 * 1000; + timer_observation_t remaining = { 0 }; + timer_observation_t stopped = { 0 }; + esp_timer_handle_t remaining_timer; + esp_timer_handle_t stopped_timer; + const esp_timer_create_args_t remaining_args = { + .callback = &record_timer_fire, + .arg = &remaining, + .name = "remaining_early", + }; + const esp_timer_create_args_t stopped_args = { + .callback = &record_timer_fire, + .arg = &stopped, + .name = "stopped_late", + }; + TEST_ESP_OK(esp_timer_create(&remaining_args, &remaining_timer)); + TEST_ESP_OK(esp_timer_create(&stopped_args, &stopped_timer)); + + int64_t start_time_us = esp_timer_get_time(); + TEST_ESP_OK(esp_timer_start_once(remaining_timer, remaining_timer_delay_us)); + TEST_ESP_OK(esp_timer_start_once(stopped_timer, stopped_timer_delay_us)); +#if !CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + uint64_t alarm_before_stop = esp_timer_impl_get_alarm_reg(); +#endif + TEST_ESP_OK(esp_timer_stop(stopped_timer)); +#if !CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + TEST_ASSERT_TRUE(alarm_before_stop == esp_timer_impl_get_alarm_reg()); +#endif + + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ASSERT_EQUAL(1, remaining.fire_count); + TEST_ASSERT_EQUAL(0, stopped.fire_count); + TEST_ASSERT_TRUE(time_is_within(remaining.fire_time_us, + start_time_us + remaining_timer_delay_us, + fire_time_tolerance_us)); + + vTaskDelay(pdMS_TO_TICKS(600)); + TEST_ASSERT_EQUAL(1, remaining.fire_count); + TEST_ASSERT_EQUAL(0, stopped.fire_count); + + TEST_ESP_OK(esp_timer_delete(remaining_timer)); + TEST_ESP_OK(esp_timer_delete(stopped_timer)); + vTaskDelay(3); +}