/* * 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 "applib/ui/kino/kino_player.h" #include "applib/ui/kino/kino_reel.h" #include "applib/ui/kino/kino_reel_gbitmap.h" #include "applib/ui/kino/kino_reel_gbitmap_sequence.h" #include "applib/ui/kino/kino_reel_pdci.h" #include "applib/ui/kino/kino_reel_pdcs.h" #include "applib/ui/kino/kino_reel_custom.h" #include "clar.h" // Fakes //////////////////////////////////// #include "fake_resource_syscalls.h" // Stubs //////////////////////////////////// #include "stubs_app_state.h" #include "stubs_applib_resource.h" #include "stubs_compiled_with_legacy2_sdk.h" #include "stubs_gpath.h" #include "stubs_graphics.h" #include "stubs_graphics_context.h" #include "stubs_heap.h" #include "stubs_logging.h" #include "stubs_memory_layout.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" #include "stubs_pebble_tasks.h" #include "stubs_resources.h" #include "stubs_ui_window.h" #include "stubs_unobstructed_area.h" void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {} typedef uint16_t ResourceId; const uint8_t *resource_get_builtin_bytes(ResAppNum app_num, uint32_t resource_id, uint32_t *num_bytes_out) { return NULL; } typedef struct TestReelData { uint32_t elapsed_ms; uint32_t duration_ms; } TestReelData; static int s_num_destructor_calls; static void prv_destructor(KinoReel *reel) { s_num_destructor_calls++; free(kino_reel_custom_get_data(reel)); } static uint32_t prv_elapsed_getter(KinoReel *reel) { return ((TestReelData*)kino_reel_custom_get_data(reel))->elapsed_ms; } static bool prv_elapsed_setter(KinoReel *reel, uint32_t elapsed_ms) { ((TestReelData*)kino_reel_custom_get_data(reel))->elapsed_ms = elapsed_ms; return true; } static uint32_t prv_duration_getter(KinoReel *reel) { return ((TestReelData*)kino_reel_custom_get_data(reel))->duration_ms; } static struct TestReelData *test_reel_data; static KinoReelImpl *test_reel_impl = NULL; static KinoReel *test_reel = NULL; static KinoPlayer *test_player = NULL; // Setup void test_kino_player__initialize(void) { test_reel_data = malloc(sizeof(TestReelData)); memset(test_reel_data, 0, sizeof(TestReelData)); s_num_destructor_calls = 0; test_reel_impl = malloc(sizeof(KinoReelImpl)); *test_reel_impl = (KinoReelImpl) { .destructor = prv_destructor, .set_elapsed = prv_elapsed_setter, .get_elapsed = prv_elapsed_getter, .get_duration = prv_duration_getter }; test_reel = kino_reel_custom_create(test_reel_impl, test_reel_data); cl_assert(test_reel != NULL); test_player = malloc(sizeof(KinoPlayer)); memset(test_player, 0, sizeof(KinoPlayer)); kino_player_set_reel(test_player, test_reel, true); } // Teardown void test_kino_player__cleanup(void) { kino_reel_destroy(test_reel); } // Tests //////////////////////////////////// extern void prv_play_animation_update(Animation *animation, const AnimationProgress normalized); void test_kino_player__finite_animation_finite_reel_foward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; kino_player_play(test_player); animation_set_elapsed(test_player->animation, 1234); // intentionally bad value prv_play_animation_update(test_player->animation, ANIMATION_NORMALIZED_MAX * 20 / 300); cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__create_finite_animation_finite_reel_foward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_elapsed(animation, 1234); // intentionally bad value prv_play_animation_update(animation, ANIMATION_NORMALIZED_MAX * 20 / 300); cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__finite_animation_finite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; kino_player_play(test_player); animation_set_reverse(test_player->animation, true); animation_set_elapsed(test_player->animation, 1234); // intentionally bad value prv_play_animation_update(test_player->animation, ANIMATION_NORMALIZED_MAX - ANIMATION_NORMALIZED_MAX * 20 / 300); cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20); } void test_kino_player__create_finite_animation_finite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_reverse(animation, true); animation_set_elapsed(animation, 1234); // intentionally bad value prv_play_animation_update(animation, ANIMATION_NORMALIZED_MAX - ANIMATION_NORMALIZED_MAX * 20 / 300); cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20); } void test_kino_player__finite_animation_infinite_reel_forward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; kino_player_play(test_player); animation_set_elapsed(test_player->animation, 20); animation_set_duration(test_player->animation, 300); prv_play_animation_update(test_player->animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__create_finite_animation_infinite_reel_forward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_elapsed(animation, 20); animation_set_duration(animation, 300); prv_play_animation_update(animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__infinite_animation_finite_reel_forward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; kino_player_play(test_player); animation_set_elapsed(test_player->animation, 20); animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE); prv_play_animation_update(test_player->animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__create_infinite_animation_finite_reel_forward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_elapsed(animation, 20); animation_set_duration(animation, ANIMATION_DURATION_INFINITE); prv_play_animation_update(animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__infinite_animation_infinite_reel_forward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; kino_player_play(test_player); animation_set_elapsed(test_player->animation, 20); animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE); prv_play_animation_update(test_player->animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__create_infinite_animation_infinite_reel_forward(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_elapsed(animation, 20); animation_set_duration(animation, ANIMATION_DURATION_INFINITE); prv_play_animation_update(animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 20); } void test_kino_player__infinite_animation_finite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; kino_player_play(test_player); animation_set_reverse(test_player->animation, true); animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE); animation_set_elapsed(test_player->animation, 20); prv_play_animation_update(test_player->animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20); } void test_kino_player__create_infinite_animation_finite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = 300; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_reverse(animation, true); animation_set_duration(animation, ANIMATION_DURATION_INFINITE); animation_set_elapsed(animation, 20); prv_play_animation_update(animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), 300 - 20); } void test_kino_player__finite_animation_infinite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; kino_player_play(test_player); animation_set_reverse(test_player->animation, true); animation_set_duration(test_player->animation, 300); animation_set_elapsed(test_player->animation, 20); prv_play_animation_update(test_player->animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE); } void test_kino_player__create_finite_animation_infinite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_reverse(animation, true); animation_set_duration(animation, 300); animation_set_elapsed(animation, 20); prv_play_animation_update(animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE); } void test_kino_player__infinite_animation_infinite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; kino_player_play(test_player); animation_set_reverse(test_player->animation, true); animation_set_duration(test_player->animation, ANIMATION_DURATION_INFINITE); animation_set_elapsed(test_player->animation, 20); prv_play_animation_update(test_player->animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE); } void test_kino_player__create_infinite_animation_infinite_reel_reverse(void) { // Choose duration and elapsed to have clean division for // ANIMATION_NORMALIZED_MAX * elapsed / duration = whole_number test_reel_data->duration_ms = ANIMATION_DURATION_INFINITE; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); animation_set_reverse(animation, true); animation_set_duration(animation, ANIMATION_DURATION_INFINITE); animation_set_elapsed(animation, 20); prv_play_animation_update(animation, 0); // intentionally bad value cl_assert_equal_i(kino_reel_get_elapsed(test_reel), ANIMATION_DURATION_INFINITE); } void test_kino_player__create_animation_is_immutable(void) { test_reel_data->duration_ms = 300; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); cl_assert_equal_i(animation_is_immutable(animation), true); } void test_kino_player__create_animation_is_unscheduled_by_play_pause_rewind(void) { test_reel_data->duration_ms = 300; Animation *animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); // Play plays a new animation cl_assert_equal_i(animation_is_scheduled(animation), true); kino_player_play(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); // Create play animation unschedules previous animation animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); // Pause unschedules the current animation cl_assert_equal_i(animation_is_scheduled(animation), true); kino_player_pause(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation = (Animation *)kino_player_create_play_animation(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); animation_schedule(animation); // Rewind unschedules the current animation cl_assert_equal_i(animation_is_scheduled(animation), true); kino_player_rewind(test_player); cl_assert_equal_i(animation_is_scheduled(animation), false); } void test_kino_player__set_reel_calls_destructor(void) { kino_player_set_reel(test_player, test_reel, true); cl_assert_equal_p(test_player->reel, test_reel); cl_assert_equal_i(s_num_destructor_calls, 0); kino_player_set_reel(test_player, NULL, true); cl_assert_equal_p(test_player->reel, NULL); cl_assert_equal_i(s_num_destructor_calls, 1); kino_player_set_reel(test_player, NULL, true); cl_assert_equal_p(test_player->reel, NULL); cl_assert_equal_i(s_num_destructor_calls, 1); test_reel = NULL; } void test_kino_player__set_reel_does_not_call_destructor(void) { test_player->owns_reel = false; kino_player_set_reel(test_player, test_reel, false); cl_assert_equal_p(test_player->reel, test_reel); cl_assert_equal_i(s_num_destructor_calls, 0); kino_player_set_reel(test_player, NULL, true); cl_assert_equal_p(test_player->reel, NULL); cl_assert_equal_i(s_num_destructor_calls, 0); }