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,333 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/bluetooth/ble_ad_parse.h"
#include "system/hexdump.h"
#include "clar.h"
#include <btutil/bt_uuid.h>
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_ble_syscalls.h"
#include "stubs_ble_syscalls.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_serial.h"
// The test data and descriptions in this file are captured using the FrontLine
// Bluetooth sniffer.
static const size_t s_buffer_size = sizeof(BLEAdData) +
(2 * GAP_LE_AD_REPORT_DATA_MAX_LENGTH);
static uint8_t s_buffer[s_buffer_size];
static BLEAdData * const s_ad_data = (BLEAdData *)s_buffer;
static void set_ad_data(uint8_t *data, size_t length) {
memcpy(s_ad_data->data, data, length);
s_ad_data->ad_data_length = length;
}
void test_ble_ad_parse__initialize(void) {
memset(s_ad_data, 0, sizeof(s_buffer_size));
}
// -----------------------------------------------------------------------------
// Consuming BLEAdData:
// -----------------------------------------------------------------------------
void test_ble_ad_parse__16_bit_uuid_and_device_name(void) {
// AD Element, Length: 2, AD Type: Flags (0x1a)
// AD Element, Length: 3, AD Type: Complete list of 16-bit UUID, [0x7b29]
// AD Element, Length: 10, AD Type: Complete local name, Text: LightBlue
uint8_t data[] =
"\x02\x01\x1a\x03\x03\x29\x7b\x0a\x09\x4c\x69\x67\x68\x74\x42\x6c\x75\x65";
set_ad_data(data, sizeof(data));
// Test ble_ad_get_raw_data_size:
cl_assert_equal_i(ble_ad_get_raw_data_size(s_ad_data), sizeof(data));
// Test ble_ad_copy_raw_data:
uint8_t buffer[GAP_LE_AD_REPORT_DATA_MAX_LENGTH * 2];
size_t size = ble_ad_copy_raw_data(s_ad_data, buffer, sizeof(buffer));
cl_assert_equal_i(size, sizeof(data));
cl_assert_equal_i(memcmp(buffer, data, sizeof(data)), 0);
// Test ble_ad_copy_local_name, destination buffer large enough:
char local_name[64];
size = ble_ad_copy_local_name(s_ad_data, local_name, 64);
cl_assert_equal_s(local_name, "LightBlue");
cl_assert_equal_i(size, strlen("LightBlue") + 1);
// Test ble_ad_copy_local_name, destination buffer too small:
size = ble_ad_copy_local_name(s_ad_data, local_name, 6);
cl_assert_equal_s(local_name, "Light");
cl_assert_equal_i(size, strlen("Light") + 1);
// Test ble_ad_includes_service:
Uuid included_uuid = bt_uuid_expand_16bit(0x7b29);
cl_assert(ble_ad_includes_service(s_ad_data, &included_uuid));
Uuid missing_uuid = bt_uuid_expand_16bit(0xabcd);
cl_assert(!ble_ad_includes_service(s_ad_data, &missing_uuid));
// Test ble_ad_copy_service_uuids, destination array sized larged enough:
const uint8_t count = 4;
Uuid copied_uuids[count];
uint8_t found = ble_ad_copy_service_uuids(s_ad_data, copied_uuids, count);
cl_assert_equal_i(found, 1);
// Test ble_ad_copy_service_uuids, destination array too small:
found = ble_ad_copy_service_uuids(s_ad_data, copied_uuids, 0);
cl_assert_equal_i(found, 1);
// Test ble_ad_get_tx_power_level returns false, when no TX Power Level:
int8_t tx_power_level_out;
cl_assert(!ble_ad_get_tx_power_level(s_ad_data, &tx_power_level_out));
}
void test_ble_ad_parse__128_bit_uuid(void) {
// AD Element, Length: 2, AD Type: Flags
// AD Element, Length: 17, AD Type: More 128-bit UUIDs available,
// Value: 0x68753a444d6f12269c600050e4c00067
uint8_t data[GAP_LE_AD_REPORT_DATA_MAX_LENGTH] =
"\x02\x01\x1a\x11\x06\x67\x00\xc0\xe4\x50\x00\x60\x9c\x26\x12\x6f\x4d\x44" \
"\x3a\x75\x68";
set_ad_data(data, sizeof(data));
// Test ble_ad_includes_service:
uint8_t uuid_bytes[] = "\x68\x75\x3a\x44\x4d\x6f\x12\x26\x9c\x60\x00\x50" \
"\xe4\xc0\x00\x67";
Uuid included_uuid = UuidMakeFromBEBytes(uuid_bytes);
cl_assert(ble_ad_includes_service(s_ad_data, &included_uuid));
Uuid missing_uuid = bt_uuid_expand_16bit(0xabcd);
cl_assert(!ble_ad_includes_service(s_ad_data, &missing_uuid));
}
// -----------------------------------------------------------------------------
// Creating BLEAdData:
// -----------------------------------------------------------------------------
void test_ble_ad_parse__ad_and_scan_resp_boundaries(void) {
}
void test_ble_ad_parse__start_scan_response(void) {
BLEAdData *ad = ble_ad_create();
ble_ad_start_scan_response(ad);
uint8_t expected_scan_resp_data[] = {
1 /* +1 for Type byte */ + strlen("Pebble 1234"),
0x09, // Local Name, Complete
'P', 'e', 'b', 'b', 'l', 'e', ' ', '1', '2', '3', '4'
};
// Should fit fine, expect true:
cl_assert_equal_b(ble_ad_set_local_name(ad, "Pebble 1234"), true);
// Expect no advertisement data:
cl_assert_equal_i(ad->ad_data_length, 0);
// Expect scan response data:
cl_assert_equal_i(ad->scan_resp_data_length, sizeof(expected_scan_resp_data));
// Compare scan response data:
cl_assert_equal_i(memcmp(expected_scan_resp_data,
ad->data + ad->ad_data_length,
ad->scan_resp_data_length), 0);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_service_uuids_128_bit(void) {
BLEAdData *ad = ble_ad_create();
uint8_t uuid_bytes[] = "\x97\x6e\xbb\x18\xd3\xe9\x43\xc0\x8a\x63\x8d\x2b" \
"\x60\xd9\x04\x2a";
Uuid uuid[2];
uuid[0] = UuidMakeFromBEBytes(uuid_bytes);
uuid[1] = UuidMakeFromLEBytes(uuid_bytes); // just reversed, laziness
// 2x 128-bit UUIDs is not going to fit, expect false:
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 2), false);
// Hand-construct expected raw advertisement data:
uint8_t expected_ad_data[sizeof(Uuid) + 2 /* +1 Length, +1 Type bytes */] = {
sizeof(Uuid) + 1 /* +1 for Type byte */,
0x07, // Service UUIDs, 128-bit, Complete
};
memcpy(&expected_ad_data[2], uuid_bytes, sizeof(Uuid));
// One should fit though:
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 1), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data,
sizeof(expected_ad_data)), 0);
cl_assert_equal_i(ad->ad_data_length, sizeof(expected_ad_data));
cl_assert_equal_i(ad->scan_resp_data_length, 0);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_service_uuids_32_bit(void) {
BLEAdData *ad;
Uuid uuid[8];
for (int i = 0; i < 8; ++i) {
uuid[i] = bt_uuid_expand_32bit(0x12346700 + i);
}
// Hand-construct expected raw advertisement data:
uint8_t expected_ad_data[] = {
(2 * sizeof(uint32_t)) + 1 /* +1 for Type byte */,
0x05, // Service UUIDs, 32-bit, Complete
0x00, 0x67, 0x34, 0x12, // Little endian
0x01, 0x67, 0x34, 0x12,
};
// 2x 32-bit UUIDs should fit fine, expect true:
ad = ble_ad_create();
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 2), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data,
sizeof(expected_ad_data)), 0);
cl_assert_equal_i(ad->ad_data_length, sizeof(expected_ad_data));
cl_assert_equal_i(ad->scan_resp_data_length, 0);
ble_ad_destroy(ad);
// 7x 32-bit UUIDs should fit, expect true:
ad = ble_ad_create();
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 7), true);
ble_ad_destroy(ad);
// 8x 32-bit UUIDs does not fit, expect false:
ad = ble_ad_create();
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 8), false);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_service_uuids_16_bit(void) {
BLEAdData *ad;
Uuid uuid[15];
for (int i = 0; i < 15; ++i) {
uuid[i] = bt_uuid_expand_16bit(0x1800 + i);
}
// Hand-construct expected raw advertisement data:
uint8_t expected_ad_data[] = {
(2 * sizeof(uint16_t)) + 1 /* +1 for Type byte */,
0x03, // Service UUIDs, 16-bit, Complete
0x00, 0x18, // Little endian
0x01, 0x18,
};
// 2x 16-bit UUIDs should fit fine, expect true:
ad = ble_ad_create();
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 2), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data,
sizeof(expected_ad_data)), 0);
cl_assert_equal_i(ad->ad_data_length, sizeof(expected_ad_data));
cl_assert_equal_i(ad->scan_resp_data_length, 0);
ble_ad_destroy(ad);
// 14x 16-bit UUIDs should fit, expect true:
ad = ble_ad_create();
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 14), true);
ble_ad_destroy(ad);
// 15x 16-bit UUIDs does not fit, expect false:
ad = ble_ad_create();
cl_assert_equal_b(ble_ad_set_service_uuids(ad, uuid, 15), false);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_local_name(void) {
BLEAdData *ad;
ad = ble_ad_create();
uint8_t expected_ad_data[] = {
1 /* +1 for Type byte */ + strlen("Pebble 1234"),
0x09, // Local Name, Complete
'P', 'e', 'b', 'b', 'l', 'e', ' ', '1', '2', '3', '4'
};
// Should fit fine, expect true:
cl_assert_equal_b(ble_ad_set_local_name(ad, "Pebble 1234"), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data, ad->ad_data_length), 0);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_tx_power_level(void) {
BLEAdData *ad;
ad = ble_ad_create();
uint8_t expected_ad_data[] = {
1 /* +1 for Type byte */ + 1 /* int8_t with value */,
0x0a, // TX Power Level
-55,
};
// Should fit fine, expect true:
cl_assert_equal_b(ble_ad_set_tx_power_level(ad), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data, ad->ad_data_length), 0);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_manufacturer_specific_data(void) {
BLEAdData *ad;
ad = ble_ad_create();
uint8_t expected_ad_data[] = {
1 /* +1 for Type byte */ + 13 /* Company ID + data */,
0xff, // Manufacturer Specific data
0x34, 0x12,
'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
};
// Should fit fine, expect true:
cl_assert_equal_b(ble_ad_set_manufacturer_specific_data(ad,
0x1234,
(uint8_t *) "hello world",
11), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data, ad->ad_data_length), 0);
ble_ad_destroy(ad);
}
void test_ble_ad_parse__set_flags(void) {
BLEAdData *ad;
ad = ble_ad_create();
const uint8_t flags = 0x03;
const uint8_t expected_ad_data[] = {
1 /* +1 for Type byte */ + 1 /* uint8_t with value */,
0x01, // Flags type
flags,
};
// Should fit fine, expect true:
cl_assert_equal_b(ble_ad_set_flags(ad, flags), true);
cl_assert_equal_i(memcmp(expected_ad_data, ad->data, ad->ad_data_length), 0);
ble_ad_destroy(ad);
}

View file

@ -0,0 +1,132 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/bluetooth/ble_ibeacon.h"
#include <btutil/bt_device.h>
#include "clar.h"
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_rand_ptr.h"
#include "stubs_ble_syscalls.h"
// The test data and descriptions in this file are captured using the FrontLine
// Bluetooth sniffer.
// AD Element, Length: 26, AD Type: Manufacturer Specific, Manufacturer ID:
// Apple, Inc. (0x004c) Additional Data: 0x 02 15 97 6e bb 18 d3 e9 43 c0 8a
// 63 8d 2b 60 d9 04 2a 00 0c 00 22 c5
// This excludes the "Manufacturer Specific" opcode (0xff) but it
// includes the Apple Inc company ID:
static BLEAdData *create_apple_ibeacon_ad_data(void) {
static const uint8_t apple_ibeacon_ad_element[] =
{
0x1a, // 26 bytes
0xff, // Manufacturer Specific AD Type
0x4c, 0x00, // Apple
0x02, // iBeacon
0x15, // Number of bytes to follow
0x97, 0x6e, 0xbb, 0x18, 0xd3, 0xe9, 0x43, 0xc0, // Proximity UUID
0x8a, 0x63, 0x8d, 0x2b, 0x60, 0xd9, 0x04, 0x2a,
0x00, 0x0c, // Minor (BE)
0x00, 0x22, // Major (BE)
0xc5 // TX Power
};
const size_t ad_data_length = sizeof(apple_ibeacon_ad_element);
BLEAdData *ad_data = (BLEAdData *) malloc(sizeof(BLEAdData) + ad_data_length);
ad_data->ad_data_length = ad_data_length;
ad_data->scan_resp_data_length = 0;
memcpy(ad_data->data, apple_ibeacon_ad_element, ad_data_length);
return ad_data;
}
void test_ble_ibeacon__parse_ibeacon_data(void) {
BLEAdData *apple_ibeacon_ad_data = create_apple_ibeacon_ad_data();
BLEiBeacon ibeacon;
const int8_t rssi = -60;
bool is_ibeacon = ble_ibeacon_parse(apple_ibeacon_ad_data, rssi, &ibeacon);
cl_assert(is_ibeacon);
uint8_t uuid_bytes[] = "\x97\x6e\xbb\x18\xd3\xe9\x43\xc0\x8a\x63\x8d\x2b" \
"\x60\xd9\x04\x2a";
Uuid uuid = UuidMakeFromBEBytes(uuid_bytes);
cl_assert(uuid_equal(&ibeacon.uuid, &uuid));
cl_assert_equal_i(ibeacon.major, 12);
cl_assert_equal_i(ibeacon.minor, 34);
cl_assert_equal_i(ibeacon.rssi, -60);
cl_assert_equal_i(ibeacon.calibrated_tx_power, -59);
// cl_assert_equal_i(ibeacon.distance_cm, 110);
free(apple_ibeacon_ad_data);
}
void test_ble_ibeacon__ibeacon_compose(void) {
BLEAdData *apple_ibeacon_ad_data = create_apple_ibeacon_ad_data();
BLEiBeacon ibeacon;
const int8_t rssi = -60;
ble_ibeacon_parse(apple_ibeacon_ad_data, rssi, &ibeacon);
BLEAdData *new_ibeacon_ad_data = ble_ad_create();
cl_assert_equal_b(ble_ibeacon_compose(&ibeacon, new_ibeacon_ad_data), true);
const size_t ad_data_size = apple_ibeacon_ad_data->ad_data_length +
apple_ibeacon_ad_data->scan_resp_data_length;
cl_assert_equal_i(new_ibeacon_ad_data->ad_data_length,
apple_ibeacon_ad_data->ad_data_length);
cl_assert_equal_i(new_ibeacon_ad_data->scan_resp_data_length,
apple_ibeacon_ad_data->scan_resp_data_length);
cl_assert_equal_i(memcmp(new_ibeacon_ad_data->data,
apple_ibeacon_ad_data->data,
ad_data_size), 0);
free(apple_ibeacon_ad_data);
ble_ad_destroy(new_ibeacon_ad_data);
}
static BLEAdData *create_too_short_ad_data(void) {
static const uint8_t too_short_ad_element[] =
{ 0x1a, // 26 bytes
0xff, // Manufacturer Specific AD Type
0x4c, 0x00, // Apple
0x02, // iBeacon
0x14, // Number of bytes to follow -- Internally inconsistent!
0x97, 0x6e, 0xbb, 0x18, 0xd3, 0xe9, 0x43, 0xc0, // Proximity UUID
0x8a, 0x63, 0x8d, 0x2b, 0x60, 0xd9, 0x04, 0x2a,
0x00, 0x0c, // Minor (BE)
0x00, 0x22, // Major (BE)
0xc5 // TX Power
};
BLEAdData *ad_data = (BLEAdData *) malloc(sizeof(BLEAdData) +
sizeof(too_short_ad_element));
ad_data->ad_data_length = sizeof(too_short_ad_element);
ad_data->scan_resp_data_length = 0;
return ad_data;
}
void test_ble_ibeacon__ibeacon_data_too_short(void) {
BLEAdData *too_short_to_ibeacon = create_too_short_ad_data();
bool is_ibeacon = ble_ibeacon_parse(too_short_to_ibeacon, 0, NULL);
cl_assert(!is_ibeacon);
free(too_short_to_ibeacon);
}

View file

@ -0,0 +1,23 @@
from waftools.pebble_test import clar
def build(ctx):
clar(ctx,
sources_ant_glob =
"src/fw/applib/bluetooth/ble_ad_parse.c "
"src/fw/util/rand/rand.c "
"src/fw/vendor/tinymt32/tinymt32.c "
"src/fw/system/hexdump.c "
"src/fw/util/buffer.c "
"tests/fakes/fake_rtc.c",
test_sources_ant_glob = "test_ble_ad_parse.c")
clar(ctx,
sources_ant_glob =
"src/fw/applib/bluetooth/ble_ibeacon.c "
"src/fw/applib/bluetooth/ble_ad_parse.c "
"src/fw/util/rand/rand.c "
"src/fw/vendor/tinymt32/tinymt32.c "
"tests/fakes/fake_rtc.c",
test_sources_ant_glob = "test_ble_ibeacon.c")
# vim:filetype=python

View file

@ -0,0 +1,319 @@
/*
* 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 "applib/app_glance.h"
#include "drivers/rtc.h"
#include "kernel/pbl_malloc.h"
#include "process_management/app_install_manager.h"
#include "resource/resource_ids.auto.h"
#include "resource/timeline_resource_ids.auto.h"
#include "services/normal/app_glances/app_glance_service.h"
#include "services/normal/blob_db/app_glance_db.h"
#include "services/normal/blob_db/app_glance_db_private.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/timeline/timeline_resources.h"
#include "util/uuid.h"
#define APP_GLANCE_TEST_UUID \
(UuidMake(0x3d, 0xc6, 0xb9, 0x4c, 0x4, 0x2, 0x48, 0xf4, \
0xbe, 0x14, 0x81, 0x17, 0xf1, 0xa, 0xa9, 0xc4))
// Fakes
////////////////////////////////////////////////////////////////
#include "fake_rtc.h"
#include "fake_settings_file.h"
void sys_get_app_uuid(Uuid *uuid) {
if (uuid) {
*uuid = APP_GLANCE_TEST_UUID;
}
}
typedef struct AppGlanceTestState {
bool resource_is_valid;
void *context;
bool reload_callback_was_called;
} AppGlanceTestState;
static AppGlanceTestState s_test_state;
ResAppNum sys_get_current_resource_num(void) {
return 0;
}
void sys_timeline_resources_get_id(const TimelineResourceInfo *timeline_res,
TimelineResourceSize size, AppResourceInfo *res_info) {
if (!res_info) {
return;
}
// Just fill the output resource ID with some number so it's considered "valid"
res_info->res_id = s_test_state.resource_is_valid ? 1337 : 0;
}
// Stubs
////////////////////////////////////////////////////////////////
#include "stubs_app_cache.h"
#include "stubs_app_install_manager.h"
#include "stubs_app_state.h"
#include "stubs_events.h"
#include "stubs_event_service_client.h"
#include "stubs_i18n.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
status_t pfs_remove(const char *name) {
fake_settings_file_reset();
return S_SUCCESS;
}
// Setup
////////////////////////////////////////////////////////////////
void test_app_glance__initialize(void) {
fake_rtc_init(0, 1337);
fake_settings_file_reset();
app_glance_db_init();
app_glance_service_init();
s_test_state = (AppGlanceTestState) {};
}
void app_glance_db_deinit(void);
void test_app_glance__cleanup(void) {
app_glance_db_deinit();
}
void prv_basic_reload_cb(AppGlanceReloadSession *session, size_t limit, void *context) {
s_test_state.reload_callback_was_called = true;
s_test_state.resource_is_valid = true;
AppGlanceSlice slice = (AppGlanceSlice) {
.expiration_time = rtc_get_time() + 10,
.layout.icon = TIMELINE_RESOURCE_HOTEL_RESERVATION,
.layout.subtitle_template_string = "Test subtitle",
};
cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS);
slice = (AppGlanceSlice) {
.expiration_time = rtc_get_time() + 20,
.layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON,
};
cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS);
}
void test_app_glance__basic_reload(void) {
// Reload the glance with two slices
app_glance_reload(prv_basic_reload_cb, s_test_state.context);
cl_assert_equal_b(s_test_state.reload_callback_was_called, true);
// Read the glance back
AppGlance glance = {};
cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &glance), S_SUCCESS);
// Compare the glance read back with the expected glance below
AppGlance expected_glance = (AppGlance) {
.num_slices = 2,
.slices = {
{
.expiration_time = rtc_get_time() + 10,
.type = AppGlanceSliceType_IconAndSubtitle,
.icon_and_subtitle.icon_resource_id = TIMELINE_RESOURCE_HOTEL_RESERVATION,
.icon_and_subtitle.template_string = "Test subtitle",
},
{
.expiration_time = rtc_get_time() + 20,
.type = AppGlanceSliceType_IconAndSubtitle,
.icon_and_subtitle.icon_resource_id = APP_GLANCE_SLICE_DEFAULT_ICON,
}
},
};
cl_assert_equal_m(&glance, &expected_glance, sizeof(AppGlance));
}
void prv_reload_with_validation_cb(AppGlanceReloadSession *session, size_t limit, void *context) {
s_test_state.reload_callback_was_called = true;
// Check that the context here is the context we passed to `app_glance_reload()`
cl_assert_equal_p(context, s_test_state.context);
// Check that the limit passed in matches the max slices per glance
cl_assert_equal_i(limit, APP_GLANCE_DB_MAX_SLICES_PER_GLANCE);
unsigned int num_slices_added = 0;
AppGlanceSlice slice = {};
// Check that using a bogus session variable fails
AppGlanceReloadSession bogus_session;
cl_assert(app_glance_add_slice(&bogus_session, slice) & APP_GLANCE_RESULT_INVALID_SESSION);
// Check that adding a slice with APP_GLANCE_SLICE_DEFAULT_ICON as the icon succeeds
slice = (AppGlanceSlice) {
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION,
.layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON,
.layout.subtitle_template_string = "Test subtitle {time_until(500)|format('%uS')}",
};
cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS);
num_slices_added++;
// Check that adding a slice with a NULL subtitle succeeds
s_test_state.resource_is_valid = true;
slice = (AppGlanceSlice) {
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION,
.layout.icon = TIMELINE_RESOURCE_BIRTHDAY_EVENT,
.layout.subtitle_template_string = NULL,
};
cl_assert_equal_i(app_glance_add_slice(session, slice), APP_GLANCE_RESULT_SUCCESS);
num_slices_added++;
// Check that adding a slice with an invalid icon fails
s_test_state.resource_is_valid = false;
slice = (AppGlanceSlice) {
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION,
.layout.icon = RESOURCE_ID_SETTINGS_ICON_AIRPLANE,
.layout.subtitle_template_string = "Test subtitle",
};
cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_INVALID_ICON);
// Check that adding a slice with a subtitle that's too long fails
const char *really_long_subtitle = "This is a really really really really really really really "
"really really really really really really really really "
"really really really really really really really really "
"really really really really really really really really "
"really long subtitle.";
slice = (AppGlanceSlice) {
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION,
.layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON,
.layout.subtitle_template_string = really_long_subtitle,
};
cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG);
// Check that adding a slice with a bad template string fails
const char *invalid_template_subtitle = "How much time? {time_until(500)|format('%uS',)}";
slice = (AppGlanceSlice) {
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION,
.layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON,
.layout.subtitle_template_string = invalid_template_subtitle,
};
cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_INVALID_TEMPLATE_STRING);
// Check that adding a slice that expires in the past fails
slice = (AppGlanceSlice) {
.expiration_time = rtc_get_time() - 10,
.layout.icon = APP_GLANCE_SLICE_DEFAULT_ICON,
.layout.subtitle_template_string = "Test subtitle",
};
cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST);
// At this point we've actually filled up the glance to the capacity
cl_assert_equal_i(num_slices_added, limit);
// So adding one more slice to the glance should fail
s_test_state.resource_is_valid = true;
slice = (AppGlanceSlice) {
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION,
.layout.subtitle_template_string = NULL,
};
cl_assert(app_glance_add_slice(session, slice) & APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED);
// Check that we can get reports of multiple kinds of failures at the same time
s_test_state.resource_is_valid = false;
slice = (AppGlanceSlice) {
.expiration_time = rtc_get_time() - 10,
.layout.icon = RESOURCE_ID_SETTINGS_ICON_AIRPLANE,
.layout.subtitle_template_string = really_long_subtitle,
};
const AppGlanceResult result = app_glance_add_slice(session, slice);
cl_assert(result & APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST);
cl_assert(result & APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED);
cl_assert(result & APP_GLANCE_RESULT_INVALID_ICON);
cl_assert(result & APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG);
}
void test_app_glance__reload_with_validation_callback(void) {
app_glance_reload(prv_reload_with_validation_cb, s_test_state.context);
cl_assert_equal_b(s_test_state.reload_callback_was_called, true);
}
static void prv_glance_clear_test(AppGlanceReloadCallback reload_cb) {
// Insert some slices for the glance
const AppGlance glance = (AppGlance) {
.num_slices = 2,
.slices = {
{
.expiration_time = 1464734504, // (Tue, 31 May 2016 22:41:44 GMT)
.type = AppGlanceSliceType_IconAndSubtitle,
.icon_and_subtitle = {
.template_string = "Test subtitle 2",
},
},
{
.expiration_time = 1464734484, // (Tue, 31 May 2016 22:41:24 GMT)
.type = AppGlanceSliceType_IconAndSubtitle,
.icon_and_subtitle = {
.template_string = "Test subtitle 1",
},
},
},
};
cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, &glance), S_SUCCESS);
// Request the current slice for this glance; this should match the earliest-expiring slice in
// the glance we just inserted above
AppGlanceSliceInternal slice_out;
cl_assert_equal_b(app_glance_service_get_current_slice(&APP_GLANCE_TEST_UUID, &slice_out), true);
cl_assert_equal_m(&slice_out, &glance.slices[1], sizeof(slice_out));
// Let some time "pass" so that the creation time of this next reload doesn't get ignored
fake_rtc_increment_time(10);
// Reload the glance using the provided callback; this should empty the slices in the glance
app_glance_reload(reload_cb, NULL);
// Read the glance back and check that it doesn't have any slices anymore
AppGlance glance_read = {};
cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &glance_read), S_SUCCESS);
cl_assert_equal_i(glance_read.num_slices, 0);
for (unsigned int i = 0; i < sizeof(glance_read.slices); i++) {
const uint8_t byte = ((uint8_t *)glance_read.slices)[i];
cl_assert_equal_i(byte, 0);
}
// Request the current slice for this glance again; this should return false since there aren't
// any slices in the glance anymore
cl_assert_equal_b(app_glance_service_get_current_slice(&APP_GLANCE_TEST_UUID, &slice_out), false);
}
void test_app_glance__reload_with_null_callback_empties_slices(void) {
prv_glance_clear_test(NULL);
}
static void prv_reload_with_no_slices_added_cb(AppGlanceReloadSession *session, size_t limit,
void *context) {
// We don't add any slices in this callback on purpose
return;
}
void test_app_glance__reload_with_no_slices_added_empties_slices(void) {
prv_glance_clear_test(prv_reload_with_no_slices_added_cb);
}

View file

@ -0,0 +1,599 @@
/*
* 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 "applib/app_inbox.h"
#include "kernel/events.h"
#include "services/normal/app_inbox_service.h"
#include "util/list.h"
extern bool app_inbox_service_has_inbox_for_tag(AppInboxServiceTag tag);
extern bool app_inbox_service_has_inbox_for_storage(uint8_t *storage);
extern bool app_inbox_service_is_being_written_for_tag(AppInboxServiceTag tag);
extern size_t app_inbox_service_num_failed_for_tag(AppInboxServiceTag tag);
extern size_t app_inbox_service_num_success_for_tag(AppInboxServiceTag tag);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fakes & Stubs
#include "fake_kernel_malloc.h"
#include "fake_pebble_tasks.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_syscall_internal.h"
#define BUFFER_SIZE (32)
#define NOT_PERMITTED_MSG_HANDLER ((AppInboxMessageHandler)~0)
#define NOT_PERMITTED_DROP_HANDLER ((AppInboxDroppedHandler)~0)
#define TEST_TARGET_TASK (PebbleTask_App)
typedef struct {
ListNode node;
PebbleEvent event;
} EventNode;
static EventNode *s_event_head;
static bool s_can_send_event;
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) {
cl_assert_equal_i(PEBBLE_CALLBACK_EVENT, e->type);
cl_assert_equal_i(task, TEST_TARGET_TASK);
if (s_can_send_event) {
EventNode *node = (EventNode *)malloc(sizeof(EventNode));
*node = (const EventNode) {
.event = *e,
};
s_event_head = (EventNode *)list_prepend((ListNode *)s_event_head, (ListNode *)node);
}
return s_can_send_event;
}
static void prv_process_callback_events_alt(bool should_execute_callback) {
EventNode *node = s_event_head;
while (node) {
EventNode *next = (EventNode *) node->node.next;
if (should_execute_callback) {
node->event.callback.callback(node->event.callback.data);
}
free(node);
node = next;
}
s_event_head = NULL;
}
static void prv_process_callback_events(void) {
prv_process_callback_events_alt(true /* should_execute_callback */);
}
static void prv_cleanup_callback_events(void) {
prv_process_callback_events_alt(false /* should_execute_callback */);
}
#define assert_num_callback_events(num) \
cl_assert_equal_i(list_count((ListNode *)s_event_head), num);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Inbox Service Stubs
void app_message_receiver_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {
}
void app_message_receiver_dropped_handler(uint32_t num_dropped_messages) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test Inbox Service Handlers
#define TEST_ARRAY_SIZE (4)
static int s_message_idx;
static struct {
uint8_t data[BUFFER_SIZE];
size_t length;
} s_messages[TEST_ARRAY_SIZE];
static int s_num_messages_to_consume_from_handler;
void test_message_handler(const uint8_t *data, size_t length, AppInboxConsumerInfo *consumer_info) {
cl_assert(s_message_idx < TEST_ARRAY_SIZE);
s_messages[s_message_idx].length = length;
memcpy(s_messages[s_message_idx].data, data, length);
++s_message_idx;
if (s_num_messages_to_consume_from_handler--) {
app_inbox_consume(consumer_info);
}
}
#define assert_message(idx, dd, ll) \
{ \
cl_assert(idx <= s_message_idx); \
cl_assert_equal_i(ll, s_messages[idx].length); \
cl_assert_equal_m(dd, s_messages[idx].data, ll); \
}
#define assert_num_message_callbacks(num_cbs) \
{ \
cl_assert_equal_i(num_cbs, s_message_idx); \
}
static int s_dropped_idx;
static uint32_t s_dropped_messages[TEST_ARRAY_SIZE];
void test_dropped_handler(uint32_t num_dropped_messages) {
cl_assert(s_dropped_idx < TEST_ARRAY_SIZE);
s_dropped_messages[s_dropped_idx++] = num_dropped_messages;
}
#define assert_dropped(idx, num) \
{ \
cl_assert(idx <= s_dropped_idx); \
cl_assert_equal_i(num, s_dropped_messages[idx]); \
}
#define assert_num_dropped_callbacks(num_cbs) \
{ \
cl_assert_equal_i(num_cbs, s_dropped_idx); \
}
void test_alt_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {
cl_assert(false);
}
void test_alt_dropped_handler(uint32_t num_dropped_messages) {
cl_assert(false);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Tests
void test_app_inbox__initialize(void) {
fake_kernel_malloc_init();
fake_kernel_malloc_enable_stats(true);
stub_pebble_tasks_set_current(TEST_TARGET_TASK);
s_num_messages_to_consume_from_handler = 0;
s_can_send_event = true;
s_dropped_idx = 0;
memset(s_dropped_messages, 0, sizeof(s_dropped_messages));
s_message_idx = 0;
memset(s_messages, 0, sizeof(s_messages));
}
void test_app_inbox__cleanup(void) {
app_inbox_service_unregister_all();
fake_kernel_malloc_deinit();
prv_cleanup_callback_events();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_create_and_register
void test_app_inbox__app_inbox_create_and_register_zero_buffer_size(void) {
void *result = app_inbox_create_and_register(0, 1, test_message_handler, test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_zero_min_num_messages(void) {
void *result = app_inbox_create_and_register(BUFFER_SIZE, 0,
test_message_handler, test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_null_message_handler(void) {
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1, NULL, test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_oom(void) {
// FIXME: No support for OOM simulation in applib_.. stub/fake
return;
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler,
test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
void test_app_inbox__app_inbox_create_and_register_msg_handler_not_permitted(void) {
// The syscall_failed() fake will trigger passert:
cl_assert_passert(app_inbox_create_and_register(BUFFER_SIZE, 1,
NOT_PERMITTED_MSG_HANDLER,
test_dropped_handler));
}
void test_app_inbox__app_inbox_create_and_register_drop_handler_not_permitted(void) {
// The syscall_failed() fake will trigger passert:
cl_assert_passert(app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler,
NOT_PERMITTED_DROP_HANDLER));
}
void test_app_inbox__app_inbox_create_and_register_happy_case(void) {
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler, test_dropped_handler);
cl_assert(result != NULL);
cl_assert_equal_b(true, app_inbox_service_has_inbox_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_create_and_register_kernel_oom(void) {
fake_kernel_malloc_set_largest_free_block(0);
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler,
test_dropped_handler);
cl_assert_equal_p(result, NULL);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_register
void test_app_inbox__app_inbox_create_and_register_storage_already_associated(void) {
bool success;
uint8_t storage[BUFFER_SIZE];
success = app_inbox_service_register(storage, sizeof(storage),
test_message_handler, test_dropped_handler,
AppInboxServiceTagUnitTest);
cl_assert_equal_b(success, true);
cl_assert_equal_b(true, app_inbox_service_has_inbox_for_storage(storage));
cl_assert_equal_b(true, app_inbox_service_has_inbox_for_tag(AppInboxServiceTagUnitTest));
fake_kernel_malloc_mark();
success = app_inbox_service_register(storage, sizeof(storage),
test_alt_message_handler, test_alt_dropped_handler,
AppInboxServiceTagUnitTestAlt);
cl_assert_equal_b(success, false);
cl_assert_equal_b(false, app_inbox_service_has_inbox_for_tag(AppInboxServiceTagUnitTestAlt));
fake_kernel_malloc_mark_assert_equal();
}
void test_app_inbox__app_inbox_create_and_register_tag_already_associated(void) {
bool success;
uint8_t storage[BUFFER_SIZE];
success = app_inbox_service_register(storage, sizeof(storage),
test_message_handler, test_dropped_handler,
AppInboxServiceTagUnitTest);
cl_assert_equal_b(success, true);
fake_kernel_malloc_mark();
uint8_t storage_alt[BUFFER_SIZE];
success = app_inbox_service_register(storage_alt, sizeof(storage_alt),
test_alt_message_handler, test_alt_dropped_handler,
AppInboxServiceTagUnitTest /* same tag! */);
cl_assert_equal_b(success, false);
cl_assert_equal_b(false, app_inbox_service_has_inbox_for_storage(storage_alt));
fake_kernel_malloc_mark_assert_equal();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_begin
static void *s_writer = (void *) 0xaabbccdd;
static void *s_inbox;
static void prv_create_test_inbox(void) {
s_inbox = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler, test_dropped_handler);
cl_assert(s_inbox != NULL);
}
void test_app_inbox__app_inbox_service_begin_null_writer(void) {
prv_create_test_inbox();
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, NULL));
cl_assert_equal_b(false, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_begin_no_inbox(void) {
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(false, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_begin_already_being_written(void) {
prv_create_test_inbox();
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
// Call ...begin() again:
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(1, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_begin_not_enough_storage_space(void) {
prv_create_test_inbox();
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE + 1, s_writer));
cl_assert_equal_b(false, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(1, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
// Drop should be reported immediately (not after the next write finishes):
prv_process_callback_events();
assert_dropped(0, 1);
}
void test_app_inbox__app_inbox_service_begin_happy_case(void) {
prv_create_test_inbox();
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_is_being_written_for_tag(AppInboxServiceTagUnitTest));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_write / app_inbox_service_end
static const uint8_t s_test_data[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09,
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19
};
static void prv_create_test_inbox_and_begin_write(void) {
prv_create_test_inbox();
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
}
void test_app_inbox__app_inbox_service_write_inbox_closed_in_mean_time(void) {
prv_create_test_inbox_and_begin_write();
app_inbox_destroy_and_deregister(s_inbox);
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE));
}
void test_app_inbox__app_inbox_service_write_not_enough_space(void) {
prv_create_test_inbox_and_begin_write();
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE + 1));
// A continuation should also fail, even though there is enough space for it:
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, 1));
// After ending the write, expect num_failed to be incremented by one:
app_inbox_service_end(AppInboxServiceTagUnitTest);
cl_assert_equal_i(1, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(0, app_inbox_service_num_success_for_tag(AppInboxServiceTagUnitTest));
prv_process_callback_events();
assert_num_dropped_callbacks(1);
assert_num_message_callbacks(0);
}
void test_app_inbox__app_inbox_service_write_happy_case(void) {
prv_create_test_inbox_and_begin_write();
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE));
// After ending the write, expect num_success to be incremented by one:
app_inbox_service_end(AppInboxServiceTagUnitTest);
cl_assert_equal_i(1, app_inbox_service_num_success_for_tag(AppInboxServiceTagUnitTest));
cl_assert_equal_i(0, app_inbox_service_num_failed_for_tag(AppInboxServiceTagUnitTest));
prv_process_callback_events();
assert_message(0, s_test_data, BUFFER_SIZE);
assert_num_message_callbacks(1);
assert_num_dropped_callbacks(0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_service_cancel
void test_app_inbox__app_inbox_service_cancel(void) {
prv_create_test_inbox_and_begin_write();
// Start writing a message that occupies the complete buffer, then cancel it:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE));
app_inbox_service_cancel(AppInboxServiceTagUnitTest);
// No events expected:
assert_num_callback_events(0);
// The buffer should be completely available again:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
}
void test_app_inbox__app_inbox_service_cancel_non_existing_inbox(void) {
app_inbox_service_cancel(AppInboxServiceTagUnitTest);
// No events expected:
assert_num_callback_events(0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Consuming writes
void test_app_inbox__multiple_writes_while_consuming(void) {
prv_create_test_inbox_and_begin_write();
// Message 1:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Message 2:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest, 1, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// No space:
cl_assert_equal_b(false, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE + 1, s_writer));
// Shouldn't call ..._end() here because ..._begin() failed.
// Message 3:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest, 1, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
// ... still writing when event gets processed below
// Only one callback event scheduled:
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_callback_events(0);
// Expect 2 message callbacks and 1 drop callback:
assert_num_message_callbacks(2);
assert_message(0, s_test_data, 1);
assert_message(1, s_test_data, 1);
assert_num_dropped_callbacks(1);
assert_dropped(0, 1);
// Finish message 3, should be able to write (BUFFER_SIZE - 1) again,
// because the message 1 and 2 are consumed now:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data + 1,
BUFFER_SIZE - 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// One callback event scheduled:
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_callback_events(0);
// Expect 3rd message callbacks and still 1 drop callback (same as before):
assert_num_message_callbacks(3);
assert_message(2, s_test_data, BUFFER_SIZE);
assert_num_dropped_callbacks(1);
}
void test_app_inbox__multiple_writes_consume_from_message_handler(void) {
prv_create_test_inbox_and_begin_write();
// Message 1:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Message 2:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest, 1, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Only one callback event scheduled:
assert_num_callback_events(1);
s_num_messages_to_consume_from_handler = 1;
prv_process_callback_events();
assert_num_callback_events(0);
// Expect 2 message callbacks and 1 drop callback:
assert_num_message_callbacks(2);
assert_message(0, s_test_data, 1);
assert_message(1, s_test_data, 1);
// Should be able to write (BUFFER_SIZE) again,
// because the message 1 and 2 are consumed now:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
BUFFER_SIZE, s_writer));
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data,
BUFFER_SIZE));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// One callback event scheduled:
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_callback_events(0);
}
void test_app_inbox__consume_inbox_closed_in_mean_time(void) {
prv_create_test_inbox_and_begin_write();
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest, s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
cl_assert_equal_i(1, app_inbox_destroy_and_deregister(s_inbox));
assert_num_callback_events(1);
prv_process_callback_events();
assert_num_dropped_callbacks(0);
assert_num_message_callbacks(0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// app_inbox_destroy_and_deregister / app_inbox_service_unregister_by_storage
void test_app_inbox__app_inbox_destroy_and_deregister_cleans_up_kernel_heap(void) {
fake_kernel_malloc_mark();
void *result = app_inbox_create_and_register(BUFFER_SIZE, 1,
test_message_handler, test_dropped_handler);
cl_assert_equal_i(app_inbox_destroy_and_deregister(result), 0);
fake_kernel_malloc_mark_assert_equal();
}
void test_app_inbox__app_inbox_destroy_and_deregister_cleans_up_app_heap(void) {
// TODO: No allocation tracking ability in applib_... stub/fake :(
}
void test_app_inbox__app_inbox_service_end_inbox_closed_in_mean_time(void) {
prv_create_test_inbox_and_begin_write();
// Expect to return 1, because one message is being dropped, the currently written one:
cl_assert_equal_i(1, app_inbox_destroy_and_deregister(s_inbox));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_end_inbox_closed_in_mean_time_with_pending_success(void) {
prv_create_test_inbox_and_begin_write();
// One message:
cl_assert_equal_b(true, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, 1));
cl_assert_equal_b(true, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Begin another one:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
1, s_writer));
// Expect to return 2, because two messages are being dropped, the successful one that was not
// yet processed and the currently written one:
cl_assert_equal_i(2, app_inbox_destroy_and_deregister(s_inbox));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_end_inbox_closed_in_mean_time_with_pending_failure(void) {
prv_create_test_inbox_and_begin_write();
// One message, too large, so it should get dropped:
cl_assert_equal_b(false, app_inbox_service_write(AppInboxServiceTagUnitTest,
s_test_data, BUFFER_SIZE + 1));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
// Begin another one:
cl_assert_equal_b(true, app_inbox_service_begin(AppInboxServiceTagUnitTest,
1, s_writer));
// Expect to return 2, because two messages are being dropped, the failed one that was not
// yet processed and the currently written one:
cl_assert_equal_i(2, app_inbox_destroy_and_deregister(s_inbox));
cl_assert_equal_b(false, app_inbox_service_end(AppInboxServiceTagUnitTest));
}
void test_app_inbox__app_inbox_service_unregister_by_storage_unknown_storage(void) {
uint8_t storage[BUFFER_SIZE];
cl_assert_equal_i(app_inbox_service_unregister_by_storage(storage), 0);
}

View file

@ -0,0 +1,802 @@
/*
* 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 "applib/app_message/app_message_internal.h"
#include "kernel/events.h"
#include "system/logging.h"
#include "util/attributes.h"
#include "util/math.h"
#include <stddef.h>
#include <limits.h>
extern AppTimer *app_message_outbox_get_ack_nack_timer(void);
// Stubs
////////////////////////////////////
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_rand_ptr.h"
#include "fake_pbl_malloc.h"
// Fakes
////////////////////////////////////
#include "fake_app_timer.h"
#include "fake_pebble_tasks.h"
// Structures and Externs
////////////////////////////////////
typedef struct PACKED {
AppMessageCmd command:8;
uint8_t transaction_id;
union PACKED {
struct PACKED {
Uuid uuid;
Dictionary dictionary; //!< Variable length!
} push; //!< valid for CMD_PUSH only
struct PACKED {} ack;
} payload[];
} AppMessage;
extern AppTimer *app_message_ack_timer_id(void);
extern bool app_message_is_accepting_inbound(void);
extern bool app_message_is_accepting_outbound(void);
extern bool app_message_is_closed_inbound(void);
extern bool app_message_is_closed_outbound(void);
extern void app_message_monitor_reset(void);
// Globals
////////////////////////////////////
static const uint16_t ENDPOINT_ID = 0x30;
static const uint16_t MAX_SIZE_INBOUND = 32;
static const uint16_t MAX_SIZE_OUTBOUND = 32;
static const char *TEST_DATA = "01234567890123456789012345678901234567890123456789"
"0123456789012345678901234567890123456789";
static const uint32_t TEST_KEY = 0xbeefbabe;
static const uint8_t TEST_TRANSACTION_ID_1 = 0x11; // msgs with this ID are asserted to be ack'd
static const uint8_t TEST_TRANSACTION_ID_2 = 0x22; // msgs with this ID are asserted to be nack'd
static const uint16_t MAX_DATA_SIZE = MAX_SIZE_OUTBOUND - sizeof(Dictionary) - sizeof(Tuple);
static int s_context;
static DictionaryIterator s_expected_iter;
uint8_t s_expected_buffer[MAX_SIZE_OUTBOUND];
static int s_out_sent_call_count = 0;
static int s_out_failed_call_count = 0;
static AppMessageResult s_failure_result = APP_MSG_OK;
static bool s_ack_sent_is_called = false;
static bool s_nack_sent_is_called = false;
static bool s_in_received_is_called = false;
static bool s_in_dropped_is_called = false;
static bool s_ack_received_for_id_1 = false;
static bool s_nack_received_for_id_2 = false;
static AppMessageResult s_dropped_reason = APP_MSG_OK;
static AppMessageCtx s_app_message_ctx;
typedef void (*RemoteReceiveHandler)(uint16_t endpoint_id,
const uint8_t* data, unsigned int length);
static RemoteReceiveHandler s_remote_receive_handler;
// UUID: 6bf6215b-c97f-409e-8c31-4f55657222b4
static Uuid simplicity_uuid = (Uuid){ 0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e,
0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb4 };
static CommSession *s_fake_app_comm_session = (CommSession *) 0xaabbccdd;
static bool s_is_connected;
static bool s_is_app_message_receiver_open;
static Uuid s_app_uuid;
static Uuid s_remote_app_uuid;
static bool s_app_receiver_oom;
// Utils
////////////////////////////////////
static void prv_set_app_uuid(Uuid uuid) {
s_app_uuid = uuid;
}
static void prv_set_remote_app_uuid(Uuid uuid) {
s_remote_app_uuid = uuid;
}
//! @note Assumes same order of tuples in both dictionaries!
static void prv_assert_dict_equal(DictionaryIterator *a, DictionaryIterator *b) {
Tuple *a_tuple = dict_read_first(a);
Tuple *b_tuple = dict_read_first(b);
while (b_tuple && a_tuple) {
cl_assert_equal_i(a_tuple->key, b_tuple->key);
cl_assert_equal_i(a_tuple->length, b_tuple->length);
cl_assert_equal_i(a_tuple->type, b_tuple->type);
cl_assert_equal_m(a_tuple->value, b_tuple->value, a_tuple->length);
a_tuple = dict_read_next(a);
b_tuple = dict_read_next(b);
}
if (b_tuple) {
cl_fail("Dictionary `B` contained more tuples than dictionary `A`.");
} else if (a_tuple) {
cl_fail("Dictionary `A` contained more tuples than dictionary `B`.");
}
}
// Callbacks
////////////////////////////////////
static void prv_out_sent_callback(DictionaryIterator *sent, void *context) {
s_out_sent_call_count++;
cl_assert_equal_p(context, &s_context);
prv_assert_dict_equal(sent, &s_expected_iter);
// When the outbox sent callback is called, the outbox should be in the
// ACCEPTING state again.
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
static void prv_out_failed_callback(DictionaryIterator *failed,
AppMessageResult reason, void *context) {
s_out_failed_call_count++;
cl_assert_equal_p(context, &s_context);
prv_assert_dict_equal(failed, &s_expected_iter);
s_failure_result = reason;
// When the outbox failed callback is called, the outbox should be in the
// ACCEPTING state again.
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
static void prv_in_received_callback(DictionaryIterator *received, void *context) {
cl_assert_equal_p(context, &s_context);
prv_assert_dict_equal(received, &s_expected_iter);
s_in_received_is_called = true;
}
static void prv_in_dropped_callback(AppMessageResult reason, void *context) {
cl_assert_equal_p(context, &s_context);
cl_assert_equal_b(s_in_dropped_is_called, false);
s_in_dropped_is_called = true;
s_dropped_reason = reason;
}
static void prv_send_ack_nack(uint16_t endpoint_id, const uint8_t* data,
unsigned int length, bool nack) {
const int o = offsetof(AppMessage, payload[0].push.dictionary);
cl_assert_equal_i(length, o + dict_calc_buffer_size(1, MAX_DATA_SIZE));
CommSession *session = s_fake_app_comm_session;
AppMessage *message = (AppMessage*)data;
AppMessage ack = {
.command = nack ? CMD_NACK : CMD_ACK,
.transaction_id = message->transaction_id,
};
if (endpoint_id == ENDPOINT_ID) {
app_message_app_protocol_msg_callback(session, (const uint8_t*)&ack, sizeof(AppMessage), NULL);
} else {
cl_fail("Unhandled endpoint");
}
}
static void prv_nack_sent_callback(uint16_t endpoint_id, const uint8_t* data, unsigned int length) {
s_nack_sent_is_called = true;
prv_send_ack_nack(endpoint_id, data, length, true);
}
static void prv_ack_sent_callback(uint16_t endpoint_id, const uint8_t* data, unsigned int length) {
s_ack_sent_is_called = true;
prv_send_ack_nack(endpoint_id, data, length, false);
}
static void prv_receive_test_data(uint8_t transaction_id, const bool oversized) {
const uint16_t dict_length = dict_calc_buffer_size(1, MAX_DATA_SIZE);
const uint16_t message_length = offsetof(AppMessage, payload[0].push.dictionary) +
+ dict_length + (oversized ? 20 : 0);
uint8_t buffer[message_length];
AppMessage *message = (AppMessage*)buffer;
message->command = CMD_PUSH;
message->transaction_id = transaction_id;
message->payload->push.uuid = s_remote_app_uuid;
memcpy(&message->payload->push.dictionary, s_expected_buffer, dict_length);
PBL_LOG(LOG_LEVEL_DEBUG, "message->transaction_id = %"PRIu32, message->transaction_id);
CommSession *session = s_fake_app_comm_session;
app_message_app_protocol_msg_callback(session, buffer, message_length, NULL);
}
static void prv_receive_ack_nack_callback(uint16_t endpoint_id,
const uint8_t* data, unsigned int length) {
AppMessage *message = (AppMessage*)data;
cl_assert(length == sizeof(AppMessage));
PBL_LOG(LOG_LEVEL_DEBUG, "message %"PRIu32", id1 %"PRIu32", id2 %"PRIu32, message->transaction_id,
TEST_TRANSACTION_ID_1, TEST_TRANSACTION_ID_2);
if (message->transaction_id == TEST_TRANSACTION_ID_1) {
cl_assert_equal_b(s_ack_received_for_id_1, false);
s_ack_received_for_id_1 = true;
cl_assert_equal_i(message->command, CMD_ACK);
} else if (message->transaction_id == TEST_TRANSACTION_ID_2) {
cl_assert_equal_b(s_nack_received_for_id_2, false);
s_nack_received_for_id_2 = true;
cl_assert_equal_i(message->command, CMD_NACK);
} else {
cl_assert(false);
}
}
static void prv_no_reply_callback(uint16_t endpoint_id,
const uint8_t* data, unsigned int length) {
}
// Overrides
///////////////////////////////////
bool sys_app_pp_has_capability(CommSessionCapability capability) {
return true;
}
static int s_sys_psleep_last_millis;
void sys_psleep(int millis) {
s_sys_psleep_last_millis = millis;
}
AppMessageCtx *app_state_get_app_message_ctx(void) {
return &s_app_message_ctx;
}
bool app_message_receiver_open(size_t buffer_size) {
if (s_app_receiver_oom) {
return false;
}
s_is_app_message_receiver_open = true;
return true;
}
void app_message_receiver_close(void) {
s_is_app_message_receiver_open = false;
}
size_t sys_app_pp_app_message_inbox_size_maximum(void) {
return 600;
}
void sys_app_pp_app_message_analytics_count_drop(void) {
}
bool sys_get_current_app_is_js_allowed(void) {
return false;
}
Version sys_get_current_app_sdk_version(void) {
return (Version) {};
}
static uint16_t s_sent_endpoint_id;
static uint8_t *s_sent_data;
static uint16_t s_sent_data_length;
void prv_send_data(uint16_t endpoint_id, const uint8_t* data, uint16_t length) {
const size_t header_size =
(uintptr_t)(((AppMessage *)0)->payload[0].push.dictionary.head[0].value->data);
const uint16_t max_length = (header_size + MAX_DATA_SIZE);
if (length > max_length) {
// Using cl_assert_equal_i for the nicer printing.
// when getting at this point, it will always trip:
cl_assert_equal_i(length, max_length);
}
cl_assert_equal_p(s_sent_data, NULL);
s_sent_data = kernel_malloc(length);
cl_assert(s_sent_data);
memcpy(s_sent_data, data, length);
s_sent_data_length = length;
s_sent_endpoint_id = endpoint_id;
}
bool sys_app_pp_send_data(CommSession *session, uint16_t endpoint_id,
const uint8_t* data, uint16_t length) {
if (!s_is_connected) {
return false;
}
prv_send_data(endpoint_id, data, length);
return true;
}
static AppOutboxSentHandler s_app_outbox_sent_handler;
static void *s_app_outbox_ctx;
static void prv_call_outbox_sent(int status) {
cl_assert(s_app_outbox_sent_handler);
s_app_outbox_sent_handler(status, s_app_outbox_ctx);
}
void app_outbox_send(const uint8_t *data, size_t length,
AppOutboxSentHandler sent_handler, void *cb_ctx) {
if (!s_is_connected) {
sent_handler(AppOutboxStatusConsumerDoesNotExist, cb_ctx);
return;
}
s_app_outbox_sent_handler = sent_handler;
s_app_outbox_ctx = cb_ctx;
AppMessageAppOutboxData *outbox_data = (AppMessageAppOutboxData *)data;
prv_send_data(outbox_data->endpoint_id,
outbox_data->payload, length - sizeof(AppMessageAppOutboxData));
}
static void prv_process_sent_data(void) {
if (!s_sent_data) {
return;
}
if (!s_is_connected) {
return;
}
if (!s_is_app_message_receiver_open) {
return;
}
cl_assert(s_remote_receive_handler);
s_remote_receive_handler(s_sent_endpoint_id, s_sent_data, s_sent_data_length);
kernel_free(s_sent_data);
s_sent_data = NULL;
}
void sys_get_app_uuid(Uuid *uuid) {
cl_assert(uuid);
*uuid = s_app_uuid;
}
static void (*s_process_manager_callback)(void *data);
static void *s_process_manager_callback_data;
void sys_current_process_schedule_callback(CallbackEventCallback async_cb, void *ctx) {
// Expecting the stub to be called only once durning a test:
cl_assert_equal_p(s_process_manager_callback, NULL);
cl_assert_equal_p(s_process_manager_callback_data, NULL);
s_process_manager_callback = async_cb;
s_process_manager_callback_data = ctx;
}
static int s_app_inbox_consume_call_count;
void app_inbox_consume(AppInboxConsumerInfo *consumer_info) {
++s_app_inbox_consume_call_count;
}
// Setup
////////////////////////////////////
void test_app_message__initialize(void) {
prv_set_app_uuid(simplicity_uuid);
prv_set_remote_app_uuid(simplicity_uuid);
fake_app_timer_init();
s_app_receiver_oom = false;
s_sys_psleep_last_millis = 0;
s_app_inbox_consume_call_count = 0;
app_message_init();
app_message_set_context(&s_context);
cl_assert_equal_i(app_message_open(MAX_SIZE_INBOUND, MAX_SIZE_OUTBOUND), APP_MSG_OK);
cl_assert_equal_p(app_message_register_outbox_sent(prv_out_sent_callback), NULL);
cl_assert_equal_p(app_message_register_outbox_failed(prv_out_failed_callback), NULL);
cl_assert_equal_p(app_message_register_inbox_dropped(prv_in_dropped_callback), NULL);
cl_assert_equal_p(app_message_register_inbox_received(prv_in_received_callback), NULL);
s_out_sent_call_count = 0;
s_out_failed_call_count = 0;
s_ack_sent_is_called = false;
s_nack_sent_is_called = false;
s_in_received_is_called = false;
s_in_dropped_is_called = false;
s_ack_received_for_id_1 = false;
s_nack_received_for_id_2 = false;
s_remote_receive_handler = NULL;
s_dropped_reason = APP_MSG_OK;
s_failure_result = APP_MSG_OK;
s_process_manager_callback = NULL;
s_process_manager_callback_data = NULL;
s_is_connected = true;
// Create the dictionary that is used to compare with what has been received:
dict_write_begin(&s_expected_iter, s_expected_buffer, MAX_SIZE_OUTBOUND);
cl_assert_equal_i(DICT_OK, dict_write_data(&s_expected_iter, TEST_KEY,
(const uint8_t*)TEST_DATA, MAX_DATA_SIZE));
dict_write_end(&s_expected_iter);
}
void test_app_message__cleanup(void) {
app_message_close();
cl_assert_equal_b(app_message_is_closed_inbound(), true);
cl_assert_equal_b(app_message_is_closed_outbound(), true);
fake_app_timer_deinit();
kernel_free(s_sent_data);
s_sent_data = NULL;
}
// Test OUTBOUND (watch->phone):
////////////////////////////////////
static void prv_send_test_data_expecting_result(AppMessageResult result) {
DictionaryIterator *iter;
cl_assert_equal_i(app_message_outbox_begin(&iter), APP_MSG_OK);
cl_assert_equal_i(dict_write_data(iter, TEST_KEY, (const uint8_t*)TEST_DATA, MAX_DATA_SIZE),
DICT_OK);
cl_assert_equal_i(app_message_outbox_send(), result);
}
static void prv_send_test_data(void) {
prv_send_test_data_expecting_result(APP_MSG_OK);
}
static void prv_set_remote_receive_handler(RemoteReceiveHandler handler) {
s_remote_receive_handler = handler;
}
void test_app_message__send_happy_case_outbox_sent_then_ack(void) {
prv_set_remote_receive_handler(prv_ack_sent_callback);
prv_send_test_data();
prv_call_outbox_sent(AppOutboxStatusSuccess);
prv_process_sent_data();
// After the ACK has been received, we should have been called
cl_assert_equal_b(s_ack_sent_is_called, true);
// Since that callback schedules another callback, we have to invoke
// system tasks again to get th actual callback to trigger.
cl_assert_equal_i(s_out_sent_call_count, 1);
// Check that the state is reset properly after everything
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__send_happy_case_ack_then_outbox_sent(void) {
prv_set_remote_receive_handler(prv_ack_sent_callback);
prv_send_test_data();
prv_process_sent_data();
// With certain PP transports (i.e. PPoGATT), the 'consuming' of the outbound data / outbox sent
// callback can fire after the AppMessage (N)ACK has been received.
cl_assert_equal_b(app_message_is_accepting_outbound(), false);
prv_call_outbox_sent(AppOutboxStatusSuccess);
// After the ACK has been received, we should have been called
cl_assert_equal_b(s_ack_sent_is_called, true);
// Since that callback schedules another callback, we have to invoke
// system tasks again to get th actual callback to trigger.
cl_assert_equal_i(s_out_sent_call_count, 1);
// Check that the state is reset properly after everything
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__cancel_timer(void) {
prv_set_remote_receive_handler(prv_ack_sent_callback);
prv_send_test_data();
prv_call_outbox_sent(AppOutboxStatusSuccess);
prv_process_sent_data();
// After the ACK has been received, we should have been called
cl_assert_equal_b(s_ack_sent_is_called, true);
// Check that we were called
cl_assert_equal_i(s_out_sent_call_count, 1);
// Timer should be invalid
cl_assert_equal_b(!fake_app_timer_is_scheduled(app_message_ack_timer_id()), true);
// Check the state is reset properly
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__send_ack_timeout(void) {
// We'll send the ack right after the timeout
prv_set_remote_receive_handler(prv_ack_sent_callback);
prv_send_test_data();
prv_call_outbox_sent(AppOutboxStatusSuccess);
// Fire the timeout and send the data
app_timer_trigger(app_message_ack_timer_id());
prv_process_sent_data();
cl_assert_equal_i(s_out_sent_call_count, 0);
cl_assert_equal_i(s_out_failed_call_count, 1);
cl_assert_equal_i(s_failure_result, APP_MSG_SEND_TIMEOUT);
// Check the state is reset properly
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__send_rejected(void) {
// Sending ack on timeout, but reject the send
prv_set_remote_receive_handler(prv_nack_sent_callback);
prv_send_test_data();
prv_call_outbox_sent(AppOutboxStatusSuccess);
prv_process_sent_data();
// Fire the ack timeout after receiving the nack
app_timer_trigger(app_message_ack_timer_id());
cl_assert_equal_b(s_nack_sent_is_called, true);
cl_assert_equal_i(s_out_sent_call_count, 0);
cl_assert_equal_i(s_out_failed_call_count, 1);
cl_assert_equal_i(s_failure_result, APP_MSG_SEND_REJECTED);
// Check the state is reset properly
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__nack_then_outbox_sent(void) {
// Sending ack on timeout, but reject the send
prv_set_remote_receive_handler(prv_nack_sent_callback);
prv_send_test_data();
prv_process_sent_data();
cl_assert_equal_b(app_message_is_accepting_outbound(), false);
prv_call_outbox_sent(AppOutboxStatusSuccess);
cl_assert_equal_b(s_nack_sent_is_called, true);
cl_assert_equal_i(s_out_sent_call_count, 0);
cl_assert_equal_i(s_out_failed_call_count, 1);
cl_assert_equal_i(s_failure_result, APP_MSG_SEND_REJECTED);
// Check the state is reset properly
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__busy(void) {
DictionaryIterator *iter;
prv_set_remote_receive_handler(prv_no_reply_callback);
prv_send_test_data();
prv_process_sent_data();
// Can't get or send again if still sending
cl_assert_equal_i(app_message_outbox_begin(&iter), APP_MSG_BUSY);
cl_assert_equal_i(app_message_outbox_send(), APP_MSG_BUSY);
// Can't get or send again if waiting on the ACK
cl_assert_equal_i(app_message_outbox_begin(&iter), APP_MSG_BUSY);
cl_assert_equal_i(app_message_outbox_send(), APP_MSG_BUSY);
}
void test_app_message__send_disconnected(void) {
prv_set_remote_receive_handler(prv_nack_sent_callback);
// Disconnect the comm session
s_is_connected = false;
// The return value should be APP_MSG_OK, even though we already know it's going to fail.
// The failure should be delivered after returning from app_message_outbox_send(), because
// some apps call .._send() again from the failed_callback.
prv_send_test_data_expecting_result(APP_MSG_OK);
// Make fake remote send any outstanding data (none expected)
prv_process_sent_data();
cl_assert_equal_i(s_out_sent_call_count, 0);
// failed_callback not called yet:
cl_assert_equal_i(s_out_failed_call_count, 0);
// Now process the scheduled callback event:
cl_assert(s_process_manager_callback);
s_process_manager_callback(s_process_manager_callback_data);
// Check that the ack/nack timer is removed:
cl_assert_equal_p(app_message_outbox_get_ack_nack_timer(), NULL);
cl_assert_equal_i(1, s_out_failed_call_count);
cl_assert_equal_i(s_failure_result, APP_MSG_NOT_CONNECTED);
cl_assert_equal_b(s_nack_sent_is_called, false);
// Check the state is reset properly
cl_assert_equal_b(app_message_is_accepting_outbound(), true);
}
void test_app_message__send_while_closing_and_while_being_disconnected(void) {
prv_set_remote_receive_handler(prv_nack_sent_callback);
prv_send_test_data();
// Disconnect the comm session and remove the
// app message context
s_is_connected = false;
app_message_close();
// Make fake remote send any outstanding data (none expected)
prv_process_sent_data();
// No app_message callbacks are expected to be called, as we closed the context
cl_assert_equal_i(s_out_sent_call_count, 0);
cl_assert_equal_b(s_nack_sent_is_called, false);
cl_assert_equal_i(s_out_failed_call_count, 0);
cl_assert_equal_b(app_message_is_closed_outbound(), true);
}
void test_app_message__send_while_closing(void) {
prv_set_remote_receive_handler(prv_ack_sent_callback);
prv_send_test_data();
// Close the AppMessage context
app_message_close();
// Make fake remote send the ack if something has been sent (not expected)
prv_process_sent_data();
// Test that timer has been invalidated
cl_assert_equal_b(!fake_app_timer_is_scheduled(app_message_ack_timer_id()), true);
cl_assert_equal_b(s_ack_sent_is_called, false);
cl_assert_equal_i(s_out_sent_call_count, 0);
cl_assert_equal_i(s_out_failed_call_count, 0);
cl_assert_equal_b(app_message_is_closed_outbound(), true);
cl_assert_equal_b(app_message_is_closed_inbound(), true);
}
void test_app_message__throttle_repeated_outbox_begin_calls(void) {
prv_set_remote_receive_handler(prv_no_reply_callback);
prv_send_test_data();
// Expect exponential back-off:
for (int i = 1; i <= 128; i *= 2) {
DictionaryIterator *iter;
cl_assert_equal_i(app_message_outbox_begin(&iter), APP_MSG_BUSY);
cl_assert_equal_i(MIN(i, 100), s_sys_psleep_last_millis);
}
}
// Test INBOUND (phone->watch):
////////////////////////////////////
static void check_in_accepting_again(void) {
cl_assert(app_message_is_accepting_inbound() == true);
}
void test_app_message__receive_happy_case(void) {
prv_set_remote_receive_handler(prv_receive_ack_nack_callback);
prv_receive_test_data(TEST_TRANSACTION_ID_1, false);
cl_assert_equal_i(s_app_inbox_consume_call_count, 1);
prv_process_sent_data();
// First Message
cl_assert(s_in_received_is_called == true);
// ACK the messages
// Check that state was reset properly
check_in_accepting_again();
}
void test_app_message__receive_dropped_because_buffer_too_small(void) {
// FIXME:
// https://pebbletechnology.atlassian.net/browse/PBL-22925
return;
prv_set_remote_receive_handler(prv_receive_ack_nack_callback);
prv_receive_test_data(TEST_TRANSACTION_ID_2, true);
// Message should be dropped due to buffer overflow
cl_assert_equal_b(s_in_dropped_is_called, true);
cl_assert_equal_b(s_in_received_is_called, false);
cl_assert_equal_i(s_dropped_reason, APP_MSG_BUFFER_OVERFLOW);
cl_assert_equal_b(s_nack_received_for_id_2, true);
// Check that the state was reset
check_in_accepting_again();
}
void test_app_message__receive_app_not_running(void) {
// FIXME:
// https://pebbletechnology.atlassian.net/browse/PBL-22925
return;
prv_set_remote_receive_handler(prv_receive_ack_nack_callback);
prv_receive_test_data(TEST_TRANSACTION_ID_2, false);
cl_assert_equal_b(s_in_received_is_called, false);
cl_assert_equal_b(s_in_dropped_is_called, false);
cl_assert_equal_b(s_nack_received_for_id_2, true);
// Check that the state is reset
check_in_accepting_again();
}
void test_app_message__receive_app_uuid_mismatch(void) {
// Change the current app uuid
prv_set_app_uuid(UuidMake(0xF6, 0x2C, 0xB7, 0xBA, 0x1B, 0x8D, 0x46, 0x10,
0xBE, 0xC5, 0xDE, 0xC6, 0x5A, 0xD3, 0x18, 0x29));
prv_set_remote_receive_handler(prv_receive_ack_nack_callback);
prv_receive_test_data(TEST_TRANSACTION_ID_2, false);
prv_process_sent_data();
cl_assert_equal_b(s_in_received_is_called, false);
cl_assert_equal_b(s_in_dropped_is_called, false);
cl_assert_equal_b(s_nack_received_for_id_2, true);
// Check that the state is reset
check_in_accepting_again();
}
void test_app_message__get_context(void) {
cl_assert_equal_p(app_message_get_context(), &s_context);
}
void test_app_message__open_while_already_open(void) {
cl_assert_equal_i(app_message_open(MAX_SIZE_INBOUND, MAX_SIZE_OUTBOUND), APP_MSG_INVALID_STATE);
}
void test_app_message__begin_while_already_begun(void) {
DictionaryIterator *iterator;
cl_assert_equal_i(app_message_outbox_begin(&iterator), APP_MSG_OK);
cl_assert_equal_i(app_message_outbox_begin(&iterator), APP_MSG_INVALID_STATE);
}
void test_app_message__begin_null_iterator(void) {
cl_assert_equal_i(app_message_outbox_begin(NULL), APP_MSG_INVALID_ARGS);
}
void test_app_message__send_while_not_begun(void) {
cl_assert_equal_i(app_message_outbox_send(), APP_MSG_INVALID_STATE);
}
void test_app_message__zero_inbox(void) {
app_message_close();
cl_assert_equal_i(app_message_open(0, MAX_SIZE_OUTBOUND), APP_MSG_OK);
cl_assert_equal_b(app_message_is_closed_inbound(), true);
cl_assert_equal_b(app_message_is_closed_outbound(), false);
}
void test_app_message__zero_outbox(void) {
app_message_close();
cl_assert_equal_i(app_message_open(MAX_SIZE_INBOUND, 0), APP_MSG_OK);
cl_assert_equal_b(app_message_is_closed_inbound(), false);
cl_assert_equal_b(app_message_is_closed_outbound(), true);
DictionaryIterator *iterator;
cl_assert_equal_i(app_message_outbox_begin(&iterator), APP_MSG_INVALID_STATE);
}
void test_app_message__oom(void) {
s_app_receiver_oom = true;
app_message_close();
cl_assert_equal_i(app_message_open(MAX_SIZE_INBOUND, MAX_SIZE_OUTBOUND), APP_MSG_OUT_OF_MEMORY);
cl_assert_equal_b(app_message_is_closed_inbound(), true);
cl_assert_equal_b(app_message_is_closed_outbound(), true);
}
void test_app_message__kernel_nack_handler(void) {
prv_set_remote_receive_handler(prv_receive_ack_nack_callback);
const AppMessagePush push = {
.header = {
.command = CMD_PUSH,
.transaction_id = TEST_TRANSACTION_ID_2,
},
};
app_message_app_protocol_system_nack_callback(s_fake_app_comm_session,
(const uint8_t *)&push, sizeof(push));
prv_process_sent_data();
cl_assert_equal_b(s_nack_received_for_id_2, true);
}

View file

@ -0,0 +1,274 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/app_outbox.h"
#include "applib/event_service_client.h"
#include "services/normal/app_outbox_service.h"
#include "clar.h"
extern void app_outbox_service_deinit(void);
extern uint32_t app_outbox_service_max_pending_messages(AppOutboxServiceTag tag);
extern uint32_t app_outbox_service_max_message_length(AppOutboxServiceTag tag);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fakes & Stubs
#include "fake_kernel_malloc.h"
#include "fake_pebble_tasks.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_syscall_internal.h"
static EventServiceInfo s_app_state_app_outbox_subscription_info;
EventServiceInfo *app_state_get_app_outbox_subscription_info(void) {
return &s_app_state_app_outbox_subscription_info;
}
void event_service_client_subscribe(EventServiceInfo * service_info) {
}
void sys_send_pebble_event_to_kernel(PebbleEvent* event) {
cl_assert_equal_i(event->type, PEBBLE_APP_OUTBOX_MSG_EVENT);
event->callback.callback(event->callback.data);
}
static int s_num_app_outbox_events_sent;
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) {
cl_assert_equal_i(e->type, PEBBLE_APP_OUTBOX_SENT_EVENT);
cl_assert(e->app_outbox_sent.sent_handler);
e->app_outbox_sent.sent_handler(e->app_outbox_sent.status,
e->app_outbox_sent.cb_ctx);
++s_num_app_outbox_events_sent;
return true;
}
void app_message_outbox_handle_app_outbox_message_sent(AppOutboxStatus status, void *cb_ctx) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers
static int s_num_message_handler_calls;
static AppOutboxMessage *s_last_message;
static void prv_message_handler(AppOutboxMessage *message) {
s_last_message = message;
++s_num_message_handler_calls;
}
static int s_num_sent_handler_called;
static AppOutboxStatus s_last_sent_status;
static void *s_expected_cb_ctx = (void *)0x77777777;
void test_app_outbox_sent_handler(AppOutboxStatus status, void *cb_ctx) {
cl_assert_equal_p(s_expected_cb_ctx, cb_ctx);
s_last_sent_status = status;
++s_num_sent_handler_called;
}
#define assert_sent_cb_last_status(expected_status) \
cl_assert_equal_i(s_last_sent_status, expected_status)
////////////////////////////////////////////////////////////////////////////////////////////////////
// Tests
static uint8_t *s_test_data;
static size_t s_test_data_length;
void test_app_outbox__initialize(void) {
fake_kernel_malloc_init();
fake_kernel_malloc_enable_stats(true);
s_test_data_length = app_outbox_service_max_message_length(AppOutboxServiceTagUnitTest);
s_test_data = kernel_malloc(s_test_data_length);
memset(s_test_data, 0x88, s_test_data_length);
s_num_sent_handler_called = 0;
s_num_app_outbox_events_sent = 0;
s_num_message_handler_calls = 0;
s_last_message = NULL;
stubs_syscall_init();
s_app_state_app_outbox_subscription_info = (EventServiceInfo) {};
// set to something that is not expected anywhere in the tests:
s_last_sent_status = AppOutboxStatusUserRangeEnd;
app_outbox_service_init();
app_outbox_init();
}
void test_app_outbox__cleanup(void) {
app_outbox_service_deinit();
kernel_free(s_test_data);
}
static size_t s_consumer_data_length = 1;
static void prv_register(void) {
app_outbox_service_register(AppOutboxServiceTagUnitTest,
prv_message_handler,
PebbleTask_KernelMain,
s_consumer_data_length);
}
void test_app_outbox__register_twice_asserts(void) {
prv_register();
cl_assert_passert(prv_register());
}
void test_app_outbox__send_not_user_space_buffer(void) {
// TODO: really implement privilege escalation in unit tests. See PBL-9688
return;
cl_assert_passert(app_outbox_send(NULL, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx));
assert_syscall_failed();
}
// Disallowed, because it's not white-listed in app_outbox_service.c
static void prv_disallowed_sent_handler(AppOutboxStatus status, void *cb_ctx) {
}
void test_app_outbox__send_disallowed_sent_handler(void) {
prv_register();
cl_assert_passert(app_outbox_send(s_test_data, s_test_data_length,
prv_disallowed_sent_handler, s_expected_cb_ctx));
assert_syscall_failed();
}
void test_app_outbox__send_max_length_exceeded(void) {
prv_register();
cl_assert_passert(app_outbox_send(s_test_data, s_test_data_length + 1,
test_app_outbox_sent_handler, s_expected_cb_ctx));
assert_syscall_failed();
}
void test_app_outbox__send_but_consumer_not_registered(void) {
prv_register();
app_outbox_service_unregister(AppOutboxServiceTagUnitTest);
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
assert_sent_cb_last_status(AppOutboxStatusConsumerDoesNotExist);
}
void test_app_outbox__send_but_max_pending_messages_reached(void) {
prv_register();
uint32_t max_pending_messages =
app_outbox_service_max_pending_messages(AppOutboxServiceTagUnitTest);
for (uint32_t i = 0; i < max_pending_messages; ++i) {
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
cl_assert_equal_i(s_num_sent_handler_called, 0);
}
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
assert_sent_cb_last_status(AppOutboxStatusOutOfResources);
}
void test_app_outbox__send_but_oom(void) {
prv_register();
fake_kernel_malloc_set_largest_free_block(0);
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
assert_sent_cb_last_status(AppOutboxStatusOutOfMemory);
}
void test_app_outbox__send_but_null_sent_handler(void) {
prv_register();
// Invalid data, so normally an event would get put to invoke the sent_handler,
// but sent handler is NULL. Expect no events to be put.
cl_assert_passert(app_outbox_send(NULL, 0, NULL, s_expected_cb_ctx));
cl_assert_equal_i(s_num_app_outbox_events_sent, 0);
}
void test_app_outbox__send(void) {
fake_kernel_malloc_mark();
prv_register();
uint32_t max_pending_messages =
app_outbox_service_max_pending_messages(AppOutboxServiceTagUnitTest);
AppOutboxMessage *message[max_pending_messages];
for (uint32_t i = 0; i < max_pending_messages; ++i) {
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
cl_assert_equal_i(s_num_app_outbox_events_sent, 0);
cl_assert_equal_i(s_num_message_handler_calls, i + 1);
cl_assert(s_last_message);
cl_assert_equal_p(s_test_data, s_last_message->data);
cl_assert_equal_i(s_test_data_length, s_last_message->length);
cl_assert_equal_b(false, app_outbox_service_is_message_cancelled(s_last_message));
message[i] = s_last_message;
}
for (uint32_t i = 0; i < max_pending_messages; ++i) {
app_outbox_service_consume_message(message[i], AppOutboxStatusSuccess);
cl_assert_equal_i(s_num_app_outbox_events_sent, i + 1);
assert_sent_cb_last_status(AppOutboxStatusSuccess);
}
fake_kernel_malloc_mark_assert_equal();
}
void test_app_outbox__unregister_with_pending_message(void) {
fake_kernel_malloc_mark();
prv_register();
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
cl_assert(s_last_message);
app_outbox_service_unregister(AppOutboxServiceTagUnitTest);
cl_assert_equal_i(s_num_app_outbox_events_sent, 1);
assert_sent_cb_last_status(AppOutboxStatusConsumerDoesNotExist);
cl_assert_equal_b(true, app_outbox_service_is_message_cancelled(s_last_message));
// the consumer must call ..._consume_message(), to free the resources:
app_outbox_service_consume_message(s_last_message, AppOutboxStatusSuccess);
// sent_handler shouldn't get called again, it's already been called:
cl_assert_equal_i(s_num_app_outbox_events_sent, 1);
fake_kernel_malloc_mark_assert_equal();
}
void test_app_outbox__cleanup_all_with_pending_message(void) {
fake_kernel_malloc_mark();
prv_register();
app_outbox_send(s_test_data, s_test_data_length,
test_app_outbox_sent_handler, s_expected_cb_ctx);
cl_assert(s_last_message);
app_outbox_service_cleanup_all_pending_messages();
// sent_handler shouldn't get called when cleaning up:
cl_assert_equal_i(s_num_app_outbox_events_sent, 0);
cl_assert_equal_b(true, app_outbox_service_is_message_cancelled(s_last_message));
// the consumer must call ..._consume_message(), to free the resources:
app_outbox_service_consume_message(s_last_message, AppOutboxStatusSuccess);
fake_kernel_malloc_mark_assert_equal();
}

View file

@ -0,0 +1,386 @@
/*
* 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 "applib/event_service_client.h"
#include "services/normal/accessory/smartstrap_attribute.h"
#include "kernel/pbl_malloc.h"
#include "fake_smartstrap_profiles.h"
#include "fake_smartstrap_state.h"
#include "fake_system_task.h"
#include "stubs_app_state.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_serial.h"
#define NON_NULL_MBUF ((MBuf *)1)
#define assert_result_ok(result) cl_assert(result == SmartstrapResultOk)
#define assert_result_invalid(result) cl_assert(result == SmartstrapResultInvalidArgs)
#define assert_result_busy(result) cl_assert(result == SmartstrapResultBusy)
typedef void (*EventServiceEventHandler)(PebbleEvent *e, void *context);
typedef struct {
bool active;
SmartstrapAttribute *attribute;
size_t length;
} PendingInfo;
static EventServiceEventHandler s_event_handler;
static PendingInfo s_pending_did_read;
static PendingInfo s_pending_did_write;
static PendingInfo s_pending_notified;
// Stubs / fakes
void event_service_client_subscribe(EventServiceInfo *info) {
cl_assert(info->type == PEBBLE_SMARTSTRAP_EVENT);
s_event_handler = info->handler;
}
void event_service_client_unsubscribe(EventServiceInfo *info) {
cl_assert(info->type == PEBBLE_SMARTSTRAP_EVENT);
s_event_handler = NULL;
}
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent *e) {
cl_assert(task == PebbleTask_App);
cl_assert(e->type == PEBBLE_SMARTSTRAP_EVENT);
cl_assert(s_event_handler);
s_event_handler(e, NULL);
return true;
}
void smartstrap_cancel_send(void) {
}
// Helper functions
static void prv_prepare_for_did_read(SmartstrapAttribute *attribute, uint16_t read_length) {
cl_assert(!s_pending_did_read.active);
s_pending_did_read = (PendingInfo) {
.active = true,
.attribute = attribute,
.length = read_length
};
}
static void prv_did_read_handler(SmartstrapAttribute *attribute, SmartstrapResult result,
const uint8_t *data, size_t length) {
cl_assert(data == (uint8_t *)attribute);
cl_assert(result == SmartstrapResultOk);
cl_assert(s_pending_did_read.active);
cl_assert(s_pending_did_read.attribute == attribute);
cl_assert(s_pending_did_read.length == length);
s_pending_did_read.active = false;
}
static void prv_prepare_for_did_write(SmartstrapAttribute *attribute) {
cl_assert(!s_pending_did_write.active);
s_pending_did_write = (PendingInfo) {
.active = true,
.attribute = attribute
};
}
static void prv_did_write_handler(SmartstrapAttribute *attribute, SmartstrapResult result) {
cl_assert(result == SmartstrapResultOk);
cl_assert(s_pending_did_write.active);
cl_assert(s_pending_did_write.attribute == attribute);
s_pending_did_write.active = false;
}
static void prv_prepare_for_notified(SmartstrapAttribute *attribute) {
cl_assert(!s_pending_notified.active);
s_pending_notified = (PendingInfo) {
.active = true,
.attribute = attribute
};
}
static void prv_notified_handler(SmartstrapAttribute *attribute) {
cl_assert(s_pending_notified.active);
cl_assert(s_pending_notified.attribute == attribute);
s_pending_notified.active = false;
}
// Setup
void test_app_smartstrap__initialize(void) {
smartstrap_attribute_init();
app_smartstrap_subscribe((SmartstrapHandlers) {
.did_read = prv_did_read_handler,
.did_write = prv_did_write_handler,
.notified = prv_notified_handler
});
}
void test_app_smartstrap__cleanup(void) {
}
// Tests
void test_app_smartstrap__invalid_args(void) {
// create test attribute
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
// smartstrap_attribute_create()
cl_assert(app_smartstrap_attribute_create(0x1111, 0x2222, 0) == NULL);
// smartstrap_attribute_destroy()
app_smartstrap_attribute_destroy(NULL);
// smartstrap_attribute_get_*_id()
cl_assert(app_smartstrap_attribute_get_service_id(NULL) == 0);
cl_assert(app_smartstrap_attribute_get_attribute_id(NULL) == 0);
// smartstrap_attribute_begin_write()
uint8_t *buffer;
size_t buffer_len;
assert_result_invalid(app_smartstrap_attribute_begin_write(NULL, NULL, NULL));
assert_result_invalid(app_smartstrap_attribute_begin_write(NULL, &buffer, NULL));
assert_result_invalid(app_smartstrap_attribute_begin_write(NULL, NULL, &buffer_len));
assert_result_invalid(app_smartstrap_attribute_begin_write(NULL, &buffer, &buffer_len));
assert_result_invalid(app_smartstrap_attribute_begin_write(attr, NULL, NULL));
assert_result_invalid(app_smartstrap_attribute_begin_write(attr, &buffer, NULL));
assert_result_invalid(app_smartstrap_attribute_begin_write(attr, NULL, &buffer_len));
// smartstrap_attribute_end_write()
assert_result_invalid(app_smartstrap_attribute_end_write(NULL, 0, false));
assert_result_invalid(app_smartstrap_attribute_end_write(NULL, 0, true));
assert_result_invalid(app_smartstrap_attribute_end_write(NULL, 100, false));
assert_result_invalid(app_smartstrap_attribute_end_write(NULL, 100, true));
assert_result_invalid(app_smartstrap_attribute_end_write(attr, 0, false));
assert_result_invalid(app_smartstrap_attribute_end_write(attr, 0, true));
assert_result_invalid(app_smartstrap_attribute_end_write(attr, 100, false));
assert_result_invalid(app_smartstrap_attribute_end_write(attr, 100, true));
// smartstrap_attribute_read()
assert_result_invalid(app_smartstrap_attribute_read(NULL));
// destroy test attribute
app_smartstrap_attribute_destroy(attr);
}
void test_app_smartstrap__check_ids(void) {
// craete an attribute
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
cl_assert(attr != NULL);
// verify the ids
cl_assert(app_smartstrap_attribute_get_service_id(attr) == 0x1111);
cl_assert(app_smartstrap_attribute_get_attribute_id(attr) == 0x2222);
// destroy the attribute
app_smartstrap_attribute_destroy(attr);
// verify that we can no longer get the ids
cl_assert(app_smartstrap_attribute_get_service_id(attr) == 0);
cl_assert(app_smartstrap_attribute_get_attribute_id(attr) == 0);
}
void test_app_smartstrap__create_duplicate(void) {
// create the attribute once
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
cl_assert(attr != NULL);
// try to create it again
SmartstrapAttribute *attr_dup = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
cl_assert(attr_dup == NULL);
// destroy the attribute
app_smartstrap_attribute_destroy(attr);
// destroy again (shouldn't do anything bad)
app_smartstrap_attribute_destroy(attr);
}
void test_app_smartstrap__read(void) {
// create the attribute
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
// start a read request
stub_pebble_tasks_set_current(PebbleTask_App);
assert_result_ok(app_smartstrap_attribute_read(attr));
// attempt to issue another read request
assert_result_busy(app_smartstrap_attribute_read(attr));
// trigger the read request to be sent and expect a did_write handler call
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
prv_prepare_for_did_write(attr);
cl_assert(smartstrap_attribute_send_pending());
cl_assert(!s_pending_did_write.active);
// attempt to issue another read request (should report busy)
assert_result_busy(app_smartstrap_attribute_read(attr));
// check that it was sent successfully
SmartstrapRequest request = {
.service_id = 0x1111,
.attribute_id = 0x2222,
.write_mbuf = NULL,
.read_mbuf = NON_NULL_MBUF,
.timeout_ms = SMARTSTRAP_TIMEOUT_DEFAULT
};
fake_smartstrap_profiles_check_request_params(&request);
// fake the response and expect a did_read handler call
prv_prepare_for_did_read(attr, 10);
smartstrap_attribute_send_event(SmartstrapDataReceivedEvent, SmartstrapProfileGenericService,
SmartstrapResultOk, 0x1111, 0x2222, 10);
cl_assert(!s_pending_did_read.active);
// destroy the attribute
app_smartstrap_attribute_destroy(attr);
}
void test_app_smartstrap__write(void) {
// create the attribute
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
// start a write request
stub_pebble_tasks_set_current(PebbleTask_App);
uint8_t *write_buffer = NULL;
size_t write_length = 0;
assert_result_ok(app_smartstrap_attribute_begin_write(attr, &write_buffer, &write_length));
cl_assert(write_buffer == (uint8_t *)attr);
cl_assert(write_length == 100);
// attempt to start another write request
assert_result_busy(app_smartstrap_attribute_read(attr));
uint8_t *write_buffer2 = NULL;
size_t write_length2 = 0;
assert_result_busy(app_smartstrap_attribute_begin_write(attr, &write_buffer2, &write_length2));
cl_assert(write_buffer2 == NULL);
cl_assert(write_length2 == 0);
// end the write request without sending anything
assert_result_invalid(app_smartstrap_attribute_end_write(attr, 0, false));
// start the write request again
write_buffer = NULL;
write_length = 0;
assert_result_ok(app_smartstrap_attribute_begin_write(attr, &write_buffer, &write_length));
cl_assert(write_buffer == (uint8_t *)attr);
cl_assert(write_length == 100);
// end the write request
assert_result_ok(app_smartstrap_attribute_end_write(attr, 100, false));
// trigger the write request to be sent
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
cl_assert(smartstrap_attribute_send_pending());
// check that it was sent successfully
SmartstrapRequest request = {
.service_id = 0x1111,
.attribute_id = 0x2222,
.write_mbuf = NON_NULL_MBUF,
.read_mbuf = NULL,
.timeout_ms = SMARTSTRAP_TIMEOUT_DEFAULT
};
// fake the ACK and expect a did_write handler call
fake_smartstrap_profiles_check_request_params(&request);
prv_prepare_for_did_write(attr);
smartstrap_attribute_send_event(SmartstrapDataReceivedEvent, SmartstrapProfileGenericService,
SmartstrapResultOk, 0x1111, 0x2222, 100);
cl_assert(!s_pending_did_write.active);
// destroy the attribute
app_smartstrap_attribute_destroy(attr);
}
void test_app_smartstrap__write_read(void) {
// create the attribute
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
// start a write request
stub_pebble_tasks_set_current(PebbleTask_App);
uint8_t *write_buffer = NULL;
size_t write_length = 0;
assert_result_ok(app_smartstrap_attribute_begin_write(attr, &write_buffer, &write_length));
cl_assert(write_buffer == (uint8_t *)attr);
cl_assert(write_length == 100);
// end the write request without sending anything
assert_result_invalid(app_smartstrap_attribute_end_write(attr, 0, true));
// start the write request again
write_buffer = NULL;
write_length = 0;
assert_result_ok(app_smartstrap_attribute_begin_write(attr, &write_buffer, &write_length));
cl_assert(write_buffer == (uint8_t *)attr);
cl_assert(write_length == 100);
// end the write request with request_read=true
assert_result_ok(app_smartstrap_attribute_end_write(attr, 100, true));
// trigger the write request to be sent and expect a did_write handler call
stub_pebble_tasks_set_current(PebbleTask_KernelBackground);
prv_prepare_for_did_write(attr);
cl_assert(smartstrap_attribute_send_pending());
cl_assert(!s_pending_did_write.active);
// check that it was sent successfully
SmartstrapRequest request = {
.service_id = 0x1111,
.attribute_id = 0x2222,
.write_mbuf = NON_NULL_MBUF,
.read_mbuf = NON_NULL_MBUF,
.timeout_ms = SMARTSTRAP_TIMEOUT_DEFAULT
};
// fake the response and expect a did_write and a did_read handler call
fake_smartstrap_profiles_check_request_params(&request);
prv_prepare_for_did_read(attr, 100);
smartstrap_attribute_send_event(SmartstrapDataReceivedEvent, SmartstrapProfileGenericService,
SmartstrapResultOk, 0x1111, 0x2222, 100);
cl_assert(!s_pending_did_read.active);
// destroy the attribute
app_smartstrap_attribute_destroy(attr);
}
void test_app_smartstrap__notify(void) {
// create the attribute
SmartstrapAttribute *attr = app_smartstrap_attribute_create(0x1111, 0x2222, 100);
// send a notification and expect a notified handler call
prv_prepare_for_notified(attr);
smartstrap_attribute_send_event(SmartstrapNotifyEvent, SmartstrapProfileGenericService,
SmartstrapResultOk, 0x1111, 0x2222, 0);
cl_assert(!s_pending_notified.active);
// send a notification for a non-created attribute which shouldn't cause a notified handler call
smartstrap_attribute_send_event(SmartstrapNotifyEvent, SmartstrapProfileGenericService,
SmartstrapResultOk, 0x1111, 0x3333, 0);
// destroy the attribute
app_smartstrap_attribute_destroy(attr);
// send a notification for the destroyed attribute which shouldn't cause a notified handler call
smartstrap_attribute_send_event(SmartstrapNotifyEvent, SmartstrapProfileGenericService,
SmartstrapResultOk, 0x1111, 0x2222, 0);
}

View file

@ -0,0 +1,96 @@
/*
* 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 "applib/graphics/graphics.h"
#include "applib/ui/bitmap_layer.h"
// Stubs
/////////////////////
#include "stubs_app_state.h"
#include "stubs_graphics.h"
#include "stubs_graphics_context.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
#include "stubs_ui_window.h"
#include "stubs_unobstructed_area.h"
// Fakes
/////////////////////
static GRect s_graphics_draw_bitmap_in_rect__rect = GRectZero;
void graphics_draw_bitmap_in_rect(GContext* ctx, const GBitmap *src_bitmap, const GRect *rect) {
s_graphics_draw_bitmap_in_rect__rect = *rect;
}
bool process_manager_compiled_with_legacy2_sdk(void) {
return cl_mock_type(bool);
}
// Test boilerplate
/////////////////////
void test_bitmap_layer__initialize(void) {
}
void test_bitmap_layer__cleanup(void) {
}
// Tests
//////////////////////
// Test inspired by PBL-19136. Check that bitmaps get drawn in the right rect
// on recent SDKs but that a previous bug is kept for 2.x SDK
void test_bitmap_layer__nonzero_bounds(void) {
GContext ctx = {
.draw_state = (GDrawState) {
.clip_box = GRect(0, 0, 144, 168),
.drawing_box = GRect(0, 0, 144, 168),
},
};
static const GRect BITMAP_LAYER_FRAME = GRect(0, 0, 640, 64);
static const GRect BITMAP_LAYER_BOUNDS = GRect(-32, 0, 640, 64);
static const GRect BITMAP_BOUNDS = GRect(0, 0, 640, 64);
GBitmap bitmap = {
.bounds = BITMAP_BOUNDS,
};
BitmapLayer layer;
bitmap_layer_init(&layer, &BITMAP_LAYER_FRAME);
bitmap_layer_set_bitmap(&layer, &bitmap);
// set bounds with non-zero origin
layer_set_bounds((Layer *)&layer, &BITMAP_LAYER_BOUNDS);
// !legacy2
cl_will_return(process_manager_compiled_with_legacy2_sdk, false);
layer_render_tree((Layer *)&layer, &ctx);
GRect expected_rect = BITMAP_BOUNDS;
cl_assert(grect_equal(&s_graphics_draw_bitmap_in_rect__rect, &expected_rect));
// legacy2
cl_will_return(process_manager_compiled_with_legacy2_sdk, true);
layer_render_tree((Layer *)&layer, &ctx);
expected_rect = BITMAP_LAYER_BOUNDS;
cl_assert(grect_equal(&s_graphics_draw_bitmap_in_rect__rect, &expected_rect));
}

View file

@ -0,0 +1,256 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/cpu_cache.h"
#include "mcu/cache.h"
#include "clar.h"
// Fakes
//////////////////////////////////////////////////////////
static uintptr_t s_user_start, s_user_size;
static size_t s_flush_size, s_invalidate_size;
static uintptr_t s_flush_addr, s_invalidate_addr;
typedef enum {
UserSpaceBuffer_Unchecked,
UserSpaceBuffer_Valid,
UserSpaceBuffer_Invalid,
UserSpaceBuffer_NotRun,
} UserSpaceBufferValidity;
static UserSpaceBufferValidity s_addr_result;
uint32_t dcache_line_size(void) {
return cl_mock_type(size_t);
}
uint32_t icache_line_size(void) {
return cl_mock_type(size_t);
}
bool dcache_is_enabled(void) {
return cl_mock_type(bool);
}
bool icache_is_enabled(void) {
return cl_mock_type(bool);
}
void icache_invalidate(void *addr, size_t size) {
s_invalidate_size = size;
s_invalidate_addr = (uintptr_t)addr;
}
void dcache_flush(const void *addr, size_t size) {
s_flush_size = size;
s_flush_addr = (uintptr_t)addr;
}
bool syscall_internal_check_return_address(void * ret_addr) {
s_addr_result = UserSpaceBuffer_Unchecked;
return cl_mock_type(bool);
}
void syscall_assert_userspace_buffer(const void* buf, size_t num_bytes) {
uintptr_t addr = (uintptr_t)buf;
uintptr_t end = addr + num_bytes - 1;
if ((addr >= s_user_start) && (addr < (s_user_start + s_user_size)) &&
(end >= s_user_start) && (end < (s_user_start + s_user_size))) {
s_addr_result = UserSpaceBuffer_Valid;
} else {
s_addr_result = UserSpaceBuffer_Invalid;
}
}
// Tests
////////////////////////////////////
void test_cpu_cache__alignment(void) {
cl_will_return(icache_is_enabled, true);
cl_will_return(dcache_is_enabled, true);
cl_will_return(icache_line_size, 8);
cl_will_return(dcache_line_size, 16);
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x00;
s_user_size = 0x20;
memory_cache_flush((void*)0x0F, 0x2);
cl_assert_equal_i(s_flush_addr, 0x00);
cl_assert_equal_i(s_flush_size, 0x20);
cl_assert_equal_i(s_invalidate_addr, 0x00);
cl_assert_equal_i(s_invalidate_size, 0x20);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Valid);
}
void test_cpu_cache__userspace_fail_from_size(void) {
cl_will_return(icache_is_enabled, true);
cl_will_return(dcache_is_enabled, true);
cl_will_return(icache_line_size, 1);
cl_will_return(dcache_line_size, 1);
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x00;
s_user_size = 0x20;
memory_cache_flush((void*)0x1F, 0x2);
cl_assert_equal_i(s_flush_addr, 0x1F);
cl_assert_equal_i(s_flush_size, 0x02);
cl_assert_equal_i(s_invalidate_addr, 0x1F);
cl_assert_equal_i(s_invalidate_size, 0x02);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Invalid);
}
void test_cpu_cache__userspace_fail_from_addr(void) {
cl_will_return(icache_is_enabled, true);
cl_will_return(dcache_is_enabled, true);
cl_will_return(icache_line_size, 1);
cl_will_return(dcache_line_size, 1);
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x00;
s_user_size = 0x20;
memory_cache_flush((void*)0x20, 0x1);
cl_assert_equal_i(s_flush_addr, 0x20);
cl_assert_equal_i(s_flush_size, 0x01);
cl_assert_equal_i(s_invalidate_addr, 0x20);
cl_assert_equal_i(s_invalidate_size, 0x01);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Invalid);
}
void test_cpu_cache__userspace_aligned_fail(void) {
cl_will_return(icache_is_enabled, true);
cl_will_return(dcache_is_enabled, true);
cl_will_return(icache_line_size, 8);
cl_will_return(dcache_line_size, 8);
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x24;
s_user_size = 0x20;
memory_cache_flush((void*)0x26, 0x2);
cl_assert_equal_i(s_flush_addr, 0x20);
cl_assert_equal_i(s_flush_size, 0x08);
cl_assert_equal_i(s_invalidate_addr, 0x20);
cl_assert_equal_i(s_invalidate_size, 0x08);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Invalid);
}
void test_cpu_cache__userspace_ignore(void) {
cl_will_return(icache_is_enabled, true);
cl_will_return(dcache_is_enabled, true);
cl_will_return(icache_line_size, 1);
cl_will_return(dcache_line_size, 1);
cl_will_return(syscall_internal_check_return_address, false);
s_user_start = 0x00;
s_user_size = 0x04;
memory_cache_flush((void*)0x00, 0x10);
cl_assert_equal_i(s_flush_addr, 0x00);
cl_assert_equal_i(s_flush_size, 0x10);
cl_assert_equal_i(s_invalidate_addr, 0x00);
cl_assert_equal_i(s_invalidate_size, 0x10);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Unchecked);
}
void test_cpu_cache__without_icache(void) {
cl_will_return(icache_is_enabled, false);
cl_will_return(dcache_is_enabled, true);
cl_will_return(icache_line_size, 1);
cl_will_return(dcache_line_size, 1);
s_invalidate_addr = s_flush_addr = 0xAA55;
s_invalidate_size = s_flush_size = 0x55AA;
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x00;
s_user_size = 0x20;
memory_cache_flush((void*)0x20, 0x1);
cl_assert_equal_i(s_flush_addr, 0x20);
cl_assert_equal_i(s_flush_size, 0x01);
cl_assert_equal_i(s_invalidate_addr, 0xAA55);
cl_assert_equal_i(s_invalidate_size, 0x55AA);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Invalid);
}
void test_cpu_cache__without_dcache(void) {
cl_will_return(icache_is_enabled, true);
cl_will_return(dcache_is_enabled, false);
cl_will_return(icache_line_size, 1);
cl_will_return(dcache_line_size, 1);
s_invalidate_addr = s_flush_addr = 0xAA55;
s_invalidate_size = s_flush_size = 0x55AA;
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x00;
s_user_size = 0x20;
memory_cache_flush((void*)0x20, 0x1);
cl_assert_equal_i(s_flush_addr, 0xAA55);
cl_assert_equal_i(s_flush_size, 0x55AA);
cl_assert_equal_i(s_invalidate_addr, 0x20);
cl_assert_equal_i(s_invalidate_size, 0x01);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Invalid);
}
void test_cpu_cache__without_cache(void) {
cl_will_return(icache_is_enabled, false);
cl_will_return(dcache_is_enabled, false);
cl_will_return(icache_line_size, 1);
cl_will_return(dcache_line_size, 1);
s_invalidate_addr = s_flush_addr = 0xAA55;
s_invalidate_size = s_flush_size = 0x55AA;
cl_will_return(syscall_internal_check_return_address, true);
s_user_start = 0x00;
s_user_size = 0x20;
memory_cache_flush((void*)0x20, 0x1);
cl_assert_equal_i(s_flush_addr, 0xAA55);
cl_assert_equal_i(s_flush_size, 0x55AA);
cl_assert_equal_i(s_invalidate_addr, 0xAA55);
cl_assert_equal_i(s_invalidate_size, 0x55AA);
cl_assert_equal_i(s_addr_result, UserSpaceBuffer_Invalid);
}

View file

@ -0,0 +1,185 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/pbl_std/pbl_std.h"
#include "applib/pbl_std/locale.h"
#include "clar.h"
// Stubs
//////////////////////////////////////////////////////////
#include "stubs_heap.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_syscall_internal.h"
#include "stubs_system_reset.h"
#include "stubs_task_watchdog.h"
#include "stubs_app_state.h"
#include "stubs_worker_state.h"
// Overrides
//////////////////////////////////////////////////////////
void sys_get_time_ms(time_t *t, uint16_t *out_ms) {}
time_t sys_time_utc_to_local(time_t t) {
return t;
}
int localized_strftime(char* s, size_t maxsize, const char* format,
const struct tm* tim_p, char *locale) { return 0; }
const char *get_timezone_abbr(void) {
static const char s_timezone_abbr[] = "A";
return s_timezone_abbr;
}
void sys_copy_timezone_abbr(char* timezone_abbr, time_t time) {
const char* sys_tz = get_timezone_abbr();
strncpy(timezone_abbr, sys_tz, TZ_LEN);
}
struct tm *sys_gmtime_r(const time_t *timep, struct tm *result) {
return gmtime_r(timep, result);
}
struct tm *sys_localtime_r(const time_t *timep, struct tm *result) {
return localtime_r(timep, result);
}
// Tests
////////////////////////////////////
void test_pbl_std__get_id(void) {
const int STR_SIZE = 100;
char str[STR_SIZE];
// This is the message we should get back if we try and use floating point
const char* fp_msg = "floating point not supported in snprintf";
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "%", 1); // Make sure we don't barf if no type
cl_assert_equal_s(str, "");
pbl_snprintf(str, STR_SIZE, "%%", 1);
cl_assert_equal_s(str, "%");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "%f", 1.0);
cl_assert_equal_s(str, fp_msg);
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "%s%f%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "%s%d%s", "a", 1, "b");
cl_assert_equal_s(str, "a1b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc%s %0.1f%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc%s %d%s", "a", 42, "b");
cl_assert_equal_s(str, "abca 42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %3g", 1.0);
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %3d", 42);
cl_assert_equal_s(str, "abc 42");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "%d %0.12G%s", 4, 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "%d %td%s", 4, 42, "b");
cl_assert_equal_s(str, "4 42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "ab%%c % E zz", 1.0);
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "ab%%c % d zz", 42);
cl_assert_equal_s(str, "ab%c 42 zz");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %-5e%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %-5d%s", 42, "b");
cl_assert_equal_s(str, "abc 42 b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %+f%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %+d%s", 42, "b");
cl_assert_equal_s(str, "abc +42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %lf%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %ld%s", 42, "b");
cl_assert_equal_s(str, "abc 42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %Lf%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %Ld%s", 42, "b");
cl_assert_equal_s(str, "abc 42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %hf%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %hd%s", 42, "b");
cl_assert_equal_s(str, "abc 42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %a%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %jd%s", 42, "b");
cl_assert_equal_s(str, "abc 42b");
//----------------------------------------------------
pbl_snprintf(str, STR_SIZE, "abc %A%s", "a", 1.0, "b");
cl_assert_equal_s(str, fp_msg);
pbl_snprintf(str, STR_SIZE, "abc %zd%s", 42, "b");
cl_assert_equal_s(str, "abc 42b");
}
void test_pbl_std__verify_memcpy_handles_bogus_parameters(void) {
// See PBL-7873
uint8_t from = 1;
uint8_t to;
// Make sure a normal copy works
pbl_memcpy(&to, &from, sizeof(from));
cl_assert_equal_i(to, 1);
// Make sure a copy with a negative size is a no-op.
to = 0;
pbl_memcpy(&to, &from, -sizeof(from));
cl_assert_equal_i(to, 0);
}
void test_pbl_std__verify_difftime_double_conversion(void) {
// Can only test positive diffs because of 64 bit vs 32 bit time_t
cl_assert_equal_i(pbl_override_difftime(30, 10), 20);
cl_assert_equal_i(pbl_override_difftime(22222222, 1), 22222222 - 1);
cl_assert_equal_i(pbl_override_difftime(0, 0), 0);
cl_assert_equal_i(pbl_override_difftime(1, 0), 1);
cl_assert_equal_i(pbl_override_difftime(2147483647, 0), 2147483647);
}

View file

@ -0,0 +1,293 @@
/*
* 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 <stdio.h>
#include <string.h>
#include "applib/persist.h"
#include "flash_region/flash_region.h"
#include "process_management/app_install_manager.h"
#include "process_management/pebble_process_md.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/persist.h"
#include "system/logging.h"
// Stubs
////////////////////////////////////
#include "fake_rtc.h"
#include "fake_spi_flash.h"
#include "stubs_analytics.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_system_reset.h"
#include "stubs_task_watchdog.h"
static PebbleProcessMd __pbl_app_info;
const PebbleProcessMd* sys_process_manager_get_current_process_md(void) {
return &__pbl_app_info;
}
// Tests
////////////////////////////////////
#define TEST_UUID_A { 0x2F, 0xF7, 0xFA, 0x04, 0x60, 0x11, 0x4A, 0x98, 0x8A, 0x3B, 0xA8, 0x26, 0xA4, 0xB8, 0x99, 0xF8 }
static const int system_uuid_id = 0;
static const Uuid system_uuid = UUID_SYSTEM;
static const int test_uuid_a_id = 1;
static const Uuid test_uuid_a = TEST_UUID_A;
static const int test_uuid_b_id = 2;
static const Uuid test_uuid_b = { 0xC3, 0x0D, 0xBA, 0xF1, 0x5F, 0x6F, 0x4F, 0x22, 0xBA, 0xAA, 0x8C, 0x2A, 0x96, 0x8C, 0xFC, 0x28 };
static const int test_uuid_c_id = 3;
static const Uuid test_uuid_c = { 0x1D, 0x6C, 0x7F, 0x01, 0xD9, 0x48, 0x42, 0xA6, 0xAA, 0x4E, 0xB2, 0x08, 0x42, 0x10, 0xEB, 0xBC };
static PebbleProcessMd __pbl_app_info = {
.uuid = TEST_UUID_A,
};
const char lipsum[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Nam dignissim ullamcorper sollicitudin. Suspendisse at "
"urna suscipit, congue purus a, posuere eros. Nulla eros urna, vestibulum "
"a dictum a, maximus sed nibh. Ut ut dui finibus, tincidunt ligula quis, "
"ornare mi. Pellentesque sagittis suscipit lacus nec consectetur. Nunc et "
"commodo neque. Vestibulum vitae dignissim sapien. Nulla scelerisque "
"finibus nisl. Suspendisse ac massa lacus. In hac habitasse platea "
"dictumst. Ut condimentum urna eros. Fusce ipsum metus, vehicula eu tortor "
"sed, congue tempus mauris. Maecenas mollis lacus non cursus bibendum. "
"Etiam id dolor lorem. Aenean scelerisque nulla sed tristique posuere. "
"Proin dui magna, gravida faucibus ultricies non, tincidunt id metus. "
"Integer a laoreet dolor, eu vulputate enim. Ut vitae hendrerit nunc, in "
"bibendum eros. Pellentesque congue ut quam id sollicitudin. Cras "
"malesuada arcu nec imperdiet cursus. Donec vitae ex eget mi imperdiet "
"efficitur id eu velit. Proin pretium ipsum sed convallis efficitur. Morbi "
"non feugiat erat. Ut ut efficitur massa. Sed eu auctor felis. Vestibulum "
"magna orci, placerat nec risus nec, ultricies congue ex. Morbi in "
"vestibulum leo. Nullam non dapibus lorem. Suspendisse blandit diam "
"posuere suscipit malesuada. Maecenas vehicula felis eu posuere euismod. "
"Fusce at velit ultrices, sagittis enim ac, ultrices lorem. Quisque "
"tincidunt fringilla suscipit. Curabitur tempus lorem metus, sed venenatis "
"augue maximus a. Duis venenatis tortor sit amet justo sodales suscipit. "
"Morbi tincidunt rutrum nisl, eget placerat nisi condimentum a. Vestibulum "
"ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia "
"Curae; Cras varius sagittis mauris, in consequat sapien tincidunt vitae. "
"Duis ipsum nunc, tristique sit amet blandit non, scelerisque non diam. "
"Etiam condimentum aliquam dictum. Nam nisi ex, cursus in ligula sit amet, "
"ultricies egestas libero. Aliquam luctus, metus quis ultricies sagittis, "
"nisi orci viverra felis, vitae luctus massa dolor sit amet dolor. Cras "
"mattis velit vitae pretium pulvinar. Pellentesque auctor, turpis at cras "
"amet.";
_Static_assert(sizeof(lipsum) > PERSIST_STRING_MAX_LENGTH,
"lipsum string is not long enough for persist tests");
void test_persist__initialize(void) {
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
persist_service_init();
persist_service_client_open(&test_uuid_a);
}
void test_persist__cleanup(void) {
persist_service_client_close(&test_uuid_a);
}
void test_persist__int(void) {
const uint32_t key = 0;
const uint32_t value = ~0;
cl_assert_equal_i(persist_read_int(key), 0);
cl_assert_equal_i(persist_write_int(key, value), sizeof(int));
cl_assert_equal_i(persist_get_size(key), sizeof(int));
cl_assert_equal_i(persist_read_int(key), value);
}
void test_persist__bool(void) {
const uint32_t key = 0;
cl_assert_equal_i(persist_read_bool(key), false);
cl_assert_equal_i(persist_write_bool(key, true), sizeof(bool));
cl_assert_equal_i(persist_get_size(key), sizeof(bool));
cl_assert_equal_i(persist_read_bool(key), true);
}
void test_persist__data(void) {
const uint32_t key = 0;
const int size = sizeof(test_uuid_a);
Uuid uuid_buffer;
cl_assert_equal_i(persist_read_data(key, &uuid_buffer, sizeof(uuid_buffer)), E_DOES_NOT_EXIST);
cl_assert_equal_i(persist_write_data(key, &test_uuid_a, sizeof(test_uuid_a)), size);
cl_assert_equal_i(persist_get_size(key), size);
cl_assert_equal_i(persist_read_data(key, &uuid_buffer, sizeof(uuid_buffer)), size);
cl_assert(uuid_equal(&test_uuid_a, &uuid_buffer));
}
void test_persist__data_too_big(void) {
char buf[PERSIST_DATA_MAX_LENGTH+2];
memset(buf, '~', sizeof(buf));
cl_assert_equal_i(persist_write_data(0, lipsum, sizeof(lipsum)),
PERSIST_DATA_MAX_LENGTH);
cl_assert_equal_i(persist_read_data(0, buf, sizeof(buf)),
PERSIST_DATA_MAX_LENGTH);
cl_assert(memcmp(lipsum, buf, PERSIST_DATA_MAX_LENGTH) == 0);
for (size_t i = PERSIST_DATA_MAX_LENGTH; i < sizeof(buf); ++i) {
cl_assert_(buf[i] == '~',
"persist_read_data writes past the end of destination buffer");
}
}
void test_persist__string_does_not_exist(void) {
char string_buffer[PERSIST_STRING_MAX_LENGTH];
memset(string_buffer, '~', sizeof(string_buffer));
cl_assert_equal_i(
persist_read_string(0, string_buffer, sizeof(string_buffer)),
E_DOES_NOT_EXIST);
for (size_t i = 0; i < sizeof(string_buffer); ++i) {
if (string_buffer[i] != '~') {
char error_msg[132];
snprintf(error_msg, sizeof(error_msg), "persist_read_string clobbers "
"destination buffer at %zd when key does not exist", i);
cl_fail(error_msg);
}
}
}
void test_persist__string_write_unterminated_string(void) {
char string_buffer[PERSIST_STRING_MAX_LENGTH + 2];
memset(string_buffer, '~', sizeof(string_buffer));
cl_assert_equal_i(persist_write_string(0, lipsum), PERSIST_STRING_MAX_LENGTH);
cl_assert_equal_i(persist_get_size(0), PERSIST_STRING_MAX_LENGTH);
cl_assert_equal_i(
persist_read_string(0, string_buffer, sizeof(string_buffer)),
PERSIST_STRING_MAX_LENGTH);
cl_assert_equal_i(string_buffer[PERSIST_STRING_MAX_LENGTH - 1], '\0');
cl_assert(strncmp(lipsum, string_buffer, PERSIST_STRING_MAX_LENGTH - 1) == 0);
for (size_t i = PERSIST_STRING_MAX_LENGTH; i < sizeof(string_buffer); ++i) {
cl_assert_(string_buffer[i] == '~',
"persist_read_string writes past the end of destination buffer");
}
}
void test_persist__size_of_nonexistent_key(void) {
cl_assert_equal_i(persist_get_size(0), E_DOES_NOT_EXIST);
}
void test_persist__size(void) {
char data[] = { 1, 2, 3, 4, 5, 6 };
cl_assert_equal_i(persist_write_data(0, data, sizeof(data)), sizeof(data));
cl_assert_equal_i(persist_get_size(0), sizeof(data));
}
void test_persist__exists(void) {
cl_assert_equal_i(persist_exists(0), S_FALSE);
cl_assert(PASSED(persist_write_int(0, 0)));
cl_assert_equal_i(persist_exists(0), S_TRUE);
}
void test_persist__delete(void) {
cl_assert_equal_i(persist_delete(0), E_DOES_NOT_EXIST);
cl_assert(PASSED(persist_write_int(0, 0)));
cl_assert_equal_i(persist_delete(0), S_TRUE);
cl_assert_equal_i(persist_delete(0), E_DOES_NOT_EXIST);
}
/*
* Confirm that fields can be reassigned values.
*/
void test_persist__overwrite(void) {
const uint32_t key = 0;
cl_assert_equal_i(persist_write_int(key, 1), sizeof(int));
cl_assert_equal_i(persist_read_int(key), 1);
cl_assert_equal_i(persist_write_int(key, 2), sizeof(int));
cl_assert_equal_i(persist_read_int(key), 2);
}
/*
* Confirm that overwriting with a smaller data size does not break tuple finding.
*/
void test_persist__overwrite_shrink(void) {
cl_assert(PASSED(persist_write_int(0, 1)));
cl_assert(PASSED(persist_write_bool(0, false)));
cl_assert(PASSED(persist_write_int(1, 2)));
cl_assert(persist_read_int(1) == 2);
}
/*
* Confirm that loading a smaller amount of data, then a larger amount of data,
* always returns the appropriate amount of data.
*/
void test_persist__partial_read_extension(void) {
char buffer[] = "Hello thar";
// Write out data
cl_assert_equal_i(persist_write_string(0, buffer), sizeof(buffer));
// Clear the cache (which has the entire string right now)
persist_service_client_close(&test_uuid_a);
persist_service_client_open(&test_uuid_a);
// Reset out buffer so we can check the read's honesty.
memset(buffer, 0, strlen(buffer));
// Read part of the data we wrote
cl_assert_equal_i(persist_read_string(0, buffer, 2), 2);
cl_assert_equal_i(strlen(buffer), 2 - 1); // -1 because null termination
cl_assert_equal_i(buffer[0], 'H');
// Then attempt to read back the entire thing
cl_assert_equal_i(persist_read_string(0, buffer, sizeof(buffer)), sizeof(buffer));
cl_assert_equal_i(strlen(buffer), 10);
cl_assert(strcmp(buffer, "Hello thar") == 0);
}
void test_persist__legacy2_max_usage(void) {
uint8_t buffer[256];
memset(buffer, 1, sizeof(buffer));
// The maximum amount of 'buffer' sized fields allowed by the old persist
// storage backend.
int n = (4 * 1024) / (9 + sizeof(buffer));
PBL_LOG_VERBOSE("n = %d", n);
for (int i = 0; i < n; ++i) {
PBL_LOG(LOG_LEVEL_DEBUG, "i = %d", i);
cl_assert_equal_i(persist_write_data(i, &buffer, sizeof(buffer)), sizeof(buffer));
}
// Don't be too strict about preventing apps from using more persist than they
// had available under the old implementation.
// cl_assert_equal_i(persist_write_data(n + 1, &buffer, sizeof(buffer)),
// E_OUT_OF_STORAGE);
}

View file

@ -0,0 +1,753 @@
/*
* 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 "applib/template_string.h"
#include "applib/template_string_private.h"
#include "util/size.h"
#include <limits.h>
#include <string.h>
#define DEBUG_PRINTING 1
#if DEBUG_PRINTING
# include <stdio.h>
#endif
void prv_template_evaluate_filter(TemplateStringState *state, const char *filter_name,
const char *params);
static const char *s_error_strings[] = {
"Success.",
"Can't resolve.",
"Missing closing brace.",
"Missing argument.",
"No result generated.",
"Unknown filter.",
"format() was not last filter.",
"Time unit in predicate is invalid.",
"Escape character at end of string.",
"Opening parenthesis for filter was missing.",
"Closing parenthesis for filter was missing.",
"Invalid conversion specifier for format.",
"Invalid parameter.",
"Opening quote for filter was missing.",
"Closing quote for filter was missing.",
"Invalid argument separator.",
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fakes & Stubs
#include "stubs_passert.h"
#include "stubs_i18n.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test setup
#define EVAL_FALL_THROUGH -1337
#define EVAL_DEFAULT 0
static TemplateStringState s_state;
static char s_output[256];
static TemplateStringVars s_vars;
static TemplateStringError s_error;
static TemplateStringEvalConditions s_cond;
static void prv_state_init(void) {
s_state = (TemplateStringState){
.output = s_output,
.output_remaining = sizeof(s_output),
.vars = &s_vars,
.error = &s_error,
.eval_cond = &s_cond,
.time_was_until = false,
.filter_state = 0,
.filters_complete = false,
};
memset(s_output, 'Z', sizeof(s_output));
memset(&s_vars, 0, sizeof(s_vars));
memset(&s_error, 0, sizeof(s_error));
memset(&s_cond, 0, sizeof(s_cond));
s_cond.eval_time = INT_MAX;
}
void test_template_string__initialize(void) {
}
void test_template_string__cleanup(void) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test truncation
static const struct {
size_t size;
time_t intime;
const char *instr;
const char *output;
} s_truncation_tests[] = {
{ .size = 1, .intime = 1000,
.instr = "foo",
.output = "",
},
{ .size = 3, .intime = 1000,
.instr = "foo",
.output = "fo",
},
{ .size = 3, .intime = 1000,
.instr = "{format('foo')}",
.output = "fo",
},
{ .size = 1, .intime = 1000,
.instr = "{time_until(1004)|format('%S')}",
.output = "",
},
{ .size = 2, .intime = 1000,
.instr = "{time_until(1040)|format('%S')}",
.output = "4",
},
{ .size = 6, .intime = 1000,
.instr = "{time_until(1040)|format('%uS')}",
.output = "40 se",
},
};
void test_template_string__truncation(void) {
for(size_t i = 0; i < ARRAY_LENGTH(s_truncation_tests); i++) {
#if DEBUG_PRINTING
printf("size: %zu\n", s_truncation_tests[i].size);
printf("intime: %jd\n", s_truncation_tests[i].intime);
printf("input: \"%s\"\n", s_truncation_tests[i].instr);
printf("result: \"%s\"\n", s_truncation_tests[i].output);
#endif
TemplateStringVars vars = {};
TemplateStringError err = {};
TemplateStringEvalConditions cond = {};
vars.current_time = s_truncation_tests[i].intime;
cond.eval_time = EVAL_FALL_THROUGH;
memset(s_output, 'Z', sizeof(s_output));
template_string_evaluate(s_truncation_tests[i].instr, s_output, s_truncation_tests[i].size,
&cond, &vars, &err);
cl_assert_equal_s(s_output, s_truncation_tests[i].output);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test NULL arguments
void test_template_string__null_arguments(void) {
{
TemplateStringVars vars = {};
TemplateStringError err = {};
TemplateStringEvalConditions cond = {};
vars.current_time = 0;
cond.eval_time = EVAL_FALL_THROUGH;
cond.force_eval_on_time = true;
memset(s_output, 'Z', sizeof(s_output));
bool ret = template_string_evaluate("test string {time_until(5)|format('%uS')}", s_output,
sizeof(s_output), &cond, &vars, &err);
cl_assert_equal_b(ret, true);
cl_assert_equal_s(s_output, "test string 5 seconds");
cl_assert_equal_i(cond.eval_time, 1);
cl_assert_equal_b(cond.force_eval_on_time, true);
}
{
TemplateStringVars vars = {};
TemplateStringError err = {};
TemplateStringEvalConditions cond = {};
vars.current_time = 0;
cond.eval_time = EVAL_FALL_THROUGH;
cond.force_eval_on_time = true;
strcpy(s_output, "hurf");
bool ret = template_string_evaluate("test string {time_until(5)|format('%uS')}", s_output, 0,
&cond, &vars, &err);
cl_assert_equal_b(ret, true);
cl_assert_equal_s(s_output, "hurf");
cl_assert_equal_i(cond.eval_time, 1);
cl_assert_equal_b(cond.force_eval_on_time, true);
}
{
TemplateStringVars vars = {};
TemplateStringError err = {};
TemplateStringEvalConditions cond = {};
vars.current_time = 0;
cond.eval_time = EVAL_FALL_THROUGH;
cond.force_eval_on_time = true;
strcpy(s_output, "hurf");
bool ret = template_string_evaluate("test string {time_until(5)|format('%uS')}", NULL,
sizeof(s_output), &cond, &vars, &err);
cl_assert_equal_b(ret, true);
cl_assert_equal_s(s_output, "hurf");
cl_assert_equal_i(cond.eval_time, 1);
cl_assert_equal_b(cond.force_eval_on_time, true);
}
{
TemplateStringVars vars = {};
TemplateStringError err = {};
vars.current_time = 0;
memset(s_output, 'Z', sizeof(s_output));
bool ret = template_string_evaluate("test string {time_until(5)|format('%uS')}", s_output,
sizeof(s_output), NULL, &vars, &err);
cl_assert_equal_b(ret, true);
cl_assert_equal_s(s_output, "test string 5 seconds");
}
{
TemplateStringVars vars = {};
TemplateStringError err = {};
vars.current_time = 0;
bool ret = template_string_evaluate("test string {time_until(5)|format('%uS',)}", NULL, 0,
NULL, &vars, &err);
cl_assert_equal_b(ret, false);
cl_assert_equal_i(err.status, TemplateStringErrorStatus_MissingArgument);
cl_assert_equal_i(err.index_in_string, 40);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test `time_since` and `time_until` filters
static const struct {
time_t current_time;
const char *params;
intmax_t done_state;
} s_time_since_tests[] = {
{ .current_time = 1234567,
.params = "1234567)",
.done_state = 0,
},
{ .current_time = 1234567,
.params = "1234560)",
.done_state = 7,
},
{ .current_time = 1234567,
.params = "1234570)",
.done_state = -3,
},
{ .current_time = 234567,
.params = "1234567)",
.done_state = -1000000,
},
};
void test_template_string__time_since_until(void) {
for(size_t i = 0; i < ARRAY_LENGTH(s_time_since_tests); i++) {
#if DEBUG_PRINTING
printf("current_time: %ld\n", s_time_since_tests[i].current_time);
printf("parameter: \"%s\"\n", s_time_since_tests[i].params);
printf("result: %jd\n", s_time_since_tests[i].done_state);
#endif
prv_state_init();
s_vars.current_time = s_time_since_tests[i].current_time;
prv_template_evaluate_filter(&s_state, "time_since", s_time_since_tests[i].params);
cl_assert_equal_b(s_state.filters_complete, false);
cl_assert_equal_i(s_state.filter_state, s_time_since_tests[i].done_state);
prv_state_init();
s_vars.current_time = s_time_since_tests[i].current_time;
prv_template_evaluate_filter(&s_state, "time_until", s_time_since_tests[i].params);
cl_assert_equal_b(s_state.filters_complete, false);
cl_assert_equal_i(s_state.filter_state, -s_time_since_tests[i].done_state);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test `format` filter
typedef struct FormatTestData {
const char *params;
intmax_t filter_state;
bool time_was_until;
const char *expect_str;
time_t expect_eval_time;
TemplateStringErrorStatus expect_status;
size_t expect_index;
} FormatTestData;
static const FormatTestData s_format_tests[] = {
// Simple text tests
{ "'doo')", 3600, true,
"doo",
INT_MAX,
},
// Some error testing
{ ">5H'%T')", 1, true,
"",
INT_MAX,
TemplateStringErrorStatus_InvalidTimeUnit,
3,
},
{ "'%T',)", 1, true,
"1",
1,
TemplateStringErrorStatus_MissingArgument,
5,
},
{ "'%T'fj)", 1, true,
"1",
1,
TemplateStringErrorStatus_InvalidArgumentSeparator,
4,
},
// Basic %T tests
{ "'%T')", 1, true,
"1",
1,
},
{ "'%T')", 60, true,
"1:00",
1,
},
{ "'%T')", 3600, true,
"1:00:00",
1,
},
{ "'%T')", -3666, true,
"-1:01:06",
1,
},
// Basic %R tests
{ "'%R')", 1, true,
"0",
2,
},
{ "'%R')", 66, true,
"1",
7,
},
{ "'%R')", 3607, true,
"1:00",
8,
},
{ "'%R')", -3666, true,
"-1:01",
7,
},
// Advanced %T tests
{ "'%0T')", 3666, true,
"01:01:06",
1,
},
{ "'%uT')", 3666, true,
"1 hour, 1 minute, and 6 seconds",
1,
},
{ "'%aT')", 3666, true,
"1 hr 1 min 6 sec",
1,
},
{ "'%auT')", 3666, true,
"1 hour, 1 minute, and 6 seconds",
1,
},
{ "'%0uT')", 3666, true,
"01 hour, 01 minute, and 06 seconds",
1,
},
{ "'%fT')", 129666, true,
"36:01:06",
1,
},
{ "'%T')", 129666, true,
"12:01:06",
1,
},
// Advanced %R tests
{ "'%0R')", 3666, true,
"01:01",
7,
},
{ "'%uR')", 3666, true,
"1 hour, and 1 minute",
7,
},
{ "'%aR')", 3666, true,
"1 hr 1 min",
7,
},
{ "'%auR')", 3666, true,
"1 hour, and 1 minute",
7,
},
{ "'%0uR')", 3666, true,
"01 hour, and 01 minute",
7,
},
{ "'%fR')", 129666, true,
"36:01",
7,
},
{ "'%R')", 129666, true,
"12:01",
7,
},
// Predicate tests
{ ">1d12H:'%0ud',<0S:'%-uS since',<60S:'%uS')", 9, true,
"9 seconds",
1,
},
{ ">1d12H:'%0ud',<0S:'%-uS since',<60S:'%0uS')", 129600, true,
"",
129601 - 60, // Time left until we hit <60S
TemplateStringErrorStatus_CantResolve,
42,
},
{ ">1d12H:'%0fud',<0S:'%-uS since',<60S:'%uS')", 129601, true,
"01 day",
1,
},
// 1d12H1S
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 129601, true,
"01 day",
43202, // 12H2S (time=1d-1S)
},
// 1d12H
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 129600, true,
"01 day",
43201, // 12H1S (time=1d-1S)
},
// 1d13H-100S
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 133100, true,
"01 day",
46701, // 13H-99S (time=1d12H)
},
// 1d13H100S
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 133300, true,
"01 day",
101, // time=1d13H-1S
},
// 1d14H100S
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 136900, true,
"01 day",
101, // time=1d14H-1S
},
// Predicate tests w/ since
// 1d14H
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 136800, false,
"01 day",
36000, // 2D
},
// 1d14H-100S
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 136700, false,
"01 day",
100, // time=1d14H
},
// 1d13H
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 133200, false,
"01 day",
3600, // 1H (time=1d14H)
},
// 1d13H-10S
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 133190, false,
"01 day",
10, // 10S (time=1d13H)
},
// 1d12H
{ ">=1d14H:'%0fud',<1d13H:'%0fud',>1d12H:'%0fud')", 129600, false,
"01 day",
1, // (time=1d12H1S)
},
};
void test_template_string__format(void) {
for(size_t i = 0; i < ARRAY_LENGTH(s_format_tests); i++) {
const FormatTestData *test = &s_format_tests[i];
prv_state_init();
s_state.filter_state = test->filter_state;
s_state.time_was_until = test->time_was_until;
prv_template_evaluate_filter(&s_state, "format", test->params);
// The filter isn't required to NUL-terminate, so we gotta do it manually sometimes.
if (*s_state.output != 'Z') {
cl_assert_equal_i(*s_state.output, '\0');
} else {
*s_state.output = '\0';
}
size_t err_index = s_state.position - test->params;
#if DEBUG_PRINTING
printf("parameter: \"%s\"\n", test->params);
printf("filter_state: %jd %s\n", test->filter_state,
test->time_was_until ? "until" : "since");
printf("expect: \"%s\" err %d @ %zu eval@%jd\n", test->expect_str,
test->expect_status, test->expect_index,
test->expect_eval_time);
printf("got : \"%s\" err %d @ %zu eval@%jd\n", s_output,
s_error.status, err_index, s_cond.eval_time);
#endif
if (s_error.status) {
#if DEBUG_PRINTING
printf("\"%s\"\n", test->params);
printf("%*s^\n", (int)err_index + 1, "");
if (s_error.status >= TemplateStringErrorStatusCount) {
printf("Invalid status code\n");
} else {
printf("%s\n", s_error_strings[s_error.status]);
}
#endif
cl_assert_equal_i(s_error.status, test->expect_status);
cl_assert_equal_i(err_index, test->expect_index);
}
cl_assert_equal_s(s_output, test->expect_str);
cl_assert_equal_i(s_cond.eval_time, test->expect_eval_time);
#if DEBUG_PRINTING
printf("\n");
#endif
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test pipeline parser
static const struct {
const char *instr;
time_t intime;
const char *expect_str;
time_t expect_time;
bool expect_rv;
TemplateStringErrorStatus expect_status;
size_t expect_index;
} s_full_tests[] = {
{ "Basicist test~", 1000000000,
"Basicist test~",
0,
true,
},
{ "\\\\\\", 1000000000,
"\\",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_InvalidEscapeCharacter,
3,
},
{ "\\e", 1000000000,
"e",
0,
true,
},
{ "\\\\\\{}", 1000000000,
"\\{}",
0,
true,
},
{ "\\\\{end()}", 1000000000,
"\\",
0,
true,
},
{ "\\{end()}", 1000000000,
"{end()}",
0,
true,
},
{ "Harder test {} bazza", 1000000000,
"Harder test ",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_NoResultGenerated,
13,
},
{ "Failer {time_until}", 1000000000,
"Failer ",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_MissingOpeningParen,
8,
},
{ "B {time_until(1)|format('\\\\')}", 0,
"B \\",
0,
true,
},
{ "B {time_until(1)|format('\\%foo')}", 0,
"B %foo",
0,
true,
},
{ "B {time_until(1)|format('%%foo')}", 0,
"B %foo",
0,
true,
},
{ "B {time_until(1)|format('\\'')}", 0,
"B '",
0,
true,
},
{ "B {time_until(1)|format('\\)}", 0,
"B )}",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_MissingClosingQuote,
28,
},
{ "B {time_until(1)|format('%T')}", 0,
"B 1",
1,
true,
},
{ "B {time_until(1)|format('%K')}", 0,
"B ",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_InvalidConversionSpecifier,
26,
},
{ "B {time_until(1)|format('%f')}", 0,
"B ",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_InvalidConversionSpecifier,
27,
},
{ "F {time_until(100)}", 1000000000,
"F ",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_NoResultGenerated,
18,
},
{ "{end()", 1000000000,
"",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_MissingClosingBrace,
6,
},
{ "{end(hurf", 1000000000,
"",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_MissingClosingParen,
5,
},
{ "{end}", 1000000000,
"",
EVAL_DEFAULT,
false,
TemplateStringErrorStatus_MissingOpeningParen,
1,
},
{ "B {time_until(129666)|format('%T')}", 0,
"B 12:01:06",
1,
true,
},
{ "Countdown: {time_until(1)|format(>1d12H:'%0ud',<0S:'%-uS since',<60S:'%uS')} foof",
10,
"Countdown: 9 seconds since foof",
10 + 1,
true,
},
{ "Countdown: {time_until(129601)|format(>1d12H:'%0ud',<0S:'%-uS since',<60S:'%0uS')} foof",
1,
"Countdown: ",
1 + 129601 - 60, // Time left until we hit <60S
false,
TemplateStringErrorStatus_CantResolve,
80,
},
{ "B {time_until(129666)|format('boop)I\\'m a filter')}", 0,
"B boop)I'm a filter",
0,
true,
},
{ "B {time_until(129666)|format('%T')} AND {time_until(129660)|format('%T')}", 0,
"B 12:01:06 AND 12:01:00",
1,
true,
},
};
void test_template_string__full_test(void) {
for(int i = 0; i < ARRAY_LENGTH(s_full_tests); i++) {
TemplateStringVars vars = {};
TemplateStringError err = {};
TemplateStringEvalConditions cond = {};
vars.current_time = s_full_tests[i].intime;
cond.eval_time = EVAL_FALL_THROUGH;
memset(s_output, 'Z', sizeof(s_output));
bool rv = template_string_evaluate(s_full_tests[i].instr, s_output, sizeof(s_output),
&cond, &vars, &err);
#if DEBUG_PRINTING
printf("instr: \"%s\"\n", s_full_tests[i].instr);
printf("outstr: \"%s\"\n", s_output);
printf("next_eval: %ld\n", cond.eval_time);
printf("rv: %s\n", rv ? "true" : "false");
if (!rv) {
printf("err.status: %X\n", err.status);
printf("err.index: %zu\n", err.index_in_string);
printf("\"%s\"\n", s_full_tests[i].instr);
printf("%*s^\n", (int)err.index_in_string + 1, "");
if (err.status >= TemplateStringErrorStatusCount) {
printf("Invalid status code\n");
} else {
printf("%s\n", s_error_strings[err.status]);
}
}
printf("\n");
#endif
cl_assert_equal_s(s_output, s_full_tests[i].expect_str);
cl_assert_equal_i(rv, s_full_tests[i].expect_rv);
cl_assert_equal_i(cond.eval_time, s_full_tests[i].expect_time);
if (cond.eval_time != 0) {
cl_assert_equal_b(cond.force_eval_on_time, true);
} else {
cl_assert_equal_b(cond.force_eval_on_time, false);
}
if (!rv) {
cl_assert_equal_i(err.status, s_full_tests[i].expect_status);
cl_assert_equal_i(err.index_in_string, s_full_tests[i].expect_index);
}
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text_render.h"
#include "clar.h"
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
GBitmap* graphics_context_get_bitmap(GContext* ctx) { return NULL; }
void graphics_context_mark_dirty_rect(GContext* ctx, GRect rect) {}
const GlyphData* text_resources_get_glyph(FontCache* font_cache, const Codepoint codepoint,
FontInfo* fontinfo) { return NULL; }
extern int32_t prv_convert_1bit_addr_to_8bit_x(GBitmap *dest_bitmap, uint32_t *block_addr,
int32_t y_offset);
static int32_t prv_get_8bit_x_from_1bit_x(int32_t dest_1bit_x) {
return (((dest_1bit_x / 32) * 4)) * 8;
}
void test_text_render__convert_1bit_to_8bit_144x168(void) {
GSize size = GSize(144, 168);
const int row_1bit_size_words = 1 + (size.w - 1) / 32;
GBitmap *bitmap = gbitmap_create_blank(size, GBitmapFormat8Bit);
uintptr_t base = (uintptr_t)bitmap->addr;
int dest_x = 0;
int dest_y = 0;
uint32_t *block_addr = NULL;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
dest_x = 50;
dest_y = 0;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
dest_x = 0;
dest_y = 50;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
dest_x = 20;
dest_y = 100;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
gbitmap_destroy(bitmap);
}
void test_text_render__convert_1bit_to_8bit_180x180(void) {
GSize size = GSize(180, 180);
const int row_1bit_size_words = 1 + (size.w - 1) / 32;
GBitmap *bitmap = gbitmap_create_blank(size, GBitmapFormat8Bit);
uintptr_t base = (uintptr_t)bitmap->addr;
int dest_x = 0;
int dest_y = 0;
uint32_t *block_addr = NULL;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
dest_x = 50;
dest_y = 0;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
dest_x = 0;
dest_y = 50;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
dest_x = 20;
dest_y = 100;
block_addr = (uint32_t *)(uintptr_t)(((dest_y * row_1bit_size_words) + (dest_x / 32)) * 4);
cl_assert_equal_i(prv_convert_1bit_addr_to_8bit_x(bitmap, block_addr, dest_y),
prv_get_8bit_x_from_1bit_x(dest_x));
gbitmap_destroy(bitmap);
}

View file

@ -0,0 +1,328 @@
/*
* 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 "fixtures/load_test_resources.h"
// FW headers
#include "applib/graphics/text_resources.h"
#include "resource/resource.h"
#include "resource/resource_ids.auto.h"
#include "resource/system_resource.h"
#include "util/size.h"
// Fakes
#include "fake_app_manager.h"
// Stubs
#include "stubs_analytics.h"
#include "stubs_bootbits.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_passert.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_queue.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_syscalls.h"
#include "stubs_prompt.h"
#include "stubs_task_watchdog.h"
#include "stubs_memory_layout.h"
#define WILDCARD_CODEPOINT 0x25AF
static FontCache s_font_cache;
static FontInfo s_font_info;
#define FONT_COMPRESSION_FIXTURE_PATH "font_compression"
// Helpers
////////////////////////////////////
static uint8_t glyph_get_size_bytes(const GlyphData *glyph) {
return ((glyph->header.width_px * glyph->header.height_px) + (8 - 1)) / 8;
}
void test_text_resources__initialize(void) {
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 */);
load_resource_fixture_on_pfs(RESOURCES_FIXTURE_PATH, CHINESE_FIXTURE_NAME, "lang");
//cl_assert(resource_has_valid_system_resources());
memset(&s_font_info, 0, sizeof(s_font_info));
memset(&s_font_cache, 0, sizeof(s_font_cache));
FontCache *font_cache = &s_font_cache;
memset(font_cache->cache_keys, 0, sizeof(font_cache->cache_keys));
memset(font_cache->cache_data, 0, sizeof(font_cache->cache_data));
keyed_circular_cache_init(&font_cache->line_cache, font_cache->cache_keys,
font_cache->cache_data, sizeof(LineCacheData), LINE_CACHE_SIZE);
resource_init();
}
void test_text_resources__cleanup(void) {
}
void test_text_resources__init_font(void) {
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3);
cl_assert_equal_i(s_font_info.base.md.wildcard_codepoint, WILDCARD_CODEPOINT);
cl_assert_equal_i(s_font_info.base.md.codepoint_bytes, 2);
}
void test_text_resources__horiz_advance(void) {
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info);
cl_assert(horiz_advance != 0);
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info));
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info));
}
void test_text_resources__horiz_advance_multiple(void) {
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info);
cl_assert(horiz_advance != 0);
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, 'a', &s_font_info));
horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'b', &s_font_info);
cl_assert(horiz_advance != 0);
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, 'b', &s_font_info));
horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache, 'c', &s_font_info);
cl_assert(horiz_advance != 0);
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, 'c', &s_font_info));
}
void test_text_resources__get_glyph_multiple(void) {
const uint8_t a_glyph_data_bytes[] = {0x2e, 0x42, 0x2e, 0x63, 0xb6};
const uint8_t b_glyph_data_bytes[] = {0x21, 0x84, 0x36, 0x63, 0x8c, 0x71, 0x36};
const uint8_t c_glyph_data_bytes[] = {0x2e, 0x86, 0x10, 0x42, 0x74};
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
uint8_t glyph_size_bytes;
const GlyphData *glyph;
glyph = text_resources_get_glyph(&s_font_cache, 'a', &s_font_info);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(a_glyph_data_bytes, glyph->data, glyph_size_bytes);
glyph = text_resources_get_glyph(&s_font_cache, 'b', &s_font_info);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(b_glyph_data_bytes, glyph->data, glyph_size_bytes);
glyph = text_resources_get_glyph(&s_font_cache, 'c', &s_font_info);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(c_glyph_data_bytes, glyph->data, glyph_size_bytes);
}
void test_text_resources__init_backup_font(void) {
// load the built in fallback font
uint32_t font_fallback = RESOURCE_ID_FONT_FALLBACK_INTERNAL;
cl_assert(text_resources_init_font(0, font_fallback, 0, &s_font_info));
cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3);
cl_assert_equal_i(s_font_info.base.md.wildcard_codepoint, WILDCARD_CODEPOINT);
}
void test_text_resources__test_backup_wildcard(void) {
const uint8_t wildcard_bytes[] = {0x3f, 0xc6, 0x18, 0x63, 0x8c, 0x31, 0xc6, 0x0f};
uint32_t font_fallback = RESOURCE_ID_FONT_FALLBACK_INTERNAL;
cl_assert(text_resources_init_font(0, font_fallback, 0, &s_font_info));
int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache,
WILDCARD_CODEPOINT, &s_font_info);
cl_assert(horiz_advance != 0);
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info));
const GlyphData *glyph = text_resources_get_glyph(&s_font_cache,
WILDCARD_CODEPOINT, &s_font_info);
cl_assert_equal_i(glyph->header.width_px, 5);
cl_assert_equal_i(glyph->header.height_px, 12);
uint8_t glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(wildcard_bytes, glyph->data, glyph_size_bytes);
}
void test_text_resources__test_gothic_wildcard(void) {
uint8_t wildcard_bytes[] = {0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0x0c, 0xfe, 0x01};
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
int8_t horiz_advance = text_resources_get_glyph_horiz_advance(&s_font_cache,
WILDCARD_CODEPOINT,
&s_font_info);
cl_assert(horiz_advance != 0);
cl_assert_equal_i(horiz_advance,
text_resources_get_glyph_horiz_advance(&s_font_cache, WILDCARD_CODEPOINT, &s_font_info));
const GlyphData *glyph = text_resources_get_glyph(&s_font_cache,
WILDCARD_CODEPOINT,
&s_font_info);
cl_assert_equal_i(glyph->header.width_px, 7);
cl_assert_equal_i(glyph->header.height_px, 15);
uint8_t glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(wildcard_bytes, glyph->data, glyph_size_bytes);
}
void test_text_resources__extended_font(void) {
const uint8_t chinese_wildcard_bytes[] = {0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0x0c, 0xfe, 0x01};
const uint8_t a_glyph_data_bytes[] = {0x2e, 0x42, 0x2e, 0x63, 0xb6};
const uint8_t chinese_glyph_data_bytes[] = {0x00, 0x0C, 0xE2, 0x01, 0x0F, 0x80, 0x30, 0x40,
0x08, 0x10, 0x04, 0x08, 0x82, 0xFC, 0xFF, 0x80,
0x00, 0x44, 0x00, 0x26, 0x01, 0x11, 0x41, 0x08,
0x11, 0x84, 0x04, 0x82, 0xC0, 0x01, 0x40, 0x00};
uint32_t gothic_18_bold_handle = RESOURCE_ID_GOTHIC_18;
uint32_t gothic_18_bold_extended_handle = RESOURCE_ID_GOTHIC_18_EXTENDED;
cl_assert(text_resources_init_font(0, gothic_18_bold_handle, gothic_18_bold_extended_handle, &s_font_info));
cl_assert(s_font_info.loaded);
cl_assert(s_font_info.extended);
uint8_t glyph_size_bytes;
const GlyphData *glyph;
glyph = text_resources_get_glyph(&s_font_cache, 'a', &s_font_info);
cl_assert(glyph != NULL);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(a_glyph_data_bytes, glyph->data, glyph_size_bytes);
glyph = text_resources_get_glyph(&s_font_cache, 0x4E50 /* 乐 */, &s_font_info);
// the chinese pbpack contains the letter 你, it should succeed
cl_assert(glyph != NULL);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(chinese_glyph_data_bytes, glyph->data, glyph_size_bytes);
glyph = text_resources_get_glyph(&s_font_cache, 0x8888 /* 袈 */, &s_font_info);
// the chinese pbpack does not contain the letter 袈, it should return the wildcard
cl_assert(glyph != NULL);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(chinese_wildcard_bytes, glyph->data, glyph_size_bytes);
}
void test_text_resources__test_emoji_font(void) {
const uint8_t phone_bytes[] = {0xfe, 0x81, 0x81, 0x3c, 0x66, 0x42, 0xc3, 0xe7, 0xff, 0x00, 0x00, 0x00};
uint32_t gothic_18_emoji_handle = RESOURCE_ID_GOTHIC_18_EMOJI;
cl_assert(text_resources_init_font(0, gothic_18_emoji_handle, 0, &s_font_info));
uint8_t glyph_size_bytes;
const GlyphData *glyph;
const Codepoint PHONE_CODEPOINT = 0x260E;
glyph = text_resources_get_glyph(&s_font_cache, PHONE_CODEPOINT, &s_font_info);
cl_assert(glyph != NULL);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(phone_bytes, glyph->data, glyph_size_bytes);
}
void DISABLED_test_text_resources__test_emoji_fallback(void) {
const uint8_t phone_bytes[] = {0xfe, 0x81, 0x81, 0x3c, 0x66, 0x42, 0xc3, 0xe7, 0xff, 0x00, 0x00, 0x00};
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
uint8_t glyph_size_bytes;
const GlyphData *glyph;
const Codepoint PHONE_CODEPOINT = 0x260E;
glyph = text_resources_get_glyph(&s_font_cache, PHONE_CODEPOINT, &s_font_info);
cl_assert(glyph != NULL);
glyph_size_bytes = glyph_get_size_bytes(glyph);
cl_assert_equal_m(phone_bytes, glyph->data, glyph_size_bytes);
}
void test_text_resources__test_glyph_decompression(void) {
// There is no way to get the list of glyphs present in a font with the existing API. This list
// of ranges lists the 371 glyphs currently in fontname.ttf
typedef struct Codepoint_Range {
uint16_t start;
uint16_t end;
} CodePoint_Range;
const CodePoint_Range codepoint_range[] = {
{ 0x0020, 0x007E }, { 0x00A0, 0x00AC }, { 0x00AE, 0x00D6 }, { 0x00D9, 0x017F },
{ 0x0192, 0x0192 }, { 0x01FC, 0x01FF }, { 0x0218, 0x021B }, { 0x02C6, 0x02DD },
{ 0x03C0, 0x03C0 }, { 0x2013, 0x2014 }, { 0x2018, 0x201A }, { 0x201C, 0x201E },
{ 0x2020, 0x2022 }, { 0x2026, 0x2026 }, { 0x2030, 0x2030 }, { 0x2039, 0x203A },
{ 0x2044, 0x2044 }, { 0x20AC, 0x20AC }, { 0x2122, 0x2122 }, { 0x2126, 0x2126 },
{ 0x2202, 0x2202 }, { 0x2206, 0x2206 }, { 0x220F, 0x220F }, { 0x2211, 0x2212 },
{ 0x221A, 0x221A }, { 0x221E, 0x221E }, { 0x222B, 0x222B }, { 0x2248, 0x2248 },
{ 0x2260, 0x2260 }, { 0x2264, 0x2265 }, { 0x25AF, 0x25AF }, { 0x25CA, 0x25CA },
{ 0xF6C3, 0xF6C3 }, { 0xFB01, 0xFB02 }
};
// Create a second FontInfo for the compressed font.
// The uncompressed font will use the global.
FontInfo font_info_compressed;
memset(&font_info_compressed, 0, sizeof(font_info_compressed));
// Load GOTHIC_18
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
cl_assert_equal_i(FONT_VERSION(s_font_info.base.md.version), 3);
cl_assert(!HAS_FEATURE(s_font_info.base.md.version, VERSION_FIELD_FEATURE_RLE4));
// Load GOTHIC_18_COMPRESSED. This is the same font, added by hand to the system resource pack.
// To do this, simply copy the GOTHIC_18 stanza in resource/normal/base/resource_map.json, change
// the name to include _COMPRESSED, and add the field: "compress": "RLE4". Rebuild, and run
// ./tools/update_system_pbpack.sh
uint32_t gothic_18_compressed_handle = RESOURCE_ID_GOTHIC_18_COMPRESSED; // Read source to fix
cl_assert(text_resources_init_font(0, gothic_18_compressed_handle, 0, &font_info_compressed));
cl_assert_equal_i(FONT_VERSION(font_info_compressed.base.md.version), 3);
cl_assert(HAS_FEATURE(font_info_compressed.base.md.version, VERSION_FIELD_FEATURE_RLE4));
// For each glyph in the font, get both the compressed and uncompressed bit field & header, and
// assert that they are identical (ignoring any possible garbage after the bitmap).
// Load the uncompressed glyph into this local glyph_buffer, and use the font cache for the
// compressed glyph.
uint8_t glyph_buffer[sizeof(GlyphHeaderData) + CACHE_GLYPH_SIZE];
for (unsigned index = 0; index < ARRAY_LENGTH(codepoint_range); ++index) {
for (unsigned codepoint = codepoint_range[index].start;
codepoint <= codepoint_range[index].end; ++codepoint) {
const GlyphData *glyph = text_resources_get_glyph(&s_font_cache, codepoint, &s_font_info);
cl_assert(glyph);
unsigned glyph_size = sizeof(GlyphHeaderData) + glyph_get_size_bytes(glyph);
memcpy(glyph_buffer, glyph->data, glyph_size);
glyph = text_resources_get_glyph(&s_font_cache, codepoint, &font_info_compressed);
cl_assert(glyph);
cl_assert_equal_m(glyph->data, glyph_buffer, glyph_size);
}
}
}

View file

@ -0,0 +1,352 @@
/*
* 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 "pebble_asserts.h"
#include "applib/unobstructed_area_service.h"
// Stubs
/////////////////////
#include "stubs_app.h"
#include "stubs_app_manager.h"
#include "stubs_app_state.h"
#include "stubs_events.h"
#include "stubs_framebuffer.h"
#include "stubs_graphics.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_process_manager.h"
#include "stubs_ui_window.h"
// Fakes
/////////////////////
#include "fake_event_service.h"
// Statics
/////////////////////
void *s_context_target;
typedef struct UnobstructedAreaTestData {
void *context;
int num_will_change_calls;
int num_change_calls;
int num_did_change_calls;
GRect last_will_change_final_area;
AnimationProgress last_change_progress;
} UnobstructedAreaTestData;
UnobstructedAreaTestData s_data;
static void prv_will_change(GRect final_area, void *context) {
s_data.last_will_change_final_area = final_area;
cl_assert_equal_p(context, s_data.context);
s_data.num_will_change_calls++;
}
static void prv_change(AnimationProgress progress, void *context) {
s_data.last_change_progress = progress;
cl_assert_equal_p(context, s_data.context);
s_data.num_change_calls++;
}
static void prv_did_change(void *context) {
cl_assert_equal_p(context, s_data.context);
s_data.num_did_change_calls++;
}
// Test boilerplate
/////////////////////
void test_unobstructed_area_service__initialize(void) {
s_data = (UnobstructedAreaTestData) {
.context = &s_context_target,
.last_change_progress = -1,
};
fake_event_service_init();
s_app_state_framebuffer = &(FrameBuffer) { .size = DISP_FRAME.size };
unobstructed_area_service_init(app_state_get_unobstructed_area_state(), DISP_ROWS);
}
void test_unobstructed_area_service__cleanup(void) {
app_unobstructed_area_service_unsubscribe();
unobstructed_area_service_deinit(app_state_get_unobstructed_area_state());
}
// Tests
//////////////////////
void test_unobstructed_area_service__subscribe(void) {
// Unsubscribing first should not crash
app_unobstructed_area_service_unsubscribe();
cl_assert(!app_state_get_unobstructed_area_state()->handlers.will_change);
cl_assert(!app_state_get_unobstructed_area_state()->handlers.change);
cl_assert(!app_state_get_unobstructed_area_state()->handlers.did_change);
// Subscribing should use the event service
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
.change = prv_change,
.did_change = prv_did_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.will_change, prv_will_change);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.change, prv_change);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.did_change, prv_did_change);
// Unsubscribing after subscription should cancel subscriptions
app_unobstructed_area_service_unsubscribe();
cl_assert(!app_state_get_unobstructed_area_state()->handlers.will_change);
cl_assert(!app_state_get_unobstructed_area_state()->handlers.change);
cl_assert(!app_state_get_unobstructed_area_state()->handlers.did_change);
// Unsubscribing again should not crash
app_unobstructed_area_service_unsubscribe();
cl_assert(!app_state_get_unobstructed_area_state()->handlers.will_change);
cl_assert(!app_state_get_unobstructed_area_state()->handlers.change);
cl_assert(!app_state_get_unobstructed_area_state()->handlers.did_change);
}
void test_unobstructed_area_service__will_change(void) {
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.will_change, prv_will_change);
const GRect from_area = GRect(0, 0, DISP_COLS, 400);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_will_change_calls, 1);
const GRect to_area_expected = GRect(0, 0, DISP_COLS, MIN(200, DISP_ROWS));
cl_assert_equal_grect(s_data.last_will_change_final_area, to_area_expected);
}
void test_unobstructed_area_service__will_change_twice(void) {
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.will_change, prv_will_change);
const GRect from_area = GRect(0, 0, DISP_COLS, 400);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
fake_event_service_handle_last();
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
cl_assert_passert(fake_event_service_handle_last());
}
void test_unobstructed_area_service__change(void) {
UnobstructedAreaHandlers handlers = {
.change = prv_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.change, prv_change);
const GRect from_area = GRect(0, 0, DISP_COLS, 400);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
fake_event_service_handle_last();
const GRect area = GRect(0, 0, DISP_COLS, 200);
const AnimationProgress progress = ANIMATION_NORMALIZED_MAX / 2;
unobstructed_area_service_change(area.size.h, to_area.size.h, progress);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_change_calls, 1);
cl_assert_equal_i(s_data.last_change_progress, progress);
}
void test_unobstructed_area_service__change_after_subscribe(void) {
const GRect from_area = GRect(0, 0, DISP_COLS, 400);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
.change = prv_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.change, prv_change);
const GRect area = GRect(0, 0, DISP_COLS, 200);
const AnimationProgress progress = ANIMATION_NORMALIZED_MAX / 2;
unobstructed_area_service_change(area.size.h, to_area.size.h, progress);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_will_change_calls, 1);
cl_assert_equal_i(s_data.num_change_calls, 1);
cl_assert_equal_i(s_data.last_change_progress, progress);
}
void test_unobstructed_area_service__change_no_will(void) {
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
.change = prv_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.change, prv_change);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
const GRect area = GRect(0, 0, DISP_COLS, 200);
const AnimationProgress progress = ANIMATION_NORMALIZED_MAX / 2;
unobstructed_area_service_change(area.size.h, to_area.size.h, progress);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_will_change_calls, 1);
cl_assert_equal_i(s_data.num_change_calls, 1);
cl_assert_equal_i(s_data.last_change_progress, progress);
}
void test_unobstructed_area_service__did_change(void) {
UnobstructedAreaHandlers handlers = {
.did_change = prv_did_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.did_change, prv_did_change);
const GRect from_area = GRect(0, 0, DISP_COLS, 400);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
fake_event_service_handle_last();
unobstructed_area_service_did_change(to_area.size.h);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_did_change_calls, 1);
}
void test_unobstructed_area_service__did_change_after_subscribe(void) {
const GRect from_area = GRect(0, 0, DISP_COLS, 400);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_will_change(from_area.size.h, to_area.size.h);
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
.did_change = prv_did_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.did_change, prv_did_change);
unobstructed_area_service_did_change(to_area.size.h);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_will_change_calls, 1);
cl_assert_equal_i(s_data.num_did_change_calls, 1);
}
void test_unobstructed_area_service__did_change_no_will(void) {
UnobstructedAreaHandlers handlers = {
.will_change = prv_will_change,
.did_change = prv_did_change,
};
app_unobstructed_area_service_subscribe(handlers, s_data.context);
cl_assert(fake_event_service_get_info(PEBBLE_UNOBSTRUCTED_AREA_EVENT)->handler);
cl_assert_equal_p(app_state_get_unobstructed_area_state()->handlers.did_change, prv_did_change);
const GRect to_area = GRect(0, 0, DISP_COLS, 200);
unobstructed_area_service_did_change(to_area.size.h);
fake_event_service_handle_last();
cl_assert_equal_i(s_data.num_will_change_calls, 1);
cl_assert_equal_i(s_data.num_did_change_calls, 1);
}
void test_unobstructed_area_service__layer_no_clip(void) {
app_state_get_unobstructed_area_state()->area = GRect(0, 0, 400, 400);
Layer root_layer = {
.bounds = GRect(100, 100, 200, 200),
};
GRect unobstructed_bounds;
layer_get_unobstructed_bounds(&root_layer, &unobstructed_bounds);
cl_assert_equal_grect(unobstructed_bounds, root_layer.bounds);
}
void test_unobstructed_area_service__layer_clip_x_y(void) {
app_state_get_unobstructed_area_state()->area = GRect(0, 0, 400, 400);
Layer root_layer = {
.bounds = GRect(210, 220, 300, 300),
};
GRect unobstructed_bounds;
layer_get_unobstructed_bounds(&root_layer, &unobstructed_bounds);
cl_assert_equal_grect(unobstructed_bounds, GRect(210, 220, 190, 180));
}
void test_unobstructed_area_service__layer_clip_nx_ny(void) {
app_state_get_unobstructed_area_state()->area = GRect(0, 0, 400, 400);
Layer root_layer = {
.bounds = GRect(-110, -120, 300, 300),
};
GRect unobstructed_bounds;
layer_get_unobstructed_bounds(&root_layer, &unobstructed_bounds);
cl_assert_equal_grect(unobstructed_bounds, GRect(0, 0, 190, 180));
}
void test_unobstructed_area_service__nested_layer_no_clip(void) {
app_state_get_unobstructed_area_state()->area = GRect(0, 0, 400, 400);
Layer root_layer = {
.bounds = GRect(30, 30, 30, 30),
};
Layer layer = {
.bounds = GRect(20, 20, 20, 20),
};
layer_add_child(&root_layer, &layer);
GRect unobstructed_bounds;
layer_get_unobstructed_bounds(&layer, &unobstructed_bounds);
cl_assert_equal_grect(unobstructed_bounds, layer.bounds);
}
void test_unobstructed_area_service__nested_layer_clip_x_y(void) {
app_state_get_unobstructed_area_state()->area = GRect(0, 0, 400, 400);
Layer root_layer = {
.bounds = GRect(150, 120, 10, 10), // The size of the parent layer has no affect
};
Layer layer = {
.bounds = GRect(110, 130, 300, 200),
};
layer_add_child(&root_layer, &layer);
GRect unobstructed_bounds;
layer_get_unobstructed_bounds(&layer, &unobstructed_bounds);
cl_assert_equal_grect(unobstructed_bounds, GRect(110, 130, 140, 150));
}
void test_unobstructed_area_service__nested_layer_clip_nx_ny(void) {
app_state_get_unobstructed_area_state()->area = GRect(0, 0, 400, 400);
Layer root_layer = {
.bounds = GRect(-150, -120, 10, 10), // The size of the parent layer has no affect
};
Layer layer = {
.bounds = GRect(-110, -130, 300, 290),
};
layer_add_child(&root_layer, &layer);
GRect unobstructed_bounds;
layer_get_unobstructed_bounds(&layer, &unobstructed_bounds);
cl_assert_equal_grect(unobstructed_bounds, GRect(150, 120, 40, 40));
}

161
tests/fw/applib/wscript Normal file
View file

@ -0,0 +1,161 @@
from waftools.pebble_test import clar
def build(ctx):
clar(ctx,
sources_ant_glob=" src/fw/applib/app_message/app_message.c"
" src/fw/applib/app_message/app_message_outbox.c"
" src/fw/applib/app_message/app_message_inbox.c"
" src/fw/util/rand/rand.c"
" src/fw/vendor/tinymt32/tinymt32.c"
" src/fw/process_management/pebble_process_info.c"
" src/fw/util/dict.c",
test_sources_ant_glob="test_app_message.c")
clar(ctx,
sources_ant_glob=(" src/fw/applib/app_inbox.c"
" src/fw/services/normal/app_inbox_service.c"
),
test_sources_ant_glob="test_app_inbox.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(" src/fw/applib/app_outbox.c"
" src/fw/services/normal/app_outbox_service.c"
),
test_sources_ant_glob="test_app_outbox.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(" src/fw/applib/cpu_cache.c"
" src/libos/mcu/cache_arm.c"
),
test_sources_ant_glob="test_cpu_cache.c",
defines=["UNITTEST_WITH_SYSCALL_PRIVILEGES=1"],
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = (
" src/fw/flash_region/filesystem_regions.c"
" src/fw/flash_region/flash_region.c "
" src/fw/util/dict.c"
" src/fw/applib/persist.c"
" src/fw/util/rand/rand.c"
" src/fw/vendor/tinymt32/tinymt32.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/services/normal/legacy/persist_map.c"
" src/fw/services/normal/persist.c"
" src/fw/services/normal/settings/settings_file.c"
" src/fw/services/normal/settings/settings_raw_iter.c"
" src/fw/util/crc8.c"
" src/fw/util/legacy_checksum.c"
" tests/fakes/fake_rtc.c"
" tests/fakes/fake_spi_flash.c"
" tests/fixtures/resources/builtin_resources.auto.c"
),
test_sources_ant_glob = "test_persist.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = (
" src/fw/flash_region/flash_region.c"
" src/fw/applib/pbl_std/pbl_std.c"
" tests/fakes/fake_rtc.c"
" tests/fakes/fake_spi_flash.c"
),
test_sources_ant_glob = "test_pbl_std.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = (
" src/fw/applib/graphics/gbitmap.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/text_render.c"
" src/fw/applib/fonts/codepoint.c"
" tests/fakes/fake_gbitmap_png.c"
),
test_sources_ant_glob = "test_text_render.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = " src/fw/flash_region/flash_region.c" \
" src/fw/flash_region/filesystem_regions.c" \
" src/fw/system/hexdump.c" \
" src/fw/applib/fonts/codepoint.c" \
" src/fw/applib/fonts/fonts.c" \
" src/fw/applib/graphics/text_resources.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/flash_translation.c" \
" src/fw/services/normal/filesystem/pfs.c" \
" src/fw/services/normal/filesystem/app_file.c" \
" src/fw/util/crc8.c" \
" src/fw/util/legacy_checksum.c" \
" src/fw/drivers/flash/flash_crc.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",
test_sources_ant_glob = "test_text_resources.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = " src/fw/applib/ui/bitmap_layer.c" \
" src/fw/applib/ui/layer.c" \
" tests/fakes/fake_gbitmap_png.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gtypes.c",
test_sources_ant_glob = "test_bitmap_layer.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob = "src/fw/applib/app_smartstrap.c" \
" src/fw/services/normal/accessory/smartstrap_attribute.c" \
" src/fw/util/mbuf.c" \
" tests/fakes/fake_smartstrap_connection.c" \
" tests/fakes/fake_smartstrap_profiles.c" \
" tests/fakes/fake_smartstrap_state.c",
test_sources_ant_glob = "test_app_smartstrap.c",
defines=["CAPABILITY_HAS_ACCESSORY_CONNECTOR=1"],
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(
"src/fw/applib/graphics/gtypes.c "
"src/fw/applib/ui/layer.c "
"src/fw/applib/unobstructed_area_service.c "
"tests/fakes/fake_events.c "
),
test_sources_ant_glob="test_unobstructed_area_service.c",
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(
" src/fw/applib/app_glance.c"
" src/fw/applib/template_string.c"
" src/fw/services/normal/app_glances/app_glance_service.c"
" src/fw/services/normal/blob_db/app_glance_db.c"
" src/fw/services/normal/timeline/attribute.c"
" src/fw/util/crc8.c"
" src/fw/util/lru_cache.c"
" tests/fakes/fake_rtc.c"
" tests/fakes/fake_settings_file.c"
),
test_sources_ant_glob="test_app_glance.c",
defines=["CAPABILITY_HAS_APP_GLANCES=1"],
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob=(
"src/fw/applib/template_string.c "
),
test_sources_ant_glob="test_template_string.c",
override_includes=['dummy_board'])
# vim:filetype=python