Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,91 @@
/*
* 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/common/clock.h"
#include "services/normal/alarms/alarm.h"
#include "services/normal/timeline/alarm_layout.h"
#include "services/normal/timeline/attribute.h"
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_activity.h"
#include "stubs_alarm_pin.h"
#include "stubs_analytics.h"
#include "stubs_app_install_manager.h"
#include "stubs_clock.h"
#include "stubs_cron.h"
#include "stubs_events.h"
#include "stubs_i18n.h"
#include "stubs_layout_node.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_new_timer.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_rtc.h"
#include "stubs_settings_file.h"
#include "stubs_system_task.h"
#include "stubs_timeline_event.h"
#include "stubs_timeline_layout.h"
// Functions under test
/////////////////////////
void prv_get_subtitle_from_attributes(AttributeList *attributes, char *buffer, size_t buffer_size,
const void *i18n_owner);
// Setup
/////////////////////////
void test_alarm_layout__initialize(void) {
}
void test_alarm_layout__cleanup(void) {
}
// Tests
///////////////////////////
void test_alarm_layout__get_subtitle_from_attributes(void) {
char buffer[TIME_STRING_REQUIRED_LENGTH] = {0};
const size_t buffer_size = sizeof(buffer);
const void *dummy_i18n_owner = (void *)1234;
AttributeList attribute_list = {0};
AttributeList *attribute_list_ref = &attribute_list;
// For legacy reasons (see PBL-33899), an alarm pin that only has a subtitle attribute should use
// that subtitle, manually making it all-caps using toupper_str() on rectangular displays
attribute_list_add_cstring(attribute_list_ref, AttributeIdSubtitle, "Weekdays");
prv_get_subtitle_from_attributes(attribute_list_ref, buffer, buffer_size, dummy_i18n_owner);
cl_assert_equal_s(buffer, PBL_IF_RECT_ELSE("WEEKDAYS", "Weekdays"));
// An alarm pin that has both a subtitle attribute and an AlarmKind attribute should create the
// subtitle using the AlarmKind (ignoring the subtitle attribute), respecting the desire to
// all-caps the subtitle on rectangular displays
attribute_list = (AttributeList) {0};
attribute_list_add_cstring(attribute_list_ref, AttributeIdSubtitle, "Ignore me!");
attribute_list_add_uint8(attribute_list_ref, AttributeIdAlarmKind, (uint8_t)ALARM_KIND_JUST_ONCE);
prv_get_subtitle_from_attributes(attribute_list_ref, buffer, buffer_size, dummy_i18n_owner);
cl_assert_equal_s(buffer, PBL_IF_RECT_ELSE("ONCE", "Once"));
attribute_list_destroy_list(attribute_list_ref);
}

View file

@ -0,0 +1,372 @@
/*
* 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/timeline/attribute.h"
#include "util/size.h"
#include <stdint.h>
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
// Setup
////////////////////////////////////////////////////////////////
static Attribute action1_attributes[] = {
{.id = AttributeIdTitle, .cstring = "Dismiss"},
};
static Attribute action2_attributes[] = {
{.id = AttributeIdTitle, .cstring = "Like"},
{.id = AttributeIdAncsAction, .int8 = 1}
};
static Attribute attributes[] = {
{.id = AttributeIdTitle, .cstring = "Test Notification"},
{.id = AttributeIdSubtitle, .cstring = "Subtitle"},
{.id = AttributeIdBody, .cstring = "This is a test notification. "
"Look at it and behold the awesome."},
};
void test_attribute__initialize(void) {
}
void test_attribute__cleanup(void) {
}
// Tests
////////////////////////////////////////////////////////////////
void test_attribute__uint32_list(void) {
AttributeList attr_list = {};
uint8_t metric_buffer[Uint32ListSize(3)];
Uint32List *metric_values = (Uint32List *)metric_buffer;
metric_values->num_values = 3;
metric_values->values[0] = 100;
metric_values->values[1] = 200;
metric_values->values[2] = 300;
attribute_list_add_uint32_list(&attr_list, AttributeIdMetricIcons, metric_values);
Uint32List *other = attribute_get_uint32_list(&attr_list, AttributeIdMetricIcons);
for (int i = 0; i < metric_values->num_values; i++) {
cl_assert_equal_i(metric_values->values[i], other->values[i]);
}
const size_t serialized_size = attribute_list_get_serialized_size(&attr_list);
cl_assert_equal_i(serialized_size, 19);
uint8_t serialized_buffer[serialized_size];
attribute_list_serialize(&attr_list, serialized_buffer, &serialized_buffer[serialized_size]);
const size_t buffer_size = attribute_list_get_string_buffer_size(&attr_list);
uint8_t deserialized_buffer[buffer_size];
AttributeList attr_list_out = {};
attribute_list_init_list(attr_list.num_attributes, &attr_list_out);
const uint8_t *buffer = (uint8_t *)deserialized_buffer;
const uint8_t *cursor = serialized_buffer;
attribute_deserialize_list((char **)&buffer, (char *)&deserialized_buffer[buffer_size],
&cursor, &serialized_buffer[serialized_size],
attr_list_out);
other = attribute_get_uint32_list(&attr_list_out, AttributeIdMetricIcons);
for (int i = 0; i < metric_values->num_values; i++) {
cl_assert_equal_i(metric_values->values[i], other->values[i]);
}
}
static void prv_check_attribute_list_serialize(AttributeList *attr_list_to_serialize,
const uint8_t *expected_attr_list_serialized,
size_t expected_attr_list_serialized_size) {
uint8_t buffer[expected_attr_list_serialized_size];
const size_t size = attribute_list_serialize(attr_list_to_serialize, buffer,
buffer + expected_attr_list_serialized_size);
cl_assert_equal_i(size, expected_attr_list_serialized_size);
cl_assert_equal_m(expected_attr_list_serialized, buffer, expected_attr_list_serialized_size);
}
void test_attribute__serialize_attr_list(void) {
AttributeList attr_list1 = {
.num_attributes = ARRAY_LENGTH(action1_attributes),
.attributes = action1_attributes
};
AttributeList attr_list2 = {
.num_attributes = ARRAY_LENGTH(action2_attributes),
.attributes = action2_attributes
};
AttributeList attr_list3 = {
.num_attributes = ARRAY_LENGTH(attributes),
.attributes = attributes
};
static uint8_t attr_list1_serialized[] = {
// Action Attributes
0x01, // Attribute ID - Title
0x07, 0x00, // Attribute Length
// Attribute text:
'D', 'i', 's', 'm', 'i', 's', 's',
};
static uint8_t attr_list2_serialized[] = {
0x01, // Attribute 1 ID - Title
0x04, 0x00, // Attribute 1 Length
// Attribute text:
'L', 'i', 'k', 'e',
0x07, // Attribute 2 ID - ANCS UID
0x01, 0x00, // Attribute 2 Length
// Attribute text: "Test"
0x01
};
static uint8_t attr_list3_serialized[] = {
// Attribute 1
0x01, // Attribute ID - Title
0x11, 0x00, // Attribute Length
// Attribute text: "Test Notification"
0x54, 0x65, 0x73, 0x74, 0x20, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e,
// Attribute 2
0x02, // Attribute ID - Subtitle
0x08, 0x00, // Attribute Length
// Attribute text: "Subtitle"
'S', 'u', 'b', 't', 'i', 't', 'l', 'e',
// Attribute 3
0x03, // Attribute ID - Body
0x3f, 0x00, // Attribute Length
// Attribute text: "This is a test notification. Look at it and behold the awesome."
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6e,
0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x4c, 0x6f, 0x6f,
0x6b, 0x20, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x62, 0x65, 0x68, 0x6f,
0x6c, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x77, 0x65, 0x73, 0x6f, 0x6d, 0x65, 0x2e,
};
prv_check_attribute_list_serialize(&attr_list1, attr_list1_serialized,
sizeof(attr_list1_serialized));
prv_check_attribute_list_serialize(&attr_list2, attr_list2_serialized,
sizeof(attr_list2_serialized));
prv_check_attribute_list_serialize(&attr_list3, attr_list3_serialized,
sizeof(attr_list3_serialized));
}
void test_attribute__attributes_add_to_list(void) {
static const uint32_t value_uint32 = 123123423;
static const uint8_t value_uint8 = 17;
AttributeList list = {0};
attribute_list_add_cstring(&list, AttributeIdTitle, "Title1");
cl_assert_equal_s(attribute_get_string(&list, AttributeIdTitle, ""), "Title1");
cl_assert_equal_i(list.num_attributes, 1);
attribute_list_add_cstring(&list, AttributeIdSubtitle, "Subtitle");
cl_assert_equal_s(attribute_get_string(&list, AttributeIdSubtitle, ""), "Subtitle");
cl_assert_equal_s(attribute_get_string(&list, AttributeIdTitle, ""), "Title1");
cl_assert_equal_i(list.num_attributes, 2);
attribute_list_add_cstring(&list, AttributeIdTitle, "Title2");
cl_assert_equal_s(attribute_get_string(&list, AttributeIdTitle, ""), "Title2");
cl_assert_equal_s(attribute_get_string(&list, AttributeIdSubtitle, ""), "Subtitle");
cl_assert_equal_i(list.num_attributes, 2);
attribute_list_add_uint32(&list, AttributeIdLastUpdated, value_uint32);
attribute_list_add_uint8(&list, AttributeIdBgColor, value_uint8);
cl_assert_equal_i(value_uint32, attribute_get_uint32(&list, AttributeIdLastUpdated, 0));
cl_assert_equal_i(value_uint8, attribute_get_uint8(&list, AttributeIdBgColor, 0));
cl_assert_equal_i(list.num_attributes, 4);
attribute_list_destroy_list(&list);
}
void test_attribute__attribute_list_copy(void) {
AttributeList list = {0};
attribute_list_add_cstring(&list, AttributeIdTitle, "Title");
attribute_list_add_cstring(&list, AttributeIdSubtitle, "Subtitle");
attribute_list_add_cstring(&list, AttributeIdBody, "Body");
// title + subtitle + body + 3 * (attributeid + pointer)
size_t size_list = attribute_list_get_buffer_size(&list);
cl_assert_equal_i(size_list, (5 + 1) + (8 + 1) + (4 + 1) + 3 * sizeof(Attribute));
uint8_t *buffer = kernel_malloc_check(size_list);
uint8_t *buffer_orig = buffer;
AttributeList list2 = {0};
cl_assert(attribute_list_copy(&list2, &list, buffer, buffer + size_list));
// check that we haven't modified buffer
cl_assert(buffer == buffer_orig);
cl_assert_equal_s(attribute_get_string(&list2, AttributeIdTitle, ""), "Title");
cl_assert_equal_s(attribute_get_string(&list2, AttributeIdSubtitle, ""), "Subtitle");
cl_assert_equal_s(attribute_get_string(&list2, AttributeIdBody, ""), "Body");
// check that the pointers have moved
cl_assert(attribute_get_string(&list2, AttributeIdTitle, "") !=
attribute_get_string(&list, AttributeIdTitle, ""));
cl_assert(attribute_get_string(&list2, AttributeIdSubtitle, "") !=
attribute_get_string(&list, AttributeIdSubtitle, ""));
cl_assert(attribute_get_string(&list2, AttributeIdBody, "") !=
attribute_get_string(&list, AttributeIdBody, ""));
attribute_list_destroy_list(&list);
kernel_free(buffer);
}
static void prv_check_app_glance_subtitle_in_attribute_list_deserializes(
const uint8_t *serialized_attribute_list_to_deserialize,
size_t serialized_attribute_list_to_deserialize_size, uint8_t num_attributes,
const char *expected_app_glance_subtitle_after_deserializing) {
// Get the buffer size needed for the attributes we're going to deserialize
// We don't have a value to check this against but we implicitly check it because if it's
// incorrect then the overall deserialization will fail
const uint8_t *end = serialized_attribute_list_to_deserialize +
serialized_attribute_list_to_deserialize_size;
const uint8_t *buffer_size_cursor = serialized_attribute_list_to_deserialize;
const int32_t buffer_size =
attribute_get_buffer_size_for_serialized_attributes(num_attributes, &buffer_size_cursor, end);
// Allocate buffers both for the Attribute structs as well as the data they'll hold
Attribute attribute_buffer[num_attributes];
char attribute_data_buffer[buffer_size];
// Setup the arguments for the `attribute_deserialize_list` function
char *attribute_data_buffer_pointer = attribute_data_buffer;
AttributeList deserialization_result_attribute_list = (AttributeList) {
.num_attributes = num_attributes,
.attributes = attribute_buffer,
};
const uint8_t *deserialization_cursor = serialized_attribute_list_to_deserialize;
// Check that the deserialization completes successfully
cl_assert_equal_b(attribute_deserialize_list(&attribute_data_buffer_pointer,
attribute_data_buffer + buffer_size,
&deserialization_cursor, end,
deserialization_result_attribute_list),
true);
// Check that the app glance subtitle string we deserialized matches the string we expect
cl_assert_equal_s(attribute_get_string(&deserialization_result_attribute_list,
AttributeIdSubtitleTemplateString, NULL),
expected_app_glance_subtitle_after_deserializing);
}
void test_attribute__app_glance_subtitle_in_attribute_list(void) {
Attribute app_glance_subtitle_attributes[] = {
{
.id = AttributeIdSubtitleTemplateString,
.cstring = "Your app at a glance!"
},
};
AttributeList app_glance_subtitle_attribute_list = {
.num_attributes = ARRAY_LENGTH(app_glance_subtitle_attributes),
.attributes = app_glance_subtitle_attributes,
};
const uint8_t app_glance_subtitle_attribute_list_serialized[] = {
0x2F, // Attribute ID - App Glance Subtitle
0x15, 0x00, // Attribute Length
// Attribute text:
'Y', 'o', 'u', 'r', ' ', 'a', 'p', 'p', ' ', 'a', 't', ' ', 'a', ' ',
'g', 'l', 'a', 'n', 'c', 'e', '!',
};
const size_t app_glance_subtitle_attribute_list_serialized_size =
sizeof(app_glance_subtitle_attribute_list_serialized);
// Check that serializing the AttributeList matches the serialized byte array above
prv_check_attribute_list_serialize(&app_glance_subtitle_attribute_list,
app_glance_subtitle_attribute_list_serialized,
app_glance_subtitle_attribute_list_serialized_size);
// Now let's check that deserializing the serialized byte array above results in the same
// attributes as the AttributeList above...
// It's assumed we know the number of attributes in the serialized list, so just copy it from
// the AttributeList we hope to recreate
const uint8_t num_attributes = app_glance_subtitle_attribute_list.num_attributes;
prv_check_app_glance_subtitle_in_attribute_list_deserializes(
app_glance_subtitle_attribute_list_serialized,
app_glance_subtitle_attribute_list_serialized_size, num_attributes,
attribute_get_string(&app_glance_subtitle_attribute_list, AttributeIdSubtitleTemplateString,
NULL));
}
void test_attribute__too_long_app_glance_subtitle_in_attribute_list(void) {
Attribute app_glance_subtitle_attributes[] = {
{
.id = AttributeIdSubtitleTemplateString,
.cstring = "This is a really really really really really really really really really "
"really really really really really really really really really really "
"long subtitle!"
},
};
// Check that we're actually using a string longer than the max app glance subtitle length
cl_assert(
strlen(app_glance_subtitle_attributes->cstring) > ATTRIBUTE_APP_GLANCE_SUBTITLE_MAX_LEN);
AttributeList app_glance_subtitle_attribute_list = {
.num_attributes = ARRAY_LENGTH(app_glance_subtitle_attributes),
.attributes = app_glance_subtitle_attributes,
};
const uint8_t app_glance_subtitle_attribute_list_serialized[] = {
0x2F, // Attribute ID - App Glance Subtitle
0x9D, 0x00, // Attribute Length
// Attribute text:
'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'r', 'e', 'a', 'l', 'l', 'y', ' ',
'l', 'o', 'n', 'g', ' ', 's', 'u', 'b', 't', 'i', 't', 'l', 'e', '!',
};
const size_t app_glance_subtitle_attribute_list_serialized_size =
sizeof(app_glance_subtitle_attribute_list_serialized);
// Check that serializing the AttributeList matches the serialized byte array above
// Note that serializing an app glance subtitle that is too long doesn't have any effect; we only
// respect the max length when deserializing it!
prv_check_attribute_list_serialize(&app_glance_subtitle_attribute_list,
app_glance_subtitle_attribute_list_serialized,
app_glance_subtitle_attribute_list_serialized_size);
// Now let's check that deserializing the serialized byte array above results in a truncated
// version of the original string because it's longer than the max length
// It's assumed we know the number of attributes in the serialized list, so just copy it from
// the AttributeList we hope to recreate
const uint8_t num_attributes = app_glance_subtitle_attribute_list.num_attributes;
prv_check_app_glance_subtitle_in_attribute_list_deserializes(
app_glance_subtitle_attribute_list_serialized,
app_glance_subtitle_attribute_list_serialized_size, num_attributes,
"This is a really really really really really really really really really really really "
"really really really really really really really really long su");
}

View file

@ -0,0 +1,412 @@
/*
* 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 "kernel/events.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/timeline/calendar.h"
#include "services/normal/timeline/event.h"
#include "services/normal/timeline/timeline.h"
#include "system/logging.h"
#include "clar.h"
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_ancs.h"
#include "stubs_ancs_notifications.h"
#include "stubs_app_cache.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_manager.h"
#include "stubs_blob_db.h"
#include "stubs_blob_db_sync.h"
#include "stubs_blob_db_sync_util.h"
#include "stubs_event_loop.h"
#include "stubs_event_service_client.h"
#include "stubs_hexdump.h"
#include "stubs_i18n.h"
#include "stubs_layout_layer.h"
#include "stubs_logging.h"
#include "stubs_modal_manager.h"
#include "stubs_mutex.h"
#include "stubs_notification_storage.h"
#include "stubs_notifications.h"
#include "stubs_passert.h"
#include "stubs_phone_call_util.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_regular_timer.h"
#include "stubs_reminder_db.h"
#include "stubs_session.h"
#include "stubs_sleep.h"
#include "stubs_system_task.h"
#include "stubs_task_watchdog.h"
#include "stubs_text_layer_flow.h"
#include "stubs_timeline.h"
#include "stubs_timeline_pin_window.h"
#include "stubs_window_stack.h"
// Fakes
////////////////////////////////////////////////////////////////
#include "fake_new_timer.h"
#include "fake_pbl_malloc.h"
#include "fake_pebble_tasks.h"
#include "fake_rtc.h"
#include "fake_spi_flash.h"
#include "fake_settings_file.h"
#include "fake_events.h"
bool calendar_layout_verify(bool existing_attributes[]) {
return true;
}
bool weather_layout_verify(bool existing_attributes[]) {
return true;
}
const TimelineEventImpl *timeline_peek_get_event_service(void) {
return NULL;
}
// Helpers
////////////////////////////////////////////////////////////////
static bool s_in_calendar_event = false;
static bool prv_get_calendar_ongoing(void) {
PebbleEvent event = fake_event_get_last();
if (event.type == PEBBLE_CALENDAR_EVENT) {
s_in_calendar_event = event.calendar.is_event_ongoing;
}
return s_in_calendar_event;
}
// Fake pins
////////////////////////////////////////////////////////////////
static Attribute title_attr = {
.id = AttributeIdTitle,
.cstring = "title",
};
static TimelineItem item1 = {
.header = {
.id = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.timestamp = 10*60,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem item2 = {
.header = {
.id = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.timestamp = 15*60,
.duration = 20,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem item3 = {
.header = {
.id = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.timestamp = 25*60,
.duration = 5,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem item4 = {
.header = {
.id = {0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.timestamp = 100*60,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// NOT A CALENDAR PIN
static TimelineItem item5 = {
.header = {
.id = {0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.timestamp = 10*60,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdWeather,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// ALL DAY PIN
static TimelineItem item6 = {
.header = {
.id = {0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.timestamp = 100*60,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = true,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// Setup
////////////////////////////////////////////////////////////////
void test_calendar__initialize(void) {
s_in_calendar_event = false;
rtc_set_time(0);
fake_event_init();
pin_db_init();
}
void test_calendar__cleanup(void) {
timeline_event_deinit();
stub_new_timer_cleanup();
fake_settings_file_reset();
}
// Tests
////////////////////////////////////////////////////////////////
void test_calendar__no_events(void) {
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert_equal_i(timer_id, TIMER_INVALID_ID);
}
void test_calendar__init_with_future_event(void) {
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 2);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert(timer_id != TIMER_INVALID_ID);
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(10*60, stub_new_timer_timeout(timer_id) / 1000);
}
void test_calendar_handle__future_event_added_and_removed(void) {
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert_equal_i(timer_id, TIMER_INVALID_ID);
cl_assert(!stub_new_timer_is_scheduled(timer_id));
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 2);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(10*60, stub_new_timer_timeout(timer_id) / 1000);
cl_assert(timeline_remove(&item1.header.id));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 3);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(!stub_new_timer_is_scheduled(timer_id));
}
void test_calendar__init_with_ongoing_event(void) {
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
rtc_set_time(15 * 60);
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert(timer_id != TIMER_INVALID_ID);
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(5*60, stub_new_timer_timeout(timer_id) / 1000);
}
void test_calendar_handle__ongoing_event_added_and_removed(void) {
rtc_set_time(15 * 60);
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert_equal_i(timer_id, TIMER_INVALID_ID);
cl_assert(!stub_new_timer_is_scheduled(timer_id));
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 2);
cl_assert(prv_get_calendar_ongoing());
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(5*60, stub_new_timer_timeout(timer_id) / 1000);
cl_assert(timeline_remove(&item1.header.id));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 3);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(!stub_new_timer_is_scheduled(timer_id));
}
void test_calendar__init_with_past_event(void) {
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
rtc_set_time(30 * 60);
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert_equal_i(timer_id, TIMER_INVALID_ID);
cl_assert(!stub_new_timer_is_scheduled(timer_id));
}
void test_calendar_handle__past_event_added_and_removed(void) {
rtc_set_time(30 * 60);
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert_equal_i(timer_id, TIMER_INVALID_ID);
cl_assert(!stub_new_timer_is_scheduled(timer_id));
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 2);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(!stub_new_timer_is_scheduled(timer_id));
cl_assert(timeline_remove(&item1.header.id));
timeline_event_handle_blobdb_event();
cl_assert_equal_i(fake_event_get_count(), 3);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(!stub_new_timer_is_scheduled(timer_id));
}
void test_calendar__timer_test(void) {
cl_assert(timeline_add(&item1));
timeline_event_handle_blobdb_event();
cl_assert(timeline_add(&item2));
timeline_event_handle_blobdb_event();
cl_assert(timeline_add(&item3));
timeline_event_handle_blobdb_event();
cl_assert(timeline_add(&item4));
timeline_event_handle_blobdb_event();
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert(timer_id != TIMER_INVALID_ID);
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(10*60, stub_new_timer_timeout(timer_id) / 1000);
rtc_set_time(10 * 60);
cl_assert(stub_new_timer_fire(timer_id));
cl_assert_equal_i(fake_event_get_count(), 2);
cl_assert(prv_get_calendar_ongoing());
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(10*60, stub_new_timer_timeout(timer_id) / 1000);
rtc_set_time(20 * 60);
cl_assert(stub_new_timer_fire(timer_id));
cl_assert_equal_i(fake_event_get_count(), 3);
cl_assert(prv_get_calendar_ongoing());
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(15*60, stub_new_timer_timeout(timer_id) / 1000);
rtc_set_time(35 * 60);
cl_assert(stub_new_timer_fire(timer_id));
cl_assert_equal_i(fake_event_get_count(), 4);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(65*60, stub_new_timer_timeout(timer_id) / 1000);
rtc_set_time(100 * 60);
cl_assert(stub_new_timer_fire(timer_id));
cl_assert_equal_i(fake_event_get_count(), 5);
cl_assert(prv_get_calendar_ongoing());
cl_assert(stub_new_timer_is_scheduled(timer_id));
cl_assert_equal_i(10*60, stub_new_timer_timeout(timer_id) / 1000);
rtc_set_time(110 * 60);
cl_assert(stub_new_timer_fire(timer_id));
cl_assert_equal_i(fake_event_get_count(), 6);
cl_assert(!prv_get_calendar_ongoing());
cl_assert(!stub_new_timer_is_scheduled(timer_id));
}
void test_calendar__handle_non_calendar_pins(void) {
// Insert a random pin (non calendar event)
cl_assert(timeline_add(&item5));
timeline_event_handle_blobdb_event();
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert_equal_i(timer_id, TIMER_INVALID_ID);
}
void test_calendar__handle_all_day_pins(void) {
// Insert an all day pin
cl_assert(timeline_add(&item6));
timeline_event_handle_blobdb_event();
timeline_event_init();
cl_assert_equal_i(fake_event_get_count(), 1);
cl_assert(!prv_get_calendar_ongoing());
TimerID timer_id = stub_new_timer_get_next();
cl_assert(!stub_new_timer_is_scheduled(timer_id));
}

View file

@ -0,0 +1,304 @@
/*
* 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 "services/normal/timeline/reminders.h"
#include "kernel/events.h"
#include "services/normal/filesystem/pfs.h"
#include "clar.h"
// Fixture
////////////////////////////////////////////////////////////////
// Fakes
////////////////////////////////////////////////////////////////
#include "fake_new_timer.h"
#include "fake_pbl_malloc.h"
#include "fake_pebble_tasks.h"
#include "fake_spi_flash.h"
#include "fake_system_task.h"
#include "stubs_layout_layer.h"
static time_t now = 0;
static int num_events_put = 0;
time_t rtc_get_time(void) {
return now;
}
RtcTicks rtc_get_ticks(void) {
return 0;
}
typedef void (*CallbackEventCallback)(void *data);
void launcher_task_add_callback(CallbackEventCallback callback, void *data) {
callback(data);
}
void event_put(PebbleEvent* event) {
num_events_put++;
}
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_prompt.h"
#include "stubs_regular_timer.h"
#include "stubs_sleep.h"
#include "stubs_task_watchdog.h"
extern TimerID get_reminder_timer_id(void);
static TimelineItem item1 = {
.header = {
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb4},
.timestamp = 0,
.duration = 0,
.type = TimelineItemTypeReminder,
} // don't care about the rest
};
static TimelineItem item2 = {
.header = {
.id = {0x55, 0xcb, 0x7c, 0x75, 0x8a, 0x35, 0x44, 0x87,
0x90, 0xa4, 0x91, 0x3f, 0x1f, 0xa6, 0x76, 0x01},
.timestamp = 100,
.duration = 0,
.type = TimelineItemTypeReminder,
}
};
static TimelineItem item3 = {
.header = {
.id = {0x7c, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x44, 0x2c,
0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05},
.timestamp = 300,
.duration = 0,
.type = TimelineItemTypeReminder,
}
};
static TimelineItem item4 = {
.header = {
.id = {0x8c, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x44, 0x2c,
0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05},
.timestamp = 1337,
.duration = 0,
.type = TimelineItemTypeReminder,
}
};
// Setup
////////////////////////////////////////////////////////////////
void test_reminders__initialize(void) {
now = 0;
num_events_put = 0;
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
reminder_db_init();
// add all four explicitly out of order
cl_assert(S_SUCCESS == reminders_insert(&item4));
cl_assert(S_SUCCESS == reminders_insert(&item2));
cl_assert(S_SUCCESS == reminders_insert(&item1));
cl_assert(S_SUCCESS == reminders_insert(&item3));
}
void test_reminders__cleanup(void) {
//nada
}
// Tests
////////////////////////////////////////////////////////////////
void test_reminders__timer_test(void) {
cl_assert(stub_new_timer_timeout(get_reminder_timer_id()) == 0);
cl_assert(memcmp(&item1.header.id, stub_new_timer_callback_data(get_reminder_timer_id()), sizeof(TimelineItemId)) == 0);
stub_pebble_tasks_set_current(PebbleTask_NewTimers);
cl_assert(stub_new_timer_fire(get_reminder_timer_id()));
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(num_events_put, 1);
// item 2 is now the top reminder...
cl_assert_equal_m(&item2.header.id, stub_new_timer_callback_data(get_reminder_timer_id()), sizeof(TimelineItemId));
cl_assert(stub_new_timer_timeout(get_reminder_timer_id()) == 100 * 1000);
// ...until we insert item 1 back
cl_assert(S_SUCCESS == reminders_insert(&item1));
cl_assert_equal_m(&item1.header.id, stub_new_timer_callback_data(get_reminder_timer_id()), sizeof(TimelineItemId));
cl_assert(stub_new_timer_timeout(get_reminder_timer_id()) == 0);
stub_pebble_tasks_set_current(PebbleTask_NewTimers);
cl_assert(stub_new_timer_fire(get_reminder_timer_id()));
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(num_events_put, 2);
cl_assert_equal_m(&item2.header.id, stub_new_timer_callback_data(get_reminder_timer_id()), sizeof(TimelineItemId));
cl_assert(stub_new_timer_timeout(get_reminder_timer_id()) == 100 * 1000);
now = 100;
stub_pebble_tasks_set_current(PebbleTask_NewTimers);
cl_assert(stub_new_timer_fire(get_reminder_timer_id()));
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(num_events_put, 3);
cl_assert_equal_m(&item3.header.id, stub_new_timer_callback_data(get_reminder_timer_id()), sizeof(TimelineItemId));
cl_assert(stub_new_timer_timeout(get_reminder_timer_id()) == 200 * 1000);
now += 200;
stub_pebble_tasks_set_current(PebbleTask_NewTimers);
cl_assert(stub_new_timer_fire(get_reminder_timer_id()));
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(num_events_put, 4);
cl_assert_equal_m(&item4.header.id, stub_new_timer_callback_data(get_reminder_timer_id()), sizeof(TimelineItemId));
cl_assert(stub_new_timer_timeout(get_reminder_timer_id()) == 1037 * 1000);
now += 1037;
stub_pebble_tasks_set_current(PebbleTask_NewTimers);
cl_assert(stub_new_timer_fire(get_reminder_timer_id()));
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(num_events_put, 5);
cl_assert(!new_timer_scheduled(get_reminder_timer_id(), NULL));
}
void test_reminders__first_init_test(void) {
cl_assert_equal_i(reminders_init(), 0);
test_reminders__timer_test();
}
static TimelineItem s_stale_reminder = {
.header = {
.id = {0x3C, 0xAF, 0x17, 0xD5, 0xBE, 0x15, 0x4B, 0xFD, 0xAE, 0x2A,
0xAE, 0x44, 0xC0, 0x96, 0xCB, 0x7D},
.timestamp = 60 * 60,
.duration = 0,
.type = TimelineItemTypeReminder,
}
};
void test_reminders__stale_item_insert(void) {
now = 3 * 60 * 60; // 3 hours after stale_reminder
cl_assert_equal_i(reminders_insert(&s_stale_reminder), E_INVALID_OPERATION);
}
void test_reminders__stale_item_init(void) {
cl_assert_equal_i(reminders_insert(&s_stale_reminder), S_SUCCESS);
stub_new_timer_stop(get_reminder_timer_id());
now = 1 * 60 * 60;
reminders_init();
cl_assert(new_timer_scheduled(get_reminder_timer_id(), NULL));
now = 3 * 60 * 60;
reminders_init();
cl_assert(!new_timer_scheduled(get_reminder_timer_id(), NULL));
}
static TimezoneInfo s_tz = {
.tm_gmtoff = -8 * 60 * 60, // PST
};
static TimelineItem s_all_day_reminder = {
.header = {
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x67, 0xb4},
.timestamp = 1425511800, // 23:30 UTC March 4
.duration = 0,
.type = TimelineItemTypeReminder,
.all_day = true,
} // don't care about the rest
};
// should show up before s_all_day_reminder even though its timestamp is after due to tz adjustment
static TimelineItem s_reminder_before_all_day_reminder = {
.header = {
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8d, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x67, 0xb4},
.timestamp = 1425531600, // 21:00 PST March 4
.duration = 0,
.type = TimelineItemTypeReminder,
.all_day = false,
}
};
void test_reminders__all_day(void) {
time_util_update_timezone(&s_tz);
cl_assert_equal_i(reminders_insert(&s_all_day_reminder), S_SUCCESS);
cl_assert_equal_i(reminders_insert(&s_reminder_before_all_day_reminder), S_SUCCESS);
// set time to 16:00 PST March 4
now = 1425513600;
reminders_init();
cl_assert_equal_i(stub_new_timer_timeout(get_reminder_timer_id()), 5 * 60 * 60 * 1000);
cl_assert(uuid_equal(&s_reminder_before_all_day_reminder.header.id,
(Uuid *)stub_new_timer_callback_data(get_reminder_timer_id())));
// set time to 21:00 PST March 4
now = 1425531600;
stub_pebble_tasks_set_current(PebbleTask_NewTimers);
cl_assert(stub_new_timer_fire(get_reminder_timer_id()));
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(stub_new_timer_timeout(get_reminder_timer_id()), (2 * 60 + 30) * 60 * 1000);
cl_assert(uuid_equal(&s_all_day_reminder.header.id,
(Uuid *)stub_new_timer_callback_data(get_reminder_timer_id())));
}
void test_reminders__stale_all_day(void) {
time_util_update_timezone(&s_tz);
// set time to 21:00 PST March 5, when s_all_day_reminder should be rejected for being stale
now = 1425618000;
cl_assert_equal_i(reminders_insert(&s_all_day_reminder), E_INVALID_OPERATION);
// set time to 21:00 PST March 4
now = 1425531600;
// if the timestamp of s_all_day_reminder isn't adjusted, it would be rejected for being stale
// since it "seems" to be timestamped at 15:30 PST, but it should be accepted
cl_assert_equal_i(reminders_insert(&s_all_day_reminder), S_SUCCESS);
}
void test_reminders__calculate_snooze_time(void) {
// Test half-time snooze
now = 0;
cl_assert_equal_i(50, reminders_calculate_snooze_time(&item2));
now = 50;
cl_assert_equal_i(25, reminders_calculate_snooze_time(&item2));
// Test constant snooze
now = 80;
cl_assert_equal_i(600, reminders_calculate_snooze_time(&item2));
now = 100 + 48 * 60 * 60;
cl_assert_equal_i(600, reminders_calculate_snooze_time(&item2));
// Test no snooze
now = 100 + 48 * 60 * 60 + 1;
cl_assert_equal_i(0, reminders_calculate_snooze_time(&item2));
}

View file

@ -0,0 +1,271 @@
/*
* 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 "apps/system_apps/timeline/pin_window.h"
#include "services/normal/timeline/weather_layout.h"
#include "clar.h"
#include <stdio.h>
// Fakes
/////////////////////
#include "fake_content_indicator.h"
#include "fixtures/load_test_resources.h"
bool property_animation_init(PropertyAnimation *animation,
const PropertyAnimationImplementation *implementation,
void *subject, void *from_value, void *to_value) {
if (!animation) {
return false;
}
PropertyAnimationPrivate *animation_private = (PropertyAnimationPrivate *)animation;
*animation_private = (PropertyAnimationPrivate){
.animation.implementation = (const AnimationImplementation *)implementation,
.subject = subject,
};
if (from_value) {
animation_private->values.from.int16 = *(int16_t *)from_value;
}
if (to_value) {
animation_private->values.to.int16 = *(int16_t *)to_value;
}
return true;
}
void clock_get_friendly_date(char *buffer, int buf_size, time_t timestamp) {
if (buffer) {
strncpy(buffer, "Today", buf_size);
buffer[buf_size - 1] = '\0';
}
}
void clock_get_since_time(char *buffer, int buf_size, time_t timestamp) {
if (buffer) {
strncpy(buffer, "15 minutes ago", buf_size);
buffer[buf_size - 1] = '\0';
}
}
// Stubs
/////////////////////
#include "stubs_action_menu.h"
#include "stubs_analytics.h"
#include "stubs_animation_timing.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_timer.h"
#include "stubs_app_window_stack.h"
#include "stubs_bootbits.h"
#include "stubs_click.h"
#include "stubs_event_service_client.h"
#include "stubs_layer.h"
#include "stubs_logging.h"
#include "stubs_memory_layout.h"
#include "stubs_modal_manager.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_process_info.h"
#include "stubs_pebble_tasks.h"
#include "stubs_process_manager.h"
#include "stubs_prompt.h"
#include "stubs_property_animation.h"
#include "stubs_serial.h"
#include "stubs_shell_prefs.h"
#include "stubs_sleep.h"
#include "stubs_syscalls.h"
#include "stubs_task_watchdog.h"
#include "stubs_timeline.h"
#include "stubs_timeline_actions.h"
#include "stubs_timeline_item.h"
#include "stubs_timeline_layer.h"
#include "stubs_timeline_peek.h"
#include "stubs_window_manager.h"
#include "stubs_window_stack.h"
// Helper Functions
/////////////////////
#include "fw/graphics/util.h"
// Setup and Teardown
////////////////////////////////////
static GContext s_ctx;
static FrameBuffer *fb = NULL;
GContext *graphics_context_get_current_context(void) {
return &s_ctx;
}
void test_timeline_layouts__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
const GContextInitializationMode context_init_mode = GContextInitializationMode_System;
graphics_context_init(&s_ctx, fb, context_init_mode);
framebuffer_clear(fb);
// Setup resources
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
pfs_format(true /* write erase headers */);
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME,
false /* is_next */);
resource_init();
ContentIndicatorsBuffer *buffer = content_indicator_get_current_buffer();
content_indicator_init_buffer(buffer);
}
void test_timeline_layouts__cleanup(void) {
free(fb);
}
// Helpers
//////////////////////
void prv_handle_down_click(ClickRecognizerRef recognizer, void *context);
static void prv_render_layout(LayoutId layout_id, const AttributeList *attr_list,
size_t num_down_clicks) {
PBL_ASSERTN(attr_list);
TimelineItem item = (TimelineItem) {
.header = (CommonTimelineItemHeader) {
.layout = layout_id,
.type = TimelineItemTypePin,
},
.attr_list = *attr_list,
};
TimelinePinWindow pin_window = (TimelinePinWindow) {};
timeline_pin_window_init(&pin_window, &item, rtc_get_time());
Window *window = &pin_window.window;
window_set_on_screen(window, true, true);
for (int i = 0; i < num_down_clicks; i++) {
prv_handle_down_click(NULL, &pin_window.item_detail_layer);
// Aint nobody got time for animations; advance the scrolling property animation to completion
int16_t to = 0;
if (property_animation_get_to_int16(pin_window.item_detail_layer.animation, &to)) {
pin_window.item_detail_layer.scroll_offset_pixels = to;
}
}
window_render(window, &s_ctx);
}
typedef struct TimelineLayoutTestConfig {
LayoutId layout_id;
const char *title;
const char *subtitle;
const char *location_name;
const char *body;
TimelineResourceId icon_timeline_res_id;
WeatherTimeType weather_time_type;
} TimelineLayoutTestConfig;
static void prv_construct_and_render_layout(const TimelineLayoutTestConfig *config,
size_t num_down_clicks) {
if (!config) {
return;
}
AttributeList attr_list = (AttributeList) {0};
if (config->title) {
attribute_list_add_cstring(&attr_list, AttributeIdTitle, config->title);
}
if (config->subtitle) {
attribute_list_add_cstring(&attr_list, AttributeIdSubtitle, config->subtitle);
}
if (config->location_name) {
attribute_list_add_cstring(&attr_list, AttributeIdLocationName, config->location_name);
}
if (config->body) {
attribute_list_add_cstring(&attr_list, AttributeIdBody, config->body);
}
if (config->icon_timeline_res_id != TIMELINE_RESOURCE_INVALID) {
attribute_list_add_resource_id(&attr_list, AttributeIdIconPin, config->icon_timeline_res_id);
}
attribute_list_add_uint8(&attr_list, AttributeIdDisplayTime, config->weather_time_type);
// Just need to put something here so our mocked clock_get_since_time() gets called
attribute_list_add_uint32(&attr_list, AttributeIdLastUpdated, 1337);
prv_render_layout(config->layout_id, &attr_list, num_down_clicks);
attribute_list_destroy_list(&attr_list);
}
// Tests
//////////////////////
void test_timeline_layouts__generic(void) {
const TimelineLayoutTestConfig config = (TimelineLayoutTestConfig) {
.layout_id = LayoutIdGeneric,
.title = "Delfina Pizza",
.subtitle = "Open Table Reservation",
.location_name = "145 Williams\nJohn Ave, Palo Alto",
.body = "Body message",
.icon_timeline_res_id = TIMELINE_RESOURCE_DINNER_RESERVATION,
};
prv_construct_and_render_layout(&config, 0);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE_X(peek)));
prv_construct_and_render_layout(&config, 1);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE_X(details1)));
// Round only needs to scroll down once to see everything
#if !PBL_ROUND
prv_construct_and_render_layout(&config, 2);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE_X(details2)));
#endif
}
void test_timeline_layouts__weather(void) {
const TimelineLayoutTestConfig config = (TimelineLayoutTestConfig) {
.layout_id = LayoutIdWeather,
.title = "The Greatest Sunrise Ever",
.subtitle = "90°/60°",
.location_name = "Redwood City",
.body = "A clear sky. Low around 60F.",
.icon_timeline_res_id = TIMELINE_RESOURCE_PARTLY_CLOUDY,
.weather_time_type = WeatherTimeType_Pin,
};
prv_construct_and_render_layout(&config, 0);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE_X(peek)));
prv_construct_and_render_layout(&config, 1);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE_X(details1)));
// Round needs to scroll down one more time to see everything
#if PBL_ROUND
prv_construct_and_render_layout(&config, 2);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE_X(details2)));
#endif
}

View file

@ -0,0 +1,502 @@
/*
* 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 "util/uuid.h"
#include "services/normal/filesystem/pfs.h"
#include "services/common/regular_timer.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/timeline/timeline.h"
#include "apps/system_apps/timeline/timeline_model.h"
#include "util/size.h"
// Fixture
////////////////////////////////////////////////////////////////
// Fakes
////////////////////////////////////////////////////////////////
#include "fake_spi_flash.h"
#include "fake_pbl_malloc.h"
#include "fake_rtc.h"
static TimezoneInfo tz = {
.tm_gmtoff = -8 * 60 * 60, // PST
};
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_app_cache.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_manager.h"
#include "stubs_blob_db.h"
#include "stubs_blob_db_sync.h"
#include "stubs_blob_db_sync_util.h"
#include "stubs_calendar.h"
#include "stubs_event_service_client.h"
#include "stubs_events.h"
#include "stubs_fonts.h"
#include "stubs_hexdump.h"
#include "stubs_i18n.h"
#include "stubs_layout_layer.h"
#include "stubs_logging.h"
#include "stubs_modal_manager.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pebble_tasks.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_regular_timer.h"
#include "stubs_resources.h"
#include "stubs_session.h"
#include "stubs_sleep.h"
#include "stubs_syscalls.h"
#include "stubs_task_watchdog.h"
#include "stubs_window_stack.h"
void ancs_notifications_enable_bulk_action_mode(bool enable) {
return;
}
bool ancs_notifications_is_bulk_action_mode_enabled(void) {
return false;
}
status_t reminder_db_delete_with_parent(const TimelineItemId *id) {
return S_SUCCESS;
}
void timeline_action_endpoint_invoke_action(const Uuid *id,
uint8_t action_id, AttributeList *attributes) {
}
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
}
void timeline_pin_window_push_modal(TimelineItem *item) {
}
const PebbleProcessMd *timeline_get_app_info(void) {
return NULL;
}
PebblePhoneCaller* phone_call_util_create_caller(const char *number, const char *name) {
return NULL;
}
void ancs_perform_action(uint32_t notification_uid, uint8_t action_id) {
}
void notifications_handle_notification_action_result(
PebbleSysNotificationActionResult *action_result) {
}
void notification_storage_set_status(const Uuid *id, uint8_t status) {
}
void notifications_handle_notification_acted_upon(Uuid *notification_id) {
return;
}
// Data
/////////////////////////
static TimelineItem s_items[] = {
{
.header = { // [0]
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb1},
.parent_id = {0},
.timestamp = 1421178061, // Tue Jan 13 11:41:01 2015 PST
.duration = 1,
.type = TimelineItemTypePin,
.flags = 0,
.layout = LayoutIdTest,
},
.attr_list = {
.num_attributes = 0,
.attributes = NULL,
},
.action_group = {
.num_actions = 0,
.actions = NULL,
},
.allocated_buffer = NULL,
}, {
.header = { // [1]
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb2},
.parent_id = {0},
.timestamp = 1421183642, // Tue Jan 13 13:14:02 2015 PST
.duration = 10,
.type = TimelineItemTypePin,
.flags = 0,
.layout = LayoutIdTest,
},
.attr_list = {
.num_attributes = 0,
.attributes = NULL,
},
.action_group = {
.num_actions = 0,
.actions = NULL,
},
.allocated_buffer = NULL,
}, {
.header = { // [2]
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb3},
.parent_id = {0},
.timestamp = 1421183642, // Tue Jan 13 13:14:02 2015 PST
.duration = 2,
.type = TimelineItemTypePin,
.flags = 0,
.layout = LayoutIdTest,
},
.attr_list = {
.num_attributes = 0,
.attributes = NULL,
},
.action_group = {
.num_actions = 0,
.actions = NULL,
},
.allocated_buffer = NULL,
}, {
.header = { // [3]
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb4},
.parent_id = {0},
.timestamp = 1421183642, // Tue Jan 13 13:14:02 2015 PST
.duration = 30,
.type = TimelineItemTypePin,
.flags = 0,
.layout = LayoutIdTest,
},
.attr_list = {
.num_attributes = 0,
.attributes = NULL,
},
.action_group = {
.num_actions = 0,
.actions = NULL,
},
.allocated_buffer = NULL,
}, {
.header = { // [4]
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb5},
.parent_id = {0},
.timestamp = 1421178061, // Tue Jan 13 11:41:01 2015 PST
.duration = 5,
.type = TimelineItemTypePin,
.flags = 0,
.layout = LayoutIdTest,
},
.attr_list = {
.num_attributes = 0,
.attributes = NULL,
},
.action_group = {
.num_actions = 0,
.actions = NULL,
},
.allocated_buffer = NULL,
}, {
.header = { // [5]
.id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb6},
.parent_id = {0},
.timestamp = 1421183462, // Tue Jan 13 13:11:02 PST 2015
.duration = 4,
.type = TimelineItemTypePin,
.flags = 0,
.layout = LayoutIdTest,
},
.attr_list = {
.num_attributes = 0,
.attributes = NULL,
},
.action_group = {
.num_actions = 0,
.actions = NULL,
},
.allocated_buffer = NULL,
}
};
// Setup
/////////////////////////
void test_timeline_model__initialize(void) {
fake_spi_flash_init(0, 0x1000000);
fake_rtc_init(0, 0);
pfs_init(false);
// Note: creating a settings file is going to result in one malloc for the FD name
pin_db_init();
time_util_update_timezone(&tz);
fake_pbl_malloc_clear_tracking();
for (unsigned int i = 0; i < ARRAY_LENGTH(s_items); ++i) {
cl_assert_equal_i(pin_db_insert_item(&s_items[i]), 0);
}
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}
void test_timeline_model__cleanup(void) {
}
// Tests
///////////////////////////
static int s_correct_order[] = {0, 4, 5, 2, 1, 3};
void test_timeline_model__future(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
timeline_model_init(first_time, &model);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(0));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(1));
int new_idx;
bool has_next;
cl_assert(timeline_model_iter_next(&new_idx, &has_next));
cl_assert(has_next);
cl_assert_equal_i(new_idx, 2);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
&timeline_model_get_iter_state(-1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(1));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
cl_assert(timeline_model_iter_next(&new_idx, &has_next));
cl_assert(has_next);
cl_assert_equal_i(new_idx, 3);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
&timeline_model_get_iter_state(-1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(2));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(3));
cl_assert(timeline_model_iter_next(&new_idx, &has_next));
cl_assert(has_next);
cl_assert_equal_i(new_idx, 4);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(-1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(3));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(4));
cl_assert(timeline_model_iter_next(&new_idx, &has_next));
cl_assert(has_next);
cl_assert_equal_i(new_idx, 5);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
&timeline_model_get_iter_state(-1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(4));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(5));
cl_assert(timeline_model_iter_next(&new_idx, &has_next));
cl_assert(!has_next);
cl_assert_equal_i(timeline_model_get_num_items(), 1);
cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
&timeline_model_get_iter_state(-1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(!timeline_model_iter_next(&new_idx, &has_next));
}
void test_timeline_model__and_back(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
timeline_model_init(first_time, &model);
cl_assert(timeline_model_iter_next(NULL, NULL));
cl_assert(timeline_model_iter_next(NULL, NULL));
cl_assert(timeline_model_iter_next(NULL, NULL));
cl_assert(timeline_model_iter_next(NULL, NULL));
cl_assert(timeline_model_iter_next(NULL, NULL));
cl_assert(!timeline_model_iter_next(NULL, NULL));
int new_idx;
cl_assert(timeline_model_iter_prev(&new_idx, NULL));
cl_assert_equal_i(new_idx, 4);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(4));
cl_assert(timeline_model_iter_prev(&new_idx, NULL));
cl_assert_equal_i(new_idx, 3);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[5]].header.id,
&timeline_model_get_iter_state(2)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(3));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(4));
cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(5));
cl_assert(timeline_model_iter_prev(&new_idx, NULL));
cl_assert_equal_i(new_idx, 2);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[4]].header.id,
&timeline_model_get_iter_state(2)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(2));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(3));
cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(4));
cl_assert(timeline_model_iter_prev(&new_idx, NULL));
cl_assert_equal_i(new_idx, 1);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[3]].header.id,
&timeline_model_get_iter_state(2)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(1));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(3));
cl_assert(timeline_model_iter_prev(&new_idx, NULL));
cl_assert_equal_i(new_idx, 0);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(2)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(0));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(1));
cl_assert(timeline_model_get_iter_state(2) == timeline_model_get_iter_state_with_timeline_idx(2));
cl_assert(!timeline_model_iter_prev(&new_idx, NULL));
}
void test_timeline_model__graceful_delete_middle(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
timeline_model_init(first_time, &model);
timeline_model_remove(&s_items[s_correct_order[1]].header.id);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[0]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(0));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
}
void test_timeline_model__graceful_delete_first(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
timeline_model_init(first_time, &model);
timeline_model_remove(&s_items[s_correct_order[0]].header.id);
cl_assert_equal_i(timeline_model_get_num_items(), 2);
cl_assert(uuid_equal(&s_items[s_correct_order[1]].header.id,
&timeline_model_get_iter_state(0)->pin.header.id));
cl_assert(uuid_equal(&s_items[s_correct_order[2]].header.id,
&timeline_model_get_iter_state(1)->pin.header.id));
cl_assert(timeline_model_get_iter_state(0) == timeline_model_get_iter_state_with_timeline_idx(1));
cl_assert(timeline_model_get_iter_state(1) == timeline_model_get_iter_state_with_timeline_idx(2));
}
void test_timeline_model__graceful_delete_all(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
timeline_model_init(first_time, &model);
for (int i = 0; i < ARRAY_LENGTH(s_items); i++) {
timeline_model_remove(&s_items[i].header.id);
}
cl_assert_equal_i(timeline_model_get_num_items(), 0);
cl_assert(!timeline_model_iter_next(NULL, NULL));
cl_assert(!timeline_model_iter_prev(NULL, NULL));
}
void test_timeline_model__is_empty(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
timeline_model_init(first_time, &model);
cl_assert(!timeline_model_is_empty());
for (int i = 0; i < ARRAY_LENGTH(s_items); i++) {
timeline_model_remove(&s_items[i].header.id);
}
cl_assert(timeline_model_is_empty());
}
void test_timeline_model__is_empty_immediate(void) {
TimelineModel model = {0};
model.direction = TimelineIterDirectionFuture;
time_t first_time = 1421178000;
// Note: 1421178000 = Tue Jan 13 11:40:00 PST 2015
for (int i = 0; i < ARRAY_LENGTH(s_items); i++) {
pin_db_delete((uint8_t *)&s_items[i].header.id, sizeof(TimelineItemId));
}
timeline_model_init(first_time, &model);
cl_assert(timeline_model_is_empty());
}

View file

@ -0,0 +1,786 @@
/*
* 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 "kernel/events.h"
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/timeline/event.h"
#include "services/normal/timeline/peek.h"
#include "services/normal/timeline/timeline.h"
#include "system/logging.h"
#include "clar.h"
#include "pebble_asserts.h"
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_ancs.h"
#include "stubs_ancs_notifications.h"
#include "stubs_app_cache.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_manager.h"
#include "stubs_blob_db.h"
#include "stubs_blob_db_sync.h"
#include "stubs_blob_db_sync_util.h"
#include "stubs_event_loop.h"
#include "stubs_event_service_client.h"
#include "stubs_hexdump.h"
#include "stubs_i18n.h"
#include "stubs_layout_layer.h"
#include "stubs_logging.h"
#include "stubs_modal_manager.h"
#include "stubs_mutex.h"
#include "stubs_notification_storage.h"
#include "stubs_notifications.h"
#include "stubs_passert.h"
#include "stubs_phone_call_util.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_regular_timer.h"
#include "stubs_reminder_db.h"
#include "stubs_session.h"
#include "stubs_sleep.h"
#include "stubs_system_task.h"
#include "stubs_task_watchdog.h"
#include "stubs_text_layer_flow.h"
#include "stubs_timeline.h"
#include "stubs_timeline_peek.h"
#include "stubs_timeline_pin_window.h"
#include "stubs_window_stack.h"
// Fakes
////////////////////////////////////////////////////////////////
#include "fake_new_timer.h"
#include "fake_pbl_malloc.h"
#include "fake_pebble_tasks.h"
#include "fake_rtc.h"
#include "fake_spi_flash.h"
#include "fake_settings_file.h"
#include "fake_events.h"
bool calendar_layout_verify(bool existing_attributes[]) {
return true;
}
bool weather_layout_verify(bool existing_attributes[]) {
return true;
}
const TimelineEventImpl *calendar_get_event_service(void) {
return NULL;
}
// Helpers
////////////////////////////////////////////////////////////////
typedef struct PeekTestData {
PebbleTimelinePeekEvent last_peek_event;
unsigned int num_peek_events;
} PeekTestData;
static PeekTestData s_data;
static PebbleTimelinePeekEvent prv_get_peek_event(void) {
return s_data.last_peek_event;
}
static void prv_event_handler(PebbleEvent *event) {
if (event->type == PEBBLE_TIMELINE_PEEK_EVENT) {
s_data.last_peek_event = event->timeline_peek;
s_data.num_peek_events++;
}
}
// Fake pins
////////////////////////////////////////////////////////////////
static Attribute title_attr = {
.id = AttributeIdTitle,
.cstring = "title",
};
static TimelineItem s_item1 = {
.header = {
.id = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 1 * SECONDS_PER_MINUTE,
.duration = 15,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_item2 = {
.header = {
.id = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 5 * SECONDS_PER_MINUTE,
.duration = 20,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_item3 = {
.header = {
.id = { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 9 * SECONDS_PER_MINUTE,
.duration = 5,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_future_item = {
.header = {
.id = { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 100 * SECONDS_PER_MINUTE,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
static TimelineItem s_short_future_item = {
.header = {
.id = { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 100 * SECONDS_PER_MINUTE,
.duration = 5,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// not a calendar pin
static TimelineItem s_weather_item = {
.header = {
.id = { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 10 * SECONDS_PER_MINUTE,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdWeather,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// add day pin
static TimelineItem s_all_day_item = {
.header = {
.id = { 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 100 * SECONDS_PER_MINUTE,
.duration = 10,
.type = TimelineItemTypePin,
.all_day = true,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// 0-duration event
static TimelineItem s_point_item = {
.header = {
.id = { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 20 * SECONDS_PER_MINUTE,
.duration = 0,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdWeather,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// recurring calendar event 1
static TimelineItem s_recurring_calendar_item1 = {
.header = {
.id = { 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 50 * SECONDS_PER_MINUTE - SECONDS_PER_DAY,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// recurring calendar event 2
static TimelineItem s_recurring_calendar_item2 = {
.header = {
.id = { 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 50 * SECONDS_PER_MINUTE,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// recurring calendar event 3
static TimelineItem s_recurring_calendar_item3 = {
.header = {
.id = { 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 50 * SECONDS_PER_MINUTE + SECONDS_PER_DAY,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// back-to-back calendar event 1
static TimelineItem s_back_to_back_calendar_item1 = {
.header = {
.id = { 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 60 * SECONDS_PER_MINUTE,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// back-to-back calendar event 2
static TimelineItem s_back_to_back_calendar_item2 = {
.header = {
.id = { 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.timestamp = 90 * SECONDS_PER_MINUTE,
.duration = 30,
.type = TimelineItemTypePin,
.all_day = false,
.layout = LayoutIdCalendar,
},
.attr_list = {
.num_attributes = 1,
.attributes = &title_attr,
},
};
// Setup
////////////////////////////////////////////////////////////////
void test_timeline_peek_event__initialize(void) {
s_data = (PeekTestData){};
rtc_set_time(0);
fake_event_init();
fake_event_set_callback(prv_event_handler);
pin_db_init();
timeline_event_init();
}
void test_timeline_peek_event__cleanup(void) {
timeline_peek_set_show_before_time(TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S);
timeline_event_deinit();
stub_new_timer_cleanup();
fake_settings_file_reset();
}
// Tests
////////////////////////////////////////////////////////////////
typedef struct AddEventParams {
TimelineItem *item;
} AddEventParams;
#define ADD_EVENT(...) ({ \
AddEventParams params = { __VA_ARGS__ }; \
cl_assert(timeline_add(params.item)); \
timeline_event_handle_blobdb_event(); \
params.item; \
})
typedef struct CreateEventParams {
uint8_t id;
LayoutId layout;
time_t timestamp;
uint16_t duration;
bool all_day;
bool persistent;
} CreateEventParams;
#define DEFINE_EVENT(...) ({ \
CreateEventParams params = { __VA_ARGS__ }; \
TimelineItem item = { \
.header = { \
.type = TimelineItemTypePin, \
.id = { params.id, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, \
.layout = params.layout ?: LayoutIdCalendar, \
.persistent = params.persistent ? 1 : 0, \
.timestamp = params.timestamp, \
.all_day = params.all_day, \
.duration = params.duration, \
}, \
.attr_list = { \
.num_attributes = 1, \
.attributes = &title_attr, \
}, \
}; \
ADD_EVENT( .item = &item ); \
item; \
})
typedef struct CheckNoEventsParams {
unsigned int count;
bool is_future_empty;
} CheckNoEventsParams;
#define CHECK_NO_EVENTS(...) ({ \
CheckNoEventsParams params = { __VA_ARGS__ }; \
PebbleTimelinePeekEvent peek = prv_get_peek_event(); \
cl_assert_equal_i(s_data.num_peek_events, params.count); \
cl_assert_equal_uuid(peek.item_id ? *peek.item_id : UUID_INVALID, UUID_INVALID); \
cl_assert_equal_i(peek.time_type, TimelinePeekTimeType_None); \
cl_assert_equal_i(peek.num_concurrent, 0); \
cl_assert_equal_b(peek.is_future_empty, params.is_future_empty); \
cl_assert_equal_i(stub_new_timer_get_next(), TIMER_INVALID_ID); \
peek; \
})
typedef struct CheckEventParams {
unsigned int count;
Uuid item_id;
unsigned int num_concurrent;
unsigned int timeout_ms;
TimelinePeekTimeType time_type;
bool is_first_event;
} CheckEventParams;
#define CHECK_EVENT(...) ({ \
CheckEventParams params = { __VA_ARGS__ }; \
PebbleTimelinePeekEvent peek = prv_get_peek_event(); \
cl_assert_equal_i(s_data.num_peek_events, params.count); \
cl_assert_equal_uuid(peek.item_id ? *peek.item_id : UUID_INVALID, params.item_id); \
cl_assert_equal_i(peek.time_type, params.time_type); \
cl_assert_equal_i(peek.num_concurrent, params.num_concurrent); \
cl_assert_equal_b(peek.is_first_event, params.is_first_event); \
cl_assert_equal_b(peek.is_future_empty, false); \
const TimerID timer_id = stub_new_timer_get_next(); \
cl_assert(timer_id != TIMER_INVALID_ID); \
cl_assert_equal_i(stub_new_timer_timeout(timer_id), params.timeout_ms); \
peek; \
})
static void prv_invoke_timer(unsigned int timeout_s) {
fake_rtc_increment_time(timeout_s);
stub_new_timer_invoke(1 /* num_invoke */);
}
void test_timeline_peek_event__no_events(void) {
CHECK_NO_EVENTS( .count = 1, .is_future_empty = true );
}
void test_timeline_peek_event__calendar_event(void) {
ADD_EVENT( .item = &s_item1 );
CHECK_EVENT( .count = 2, .item_id = s_item1.header.id, .num_concurrent = 0,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
}
void test_timeline_peek_event__calendar_event_all_day(void) {
ADD_EVENT( .item = &s_all_day_item );
CHECK_NO_EVENTS( .count = 2, .is_future_empty = true );
}
void test_timeline_peek_event__weather_event(void) {
ADD_EVENT( .item = &s_weather_item );
CHECK_EVENT( .count = 2, .item_id = s_weather_item.header.id, .num_concurrent = 0,
.timeout_ms = s_weather_item.header.timestamp * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
}
void test_timeline_peek_event__concurrent_count_and_priority(void) {
// Test that num_concurrent increases accordingly
// Also test that upcoming items take priority
ADD_EVENT( .item = &s_item1 );
CHECK_EVENT( .count = 2, .item_id = s_item1.header.id, .num_concurrent = 0,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
ADD_EVENT( .item = &s_item2 );
CHECK_EVENT( .count = 3, .item_id = s_item2.header.id, .num_concurrent = 1,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart );
ADD_EVENT( .item = &s_item3 );
CHECK_EVENT( .count = 4, .item_id = s_item3.header.id, .num_concurrent = 2,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart );
// The future item is too far to increase the concurrent count
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 5, .item_id = s_item3.header.id, .num_concurrent = 2,
.timeout_ms = SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart );
}
void test_timeline_peek_event__before_upcoming_event(void) {
// Check that the event is about an upcoming item
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 2, .item_id = s_future_item.header.id, .num_concurrent = 0,
.timeout_ms =
((s_future_item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S) *
MS_PER_SECOND),
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__before_upcoming_event_custom_5min(void) {
// Check that the event is about an upcoming item at a custom 5min timeout
const unsigned int show_before_time_s = 5 * SECONDS_PER_MINUTE;
timeline_peek_set_show_before_time(show_before_time_s);
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 3, .item_id = s_future_item.header.id, .num_concurrent = 0,
.timeout_ms = ((s_future_item.header.timestamp - show_before_time_s) *
MS_PER_SECOND),
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__before_event_starts(void) {
// Check that the event is about an item that is about to start
rtc_set_time(s_future_item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S / 2);
ADD_EVENT( .item = &s_future_item );
CHECK_EVENT( .count = 2, .item_id = s_future_item.header.id, .num_concurrent = 0,
.timeout_ms = (TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S * MS_PER_SECOND) / 2,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
}
void test_timeline_peek_event__after_event_starts(void) {
// Check that the event is about an item about to pass the hide time
rtc_set_time(5 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item1 );
CHECK_EVENT( .count = 2, .item_id = s_item1.header.id, .num_concurrent = 0,
.timeout_ms = ((TIMELINE_PEEK_HIDE_AFTER_TIME_S -
(rtc_get_time() - s_item1.header.timestamp)) * MS_PER_SECOND),
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
}
void test_timeline_peek_event__after_event_starts_short_event(void) {
// Check that for a short event, the timeout is the end of the item instead
rtc_set_time(10 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item3 );
CHECK_EVENT( .count = 2, .item_id = s_item3.header.id, .num_concurrent = 0,
.timeout_ms = 4 * SECONDS_PER_MINUTE * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
}
void test_timeline_peek_event__after_event_passed_hide_time(void) {
// Check that there is no event if the last item passed the hide time
rtc_set_time(15 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item2 );
CHECK_NO_EVENTS( .count = 2 );
}
void test_timeline_peek_event__after_event_passed_completely(void) {
rtc_set_time(30 * SECONDS_PER_MINUTE);
ADD_EVENT( .item = &s_item2 );
CHECK_NO_EVENTS( .count = 2, .is_future_empty = true );
}
void test_timeline_peek_event__dismiss_event(void) {
// Check that dismissing the last event causes no events to peek
TimelineItem *item = &s_future_item;
rtc_set_time(item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S / 2);
ADD_EVENT( .item = item );
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = (TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S * MS_PER_SECOND) / 2,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
// Simulate a timeline peek dismiss
cl_must_pass(pin_db_set_status_bits(&item->header.id, TimelineItemStatusDismissed));
timeline_event_refresh();
CHECK_NO_EVENTS( .count = 3 );
}
void test_timeline_peek_event__first_event_with_past_event(void) {
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 70 );
TimelineItem UNUSED item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = -50 * SECONDS_PER_MINUTE, .duration = 30 );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__first_event_with_all_day_event_before(void) {
// All day events show up if no timed event has yet passed
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 70 );
TimelineItem UNUSED item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = 0, .duration = MINUTES_PER_DAY, .all_day = true );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext );
}
void test_timeline_peek_event__first_event_with_all_day_event_after(void) {
// After a timed event has passed, all day events no longer show up for the day
rtc_set_time(SECONDS_PER_HOUR);
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = SECONDS_PER_HOUR + 20 * SECONDS_PER_MINUTE,
.duration = 70 );
TimelineItem UNUSED item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = 0, .duration = MINUTES_PER_DAY, .all_day = true );
TimelineItem UNUSED item3 =
DEFINE_EVENT( .id = 0x03, .timestamp = 0, .duration = 10 );
unsigned int timeout_s = 600;
CHECK_EVENT( .count = 4, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
}
void test_timeline_peek_event__one_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted, None
TimelineItem *item = &s_future_item;
ADD_EVENT( .item = item );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 5 );
}
void test_timeline_peek_event__one_short_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted, None
TimelineItem *item = &s_short_future_item;
ADD_EVENT( .item = item );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = item->header.duration * SECONDS_PER_MINUTE;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 5 );
}
void test_timeline_peek_event__0_duration_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted, None
TimelineItem *item = &s_point_item;
ADD_EVENT( .item = item );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 4 );
}
void test_timeline_peek_event__one_recurring_event_lifecycle(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted
TimelineItem *item = &s_recurring_calendar_item2;
ADD_EVENT( .item = &s_recurring_calendar_item1 );
ADD_EVENT( .item = item );
ADD_EVENT( .item = &s_recurring_calendar_item3 );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 6, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = (SECONDS_PER_DAY - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S -
TIMELINE_PEEK_HIDE_AFTER_TIME_S);
CHECK_EVENT( .count = 7, .item_id = s_recurring_calendar_item3.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext );
}
void test_timeline_peek_event__two_back_to_back_events(void) {
// Check that one event progresses through SomeTimeNext, WillStart, ShowStarted
TimelineItem *item = &s_back_to_back_calendar_item1;
ADD_EVENT( .item = item );
ADD_EVENT( .item = &s_back_to_back_calendar_item2 );
unsigned int timeout_s = item->header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
item = &s_back_to_back_calendar_item2;
CHECK_EVENT( .count = 6, .item_id = item->header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext );
}
void test_timeline_peek_event__one_persistent_event_lifecycle(void) {
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 30,
.persistent = true );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 2, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = item.header.duration * SECONDS_PER_MINUTE - TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 6, .is_future_empty = true );
}
void test_timeline_peek_event__upcoming_priotized_over_persistent_event_lifecycle(void) {
TimelineItem item =
DEFINE_EVENT( .id = 0x01, .timestamp = 20 * SECONDS_PER_MINUTE, .duration = 70,
.persistent = true );
TimelineItem item2 =
DEFINE_EVENT( .id = 0x02, .timestamp = 50 * SECONDS_PER_MINUTE, .duration = 30 );
unsigned int timeout_s = item.header.timestamp - TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 3, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_SomeTimeNext, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 4, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 5, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = 10 * SECONDS_PER_MINUTE; // time until the next event
CHECK_EVENT( .count = 6, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S;
CHECK_EVENT( .count = 7, .item_id = item2.header.id, .num_concurrent = 1,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowWillStart, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = TIMELINE_PEEK_HIDE_AFTER_TIME_S;
CHECK_EVENT( .count = 8, .item_id = item2.header.id, .num_concurrent = 1,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted, .is_first_event = true );
prv_invoke_timer(timeout_s);
timeout_s = 30 * SECONDS_PER_MINUTE; // time until persistent event ends
CHECK_EVENT( .count = 9, .item_id = item.header.id, .num_concurrent = 0,
.timeout_ms = timeout_s * MS_PER_SECOND,
.time_type = TimelinePeekTimeType_ShowStarted );
prv_invoke_timer(timeout_s);
CHECK_NO_EVENTS( .count = 10, .is_future_empty = true );
}

View file

@ -0,0 +1,359 @@
/*
* 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 "process_management/app_install_manager.h"
#include "resource/resource_ids.auto.h"
#include "services/normal/timeline/timeline_resources.h"
#include "system/passert.h"
#include "util/struct.h"
// Stubs
/////////////////////////
#include "stubs_kino_reel.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
// Test Data
/////////////////////////
typedef enum TimelineResourceTestAppTimelineId {
// We start at 1 because TIMELINE_RESOURCE_INVALID = 0
TimelineResourceTestTimelineId_AlarmClock = 1,
TimelineResourceTestTimelineId_Basketball,
TimelineResourceTestTimelineIdCount
} TimelineResourceTestTimelineId;
static const uint32_t s_app_lut[TimelineResourceTestTimelineIdCount][TimelineResourceSizeCount] = {
[TIMELINE_RESOURCE_INVALID] = {
RESOURCE_ID_INVALID, RESOURCE_ID_INVALID, RESOURCE_ID_INVALID
},
[TimelineResourceTestTimelineId_AlarmClock] = {
RESOURCE_ID_ALARM_CLOCK_TINY, RESOURCE_ID_ALARM_CLOCK_SMALL, RESOURCE_ID_ALARM_CLOCK_LARGE
},
[TimelineResourceTestTimelineId_Basketball] = {
RESOURCE_ID_BASKETBALL_TINY, RESOURCE_ID_BASKETBALL_SMALL, RESOURCE_ID_BASKETBALL_LARGE
},
};
typedef struct TimelineResourceTestAppData {
AppInstallEntry install_entry;
const uint32_t (*resource_lut)[TimelineResourceSizeCount];
} TimelineResourceTestAppData;
typedef enum TimelineResourceTestAppId {
// We start from 1 because INSTALL_ID_INVALID = 0
TimelineResourceTestAppId_AppWithInvalidLUT = 1,
TimelineResourceTestAppId_AppWithInvalidSDKVersion,
TimelineResourceTestAppId_ValidApp,
TimelineResourceTestAppIdInvalid,
TimelineResourceTestAppIdCount = TimelineResourceTestAppIdInvalid - 1
} TimelineResourceTestAppId;
static const TimelineResourceTestAppData s_test_apps[TimelineResourceTestAppIdCount] = {
{
.install_entry = {
.install_id = TimelineResourceTestAppId_AppWithInvalidLUT,
.uuid = {0x3c, 0x6e, 0x2e, 0x1d, 0x61, 0x7d, 0x4d, 0x17,
0x97, 0xa1, 0xbc, 0x43, 0x2d, 0x87, 0x4c, 0xed},
.sdk_version = {TIMELINE_RESOURCE_PBW_SUPPORT_FIRST_SDK_VERSION_MAJOR,
TIMELINE_RESOURCE_PBW_SUPPORT_FIRST_SDK_VERSION_MINOR},
},
// No resource_lut specified because this app has an "invalid" lut
},
{
.install_entry = {
.install_id = TimelineResourceTestAppId_AppWithInvalidSDKVersion,
.uuid = {0x37, 0xe7, 0x64, 0x5e, 0xd, 0x6a, 0x41, 0xfe,
0xb8, 0x80, 0xea, 0x47, 0x5a, 0x5f, 0x34, 0x34},
// We set the SDK version to one earlier than the first version supporting timeline resources
.sdk_version = {TIMELINE_RESOURCE_PBW_SUPPORT_FIRST_SDK_VERSION_MAJOR,
TIMELINE_RESOURCE_PBW_SUPPORT_FIRST_SDK_VERSION_MINOR - 1},
},
.resource_lut = s_app_lut,
},
{
.install_entry = {
.install_id = TimelineResourceTestAppId_ValidApp,
.uuid = {0x9e, 0x95, 0x8b, 0xfe, 0xd, 0xbd, 0x4d, 0xf2,
0xbe, 0xbc, 0xf3, 0x77, 0x5d, 0x8d, 0x9f, 0x95},
.sdk_version = {TIMELINE_RESOURCE_PBW_SUPPORT_FIRST_SDK_VERSION_MAJOR,
TIMELINE_RESOURCE_PBW_SUPPORT_FIRST_SDK_VERSION_MINOR},
},
.resource_lut = s_app_lut,
},
};
static const TimelineResourceTestAppData *prv_get_data_for_app_with_id(AppInstallId install_id) {
for (int i = 0; i < TimelineResourceTestAppIdCount; i++) {
const TimelineResourceTestAppData *data = &s_test_apps[i];
if (data->install_entry.install_id == install_id) {
return data;
}
}
return NULL;
}
static const TimelineResourceTestAppData *prv_get_data_for_app_with_uuid(const Uuid *uuid) {
for (int i = 0; i < TimelineResourceTestAppIdCount; i++) {
const TimelineResourceTestAppData *data = &s_test_apps[i];
if (uuid_equal(uuid, &data->install_entry.uuid)) {
return data;
}
}
return NULL;
}
// Fakes
/////////////////////////
bool prv_validate_lut(ResAppNum res_app_num) {
// Just check if the .resource_lut pointer for the provided res_app_num is non-NULL
const TimelineResourceTestAppData *data = prv_get_data_for_app_with_id(res_app_num);
return data ? (data->resource_lut != NULL) : false;
}
uint32_t prv_get_app_resource_id(ResAppNum res_app_num, TimelineResourceId timeline_id,
TimelineResourceSize size) {
// Size must be valid
if (size >= TimelineResourceSizeCount) {
return RESOURCE_ID_INVALID;
}
// This only supports valid non-system apps
const TimelineResourceTestAppData *data = prv_get_data_for_app_with_id(res_app_num);
if (!data) {
return RESOURCE_ID_INVALID;
}
// The app must have a valid LUT
if (!data->resource_lut) {
return RESOURCE_ID_INVALID;
}
return data->resource_lut[timeline_id][size];
}
static bool s_is_app_published_resource_invalid;
bool prv_is_app_published_resource_valid(const AppResourceInfo *res_info) {
return !s_is_app_published_resource_invalid;
}
AppInstallId app_install_get_id_for_uuid(const Uuid *uuid) {
const TimelineResourceTestAppData *data = prv_get_data_for_app_with_uuid(uuid);
return NULL_SAFE_FIELD_ACCESS(data, install_entry.install_id, INSTALL_ID_INVALID);
}
bool app_install_get_entry_for_install_id(AppInstallId install_id, AppInstallEntry *entry) {
if (!entry) {
return false;
}
const TimelineResourceTestAppData *data = prv_get_data_for_app_with_id(install_id);
if (!data) {
return false;
}
*entry = data->install_entry;
return true;
}
ResAppNum app_install_get_app_icon_bank(const AppInstallEntry *entry) {
PBL_ASSERTN(entry);
if (uuid_equal(&entry->uuid, &(Uuid)UUID_SYSTEM)) {
return SYSTEM_APP;
} else {
return entry->install_id;
}
}
// Setup
/////////////////////////
void test_timeline_resources__initialize(void) {
s_is_app_published_resource_invalid = false;
}
// Tests
/////////////////////////
void test_timeline_resources__get_id_system(void) {
AppResourceInfo res_info;
// Calling the function with an invalid TimelineResourceId should return false
cl_assert(!timeline_resources_get_id_system(TIMELINE_RESOURCE_INVALID, TimelineResourceSizeTiny,
TimelineResourceTestAppId_ValidApp, &res_info));
// Calling the function with an invalid size should return false
cl_assert(!timeline_resources_get_id_system(
(TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock, TimelineResourceSizeCount,
TimelineResourceTestAppId_ValidApp, &res_info));
// Calling the function with the ResAppNum of an app with an invalid LUT should return false
cl_assert(!timeline_resources_get_id_system(
(TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock, TimelineResourceSizeTiny,
TimelineResourceTestAppId_AppWithInvalidLUT, &res_info));
// Calling the function for an invalid resource (e.g. dimensions too large) should return false
s_is_app_published_resource_invalid = true;
cl_assert(!timeline_resources_get_id_system(
(TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock, TimelineResourceSizeTiny,
TimelineResourceTestAppId_ValidApp, &res_info));
s_is_app_published_resource_invalid = false;
// Calling the function with valid args should return true and set the correct values in res_info
cl_assert(timeline_resources_get_id_system(
(TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock, TimelineResourceSizeTiny,
TimelineResourceTestAppId_ValidApp, &res_info));
cl_assert_equal_i(res_info.res_app_num, TimelineResourceTestAppId_ValidApp);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_ALARM_CLOCK_TINY);
// Calling the function with valid args should return true even if no AppResourceInfo is provided
cl_assert(timeline_resources_get_id_system(
(TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock, TimelineResourceSizeTiny,
TimelineResourceTestAppId_ValidApp, NULL));
// Calling the function with a valid system TimelineResourceId should return true and set res_info
cl_assert(timeline_resources_get_id_system(TIMELINE_RESOURCE_AUDIO_CASSETTE,
TimelineResourceSizeSmall, SYSTEM_APP, &res_info));
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_AUDIO_CASSETTE_SMALL);
// Even if the provided ResAppNum != SYSTEM_APP
cl_assert(timeline_resources_get_id_system(TIMELINE_RESOURCE_AUDIO_CASSETTE,
TimelineResourceSizeSmall, TIMELINE_RESOURCE_INVALID,
&res_info));
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_AUDIO_CASSETTE_SMALL);
}
void test_timeline_resources__get_id(void) {
TimelineResourceInfo timeline_res_info;
AppResourceInfo res_info;
const TimelineResourceTestAppData *valid_app_data =
prv_get_data_for_app_with_id(TimelineResourceTestAppId_ValidApp);
PBL_ASSERTN(valid_app_data);
// Calling the function with an invalid TimelineResourceId should set res_info to the fallback
timeline_res_info = (TimelineResourceInfo) {
.app_id = &valid_app_data->install_entry.uuid,
.res_id = TIMELINE_RESOURCE_INVALID,
.fallback_id = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
};
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeLarge, &res_info);
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_BIRTHDAY_EVENT_LARGE);
// Set the TimelineResourceInfo to valid values
timeline_res_info = (TimelineResourceInfo) {
.app_id = &valid_app_data->install_entry.uuid,
.res_id = (TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock,
.fallback_id = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
};
// Calling the function with an invalid size, no TimelineResourceInfo, or no AppResourceInfo
// should assert
cl_assert_passert(timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeCount,
&res_info));
cl_assert_passert(timeline_resources_get_id(NULL, TimelineResourceSizeTiny, &res_info));
cl_assert_passert(timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeTiny, NULL));
// Set the TimelineResourceInfo to have the UUID of an app with an invalid LUT
const TimelineResourceTestAppData *app_with_invalid_lut_data =
prv_get_data_for_app_with_id(TimelineResourceTestAppId_AppWithInvalidLUT);
PBL_ASSERTN(app_with_invalid_lut_data);
timeline_res_info = (TimelineResourceInfo) {
.app_id = &app_with_invalid_lut_data->install_entry.uuid,
.res_id = (TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock,
.fallback_id = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
};
// Calling the function with the UUID of an app with an invalid LUT should set res_info to the
// fallback
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeLarge, &res_info);
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_BIRTHDAY_EVENT_LARGE);
// Set the TimelineResourceInfo to valid values
timeline_res_info = (TimelineResourceInfo) {
.app_id = &valid_app_data->install_entry.uuid,
.res_id = (TimelineResourceId)TimelineResourceTestTimelineId_AlarmClock,
.fallback_id = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
};
// Calling the function for an invalid resource (e.g. dimensions too large) should set res_info
// to the fallback
s_is_app_published_resource_invalid = true;
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeLarge, &res_info);
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_BIRTHDAY_EVENT_LARGE);
s_is_app_published_resource_invalid = false;
// Calling the function with valid args should return true and set the correct values in res_info
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeLarge, &res_info);
cl_assert_equal_i(res_info.res_app_num, TimelineResourceTestAppId_ValidApp);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_ALARM_CLOCK_LARGE);
// Set the TimelineResourceInfo to have the UUID of an app with an unsupported SDK version
const TimelineResourceTestAppData *app_with_invalid_sdk_version =
prv_get_data_for_app_with_id(TimelineResourceTestAppId_AppWithInvalidSDKVersion);
PBL_ASSERTN(app_with_invalid_sdk_version);
timeline_res_info = (TimelineResourceInfo) {
.app_id = &app_with_invalid_sdk_version->install_entry.uuid,
.res_id = (TimelineResourceId)TimelineResourceTestTimelineId_Basketball,
.fallback_id = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
};
// Calling the function with the UUID of an app with an unsupported SDK version should set
// res_info to the fallback
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeTiny, &res_info);
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_BIRTHDAY_EVENT_TINY);
// Set the TimelineResourceInfo to valid values but with a system TimelineResourceId requested
timeline_res_info = (TimelineResourceInfo) {
.app_id = &(Uuid)UUID_SYSTEM,
.res_id = TIMELINE_RESOURCE_HOTEL_RESERVATION,
.fallback_id = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
};
// Calling the function with a valid system TimelineResourceId should set the correct values in
// res_info
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeSmall, &res_info);
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_HOTEL_RESERVATION_SMALL);
// Even if the provided app UUID != UUID_SYSTEM
timeline_res_info.app_id = &valid_app_data->install_entry.uuid;
timeline_resources_get_id(&timeline_res_info, TimelineResourceSizeSmall, &res_info);
cl_assert_equal_i(res_info.res_app_num, SYSTEM_APP);
cl_assert_equal_i(res_info.res_id, RESOURCE_ID_HOTEL_RESERVATION_SMALL);
}
void test_timeline_resources__is_system(void) {
// System TimelineResourceIds should return true
cl_assert(timeline_resources_is_system(TIMELINE_RESOURCE_AUDIO_CASSETTE));
// Others should return false
cl_assert(!timeline_resources_is_system(TIMELINE_RESOURCE_INVALID));
cl_assert(!timeline_resources_is_system(NUM_TIMELINE_RESOURCES));
}

View file

@ -0,0 +1,226 @@
from waftools.pebble_test import clar
def build(ctx):
clar(ctx,
sources_ant_glob = \
" src/fw/util/legacy_checksum.c" \
" tests/fakes/fake_spi_flash.c" \
" src/fw/util/rand/rand.c" \
" src/fw/vendor/tinymt32/tinymt32.c" \
" src/fw/flash_region/flash_region.c" \
" src/fw/util/time/time.c" \
" src/fw/flash_region/filesystem_regions.c" \
" src/fw/services/normal/blob_db/timeline_item_storage.c" \
" src/fw/services/normal/settings/settings_file.c" \
" src/fw/services/normal/settings/settings_raw_iter.c" \
" src/fw/services/normal/filesystem/flash_translation.c" \
" src/fw/services/normal/filesystem/pfs.c" \
" src/fw/services/normal/blob_db/reminder_db.c" \
" src/fw/services/normal/timeline/attribute.c" \
" src/fw/services/normal/timeline/item.c" \
" src/fw/services/normal/timeline/attributes_actions.c" \
" src/fw/services/normal/timeline/attribute_group.c" \
" src/fw/services/normal/timeline/reminders.c",
test_sources_ant_glob = "test_reminders.c",
override_includes=['dummy_board'])
def build(ctx):
clar(ctx,
sources_ant_glob=(
"src/fw/services/normal/blob_db/pin_db.c "
"src/fw/services/normal/blob_db/timeline_item_storage.c "
"src/fw/services/normal/timeline/attribute.c "
"src/fw/services/normal/timeline/attribute_group.c "
"src/fw/services/normal/timeline/attributes_actions.c "
"src/fw/services/normal/timeline/calendar.c "
"src/fw/services/normal/timeline/event.c "
"src/fw/services/normal/timeline/item.c "
"src/fw/services/normal/timeline/timeline.c "
"src/fw/util/crc8.c "
"src/fw/util/time/time.c "
"tests/fakes/fake_events.c "
"tests/fakes/fake_rtc.c "
"tests/fakes/fake_settings_file.c "
),
test_sources_ant_glob="test_calendar.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(
"src/fw/services/normal/blob_db/pin_db.c "
"src/fw/services/normal/blob_db/timeline_item_storage.c "
"src/fw/services/normal/timeline/attribute.c "
"src/fw/services/normal/timeline/attribute_group.c "
"src/fw/services/normal/timeline/attributes_actions.c "
"src/fw/services/normal/timeline/event.c "
"src/fw/services/normal/timeline/item.c "
"src/fw/services/normal/timeline/peek.c "
"src/fw/services/normal/timeline/timeline.c "
"src/fw/util/crc8.c "
"src/fw/util/time/time.c "
"tests/fakes/fake_events.c "
"tests/fakes/fake_rtc.c "
"tests/fakes/fake_settings_file.c "
),
test_sources_ant_glob = "test_timeline_peek_event.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = \
" src/fw/services/normal/timeline/attribute.c",
test_sources_ant_glob = "test_attribute.c")
clar(ctx,
sources_ant_glob = \
" src/fw/util/time/time.c" \
" src/fw/util/time/mktime.c" \
" src/fw/util/crc8.c" \
" src/fw/util/legacy_checksum.c" \
" tests/fakes/fake_spi_flash.c" \
" src/fw/util/rand/rand.c" \
" src/fw/vendor/tinymt32/tinymt32.c" \
" tests/fakes/fake_rtc.c" \
" src/fw/flash_region/filesystem_regions.c" \
" src/fw/flash_region/flash_region.c" \
" src/fw/services/normal/blob_db/timeline_item_storage.c" \
" src/fw/services/normal/settings/settings_file.c" \
" src/fw/services/normal/settings/settings_raw_iter.c" \
" src/fw/services/normal/filesystem/flash_translation.c" \
" src/fw/services/normal/filesystem/pfs.c" \
" src/fw/services/normal/timeline/attribute.c" \
" src/fw/services/normal/timeline/item.c" \
" src/fw/services/normal/timeline/attributes_actions.c" \
" src/fw/services/normal/timeline/attribute_group.c" \
" src/fw/services/normal/timeline/timeline.c" \
" src/fw/services/normal/blob_db/pin_db.c" \
" src/fw/apps/system_apps/timeline/timeline_model.c",
test_sources_ant_glob = "test_timeline_model.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(
" src/fw/process_management/pebble_process_info.c"
" src/fw/services/normal/timeline/timeline_resources.c"
" tests/fixtures/resources/timeline_resource_table.auto.c"
),
test_sources_ant_glob = "test_timeline_resources.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = \
" src/fw/services/normal/alarms/alarm.c" \
" src/fw/services/normal/timeline/alarm_layout.c" \
" src/fw/services/normal/timeline/attribute.c",
test_sources_ant_glob = "test_alarm_layout.c",
defines=["CAPABILITY_HAS_HEALTH_TRACKING=0"],
override_includes=['dummy_board'],
platforms=['snowy', 'spalding'])
rendering_sources = \
" src/fw/applib/fonts/codepoint.c" \
" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c" \
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c" \
" src/fw/applib/graphics/bitblt.c" \
" src/fw/applib/graphics/framebuffer.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gbitmap_png.c" \
" src/fw/applib/graphics/gbitmap_sequence.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gdraw_command.c" \
" src/fw/applib/graphics/gdraw_command_frame.c" \
" src/fw/applib/graphics/gdraw_command_image.c" \
" src/fw/applib/graphics/gdraw_command_list.c" \
" src/fw/applib/graphics/gdraw_command_sequence.c" \
" src/fw/applib/graphics/gpath.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/graphics_bitmap.c" \
" src/fw/applib/graphics/graphics_circle.c" \
" src/fw/applib/graphics/graphics_line.c" \
" src/fw/applib/graphics/graphics_private.c" \
" src/fw/applib/graphics/graphics_private_raw.c" \
" src/fw/applib/graphics/gtransform.c" \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/perimeter.c" \
" src/fw/applib/graphics/text_layout.c" \
" src/fw/applib/graphics/text_render.c" \
" src/fw/applib/graphics/text_resources.c" \
" src/fw/applib/graphics/utf8.c" \
" src/fw/applib/ui/action_button.c" \
" src/fw/applib/ui/animation_interpolate.c" \
" src/fw/applib/ui/content_indicator.c" \
" src/fw/applib/ui/inverter_layer.c" \
" src/fw/applib/ui/kino/kino_layer.c" \
" src/fw/applib/ui/kino/kino_player.c" \
" src/fw/applib/ui/kino/kino_reel.c" \
" src/fw/applib/ui/kino/kino_reel/transform.c" \
" src/fw/applib/ui/kino/kino_reel_custom.c" \
" src/fw/applib/ui/kino/kino_reel_gbitmap.c" \
" src/fw/applib/ui/kino/kino_reel_gbitmap_sequence.c" \
" src/fw/applib/ui/kino/kino_reel_pdci.c" \
" src/fw/applib/ui/kino/kino_reel_pdcs.c" \
" src/fw/applib/ui/layer.c" \
" src/fw/applib/ui/menu_layer.c" \
" src/fw/applib/ui/menu_layer_system_cells.c" \
" src/fw/applib/ui/scroll_layer.c" \
" src/fw/applib/ui/shadows.c" \
" src/fw/applib/ui/status_bar_layer.c" \
" src/fw/applib/ui/text_layer.c" \
" src/fw/applib/ui/text_layer_flow.c" \
" src/fw/applib/ui/window.c" \
" src/fw/applib/vendor/tinflate/tinflate.c" \
" src/fw/applib/vendor/uPNG/upng.c" \
" src/fw/apps/system_apps/timeline/text_node.c" \
" src/fw/board/displays/display_spalding.c" \
" src/fw/drivers/flash/flash_crc.c" \
" src/fw/flash_region/filesystem_regions.c" \
" src/fw/flash_region/flash_region.c" \
" src/fw/resource/resource.c" \
" src/fw/resource/resource_storage.c" \
" src/fw/resource/resource_storage_builtin.c" \
" src/fw/resource/resource_storage_file.c" \
" src/fw/resource/resource_storage_flash.c" \
" src/fw/services/normal/filesystem/app_file.c" \
" src/fw/services/normal/filesystem/flash_translation.c" \
" src/fw/services/normal/filesystem/pfs.c" \
" src/fw/system/hexdump.c" \
" src/fw/util/buffer.c" \
" src/fw/util/crc8.c" \
" src/fw/util/legacy_checksum.c" \
" src/fw/util/stringlist.c" \
" src/fw/util/time/time.c" \
" tests/fakes/fake_applib_resource.c" \
" tests/fakes/fake_clock.c" \
" tests/fakes/fake_display.c" \
" tests/fakes/fake_fonts.c" \
" tests/fakes/fake_gbitmap_get_data_row.c" \
" tests/fakes/fake_rtc.c" \
" tests/fakes/fake_spi_flash.c" \
" tests/fixtures/resources/builtin_resources.auto.c" \
" tests/fixtures/resources/pfs_resource_table.c" \
" tests/fixtures/resources/timeline_resource_table.auto.c" \
" tests/stubs/stubs_animation.c"
clar(ctx,
sources_ant_glob=(
rendering_sources +
" src/fw/apps/system_apps/timeline/pin_window.c"
" src/fw/popups/timeline/timeline_item_layer.c"
" src/fw/services/normal/timeline/attribute.c"
" src/fw/services/normal/timeline/generic_layout.c"
" src/fw/services/normal/timeline/layout_layer.c"
" src/fw/services/normal/timeline/layout_node.c"
" src/fw/services/normal/timeline/timeline_layout.c"
" src/fw/services/normal/timeline/timeline_resources.c"
" src/fw/services/normal/timeline/weather_layout.c"
" src/fw/services/normal/weather/weather_types.c"
" src/fw/shell/system_theme.c"
" tests/stubs/stubs_clock.c"
" tests/stubs/stubs_timeline_layout.c"
),
test_sources_ant_glob = "test_timeline_layouts.c",
defines=ctx.env.test_image_defines + ["USE_DISPLAY_PERIMETER_ON_FONT_LAYOUT=1"],
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis + ctx.env.test_pfos,
override_includes=['dummy_board'],
platforms=['snowy', 'spalding', 'silk', 'robert'])
# vim:filetype=python