mirror of
https://github.com/google/pebble.git
synced 2025-09-01 17:25:43 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
333
tests/fw/applib/bluetooth/test_ble_ad_parse.c
Normal file
333
tests/fw/applib/bluetooth/test_ble_ad_parse.c
Normal 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);
|
||||
}
|
132
tests/fw/applib/bluetooth/test_ble_ibeacon.c
Normal file
132
tests/fw/applib/bluetooth/test_ble_ibeacon.c
Normal 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);
|
||||
}
|
23
tests/fw/applib/bluetooth/wscript
Normal file
23
tests/fw/applib/bluetooth/wscript
Normal 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
|
319
tests/fw/applib/test_app_glance.c
Normal file
319
tests/fw/applib/test_app_glance.c
Normal 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);
|
||||
}
|
599
tests/fw/applib/test_app_inbox.c
Normal file
599
tests/fw/applib/test_app_inbox.c
Normal 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);
|
||||
}
|
802
tests/fw/applib/test_app_message.c
Normal file
802
tests/fw/applib/test_app_message.c
Normal 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);
|
||||
}
|
274
tests/fw/applib/test_app_outbox.c
Normal file
274
tests/fw/applib/test_app_outbox.c
Normal 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();
|
||||
}
|
||||
|
386
tests/fw/applib/test_app_smartstrap.c
Normal file
386
tests/fw/applib/test_app_smartstrap.c
Normal 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);
|
||||
}
|
96
tests/fw/applib/test_bitmap_layer.c
Normal file
96
tests/fw/applib/test_bitmap_layer.c
Normal 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));
|
||||
}
|
256
tests/fw/applib/test_cpu_cache.c
Normal file
256
tests/fw/applib/test_cpu_cache.c
Normal 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);
|
||||
}
|
185
tests/fw/applib/test_pbl_std.c
Normal file
185
tests/fw/applib/test_pbl_std.c
Normal 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);
|
||||
}
|
293
tests/fw/applib/test_persist.c
Normal file
293
tests/fw/applib/test_persist.c
Normal 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);
|
||||
}
|
753
tests/fw/applib/test_template_string.c
Normal file
753
tests/fw/applib/test_template_string.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
116
tests/fw/applib/test_text_render.c
Normal file
116
tests/fw/applib/test_text_render.c
Normal 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);
|
||||
}
|
328
tests/fw/applib/test_text_resources.c
Normal file
328
tests/fw/applib/test_text_resources.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
352
tests/fw/applib/test_unobstructed_area_service.c
Normal file
352
tests/fw/applib/test_unobstructed_area_service.c
Normal 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
161
tests/fw/applib/wscript
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue