Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e11911d
gitlint: enforce area format instead of conventional commits
Mearman Mar 9, 2026
ae6892b
tests/graphics: add macOS-specific fixture support
Mearman Mar 9, 2026
5646913
tests/fakes: fix HCI whitelist address handling
Mearman Mar 9, 2026
6616c55
tests: add Docker testing scripts for CI-matched environment
Mearman Mar 9, 2026
a1e42b5
tests: add documentation for cross-platform testing
Mearman Mar 9, 2026
4358b72
tests/fw/graphics/util: fix platform suffix for Linux CI
Mearman Mar 9, 2026
cad7617
fw/comm/ble: fix null check and improve error handling
Mearman Mar 9, 2026
36abc19
tests/stubs: update BLE stubs for driver layer testing
Mearman Mar 9, 2026
3a256f2
tests/fakes: add conditional compilation for BLE fakes
Mearman Mar 9, 2026
8208065
tests/wscript: enable BLE tests and fix build issues
Mearman Mar 9, 2026
08721a1
tests/fakes: fix duplicate function definition in fake_HCIAPI
Mearman Mar 10, 2026
aa0f2e5
tests/fw/comm: fix gap_le_connection_add argument count
Mearman Mar 10, 2026
955a9db
tests/fw/comm: add BLE driver stubs to wscript
Mearman Mar 10, 2026
e340a75
tools: use subprocess instead of sh for pip freeze
Mearman Mar 10, 2026
05698ef
tests: fix BLE subscription tests build errors
Mearman Mar 10, 2026
4da4fc1
tests: fix stub function signatures for BLE tests
Mearman Mar 10, 2026
811bba4
tests: include stubs_gap_le_advert.h in test_gap_le_advert.c
Mearman Mar 10, 2026
bef0bc6
tests: fix stub functions to be linkable
Mearman Mar 10, 2026
a81e584
tests: simplify bt_driver_advert_set_advertising_data stub
Mearman Mar 10, 2026
f5ee5f6
tests: add implementations for GATT driver stubs
Mearman Mar 10, 2026
53195d1
tests: add stub for prv_contains_service_changed_characteristic
Mearman Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitlint
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ ignore-merge-commits=false
ignore-fixup-commits=false
ignore-fixup-amend-commits=false
ignore-squash-commits=false
regex-style-search=true

[title-match-regex]
regex=[a-z0-9/]+: .*
# Format: "area: description" where area is either:
# - A path with at least one / (e.g., fw/drivers/hrm, third_party/nonfree)
# - One of the known short areas: ci, treewide, platform, sdk, tools, resources,
# docs, waftools, settings, libc, tests, notifications, build, wscript, fw,
# third_party, ancs, compositor, console, kernel, health
# Conventional commit types like feat:, fix:, chore: are NOT allowed.
regex=^([a-z0-9_]+/[a-z0-9_/]*|ci|treewide|platform|sdk|tools|resources|docs|waftools|settings|libc|tests|notifications|build|wscript|fw|third_party|ancs|compositor|console|kernel|health|gitlint|readme|requirements|python_libs|pbl-tool|pbl|moddable|libutil|iconography|gitignore|capabilities|asterix|activity|accel): .*

[title-max-length]
line-length=100
3 changes: 2 additions & 1 deletion src/fw/comm/ble/gatt_client_subscriptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ static BTErrno prv_subscribe(BLECharacteristic characteristic_ref,
.att_handle = att_handle,
};
// Prepend to the list of subscriptions of the connection:
ListNode *head = &connection->gatt_subscriptions->node;
ListNode *head = connection->gatt_subscriptions ?
&connection->gatt_subscriptions->node : NULL;
connection->gatt_subscriptions =
(GATTClientSubscriptionNode *) list_prepend(head, &subscription->node);

Expand Down
23 changes: 21 additions & 2 deletions src/fw/comm/ble/kernel_le_client/ppogatt/ppogatt.c
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,23 @@ static void prv_handle_meta_read(PPoGATTClient *client, const uint8_t *value,
size_t value_length, BLEGATTError error) {
PBL_ASSERTN(client->state == StateDisconnectedReadingMeta);
if (error != BLEGATTErrorSuccess) {
// GATT read failed - this is retriable since the mobile app may not be ready yet
goto handle_retriable_error;
// Check if this is a permanent error that should not be retried
// Permanent errors include: InvalidHandle, ReadNotPermitted, InvalidPDU,
// InsufficientAuthentication/Authorization/Encryption, AttributeNotFound, etc.
// These errors indicate that the operation cannot succeed through retrying.
// Timeout and resource errors are retriable since the remote might recover.
switch (error) {
case BLEGATTErrorSuccess:
break;
// Retriable errors - remote might recover:
case BLEGATTErrorPrepareQueueFull:
case BLEGATTErrorInsufficientResources:
case BLEGATTErrorRequestTimeOut:
goto handle_retriable_error;
// Permanent errors - delete client immediately:
default:
goto handle_error;
}
}
if (value_length < sizeof(PPoGATTMetaV0)) {
goto handle_error;
Expand Down Expand Up @@ -844,6 +859,10 @@ static void prv_handle_meta_read(PPoGATTClient *client, const uint8_t *value,
return;
}

// Subscribe failed - this is a permanent error, delete the client
PBL_LOG_ERR("Failed to subscribe to PPoGATT data characteristic: err=%x", e);
goto handle_error;

handle_retriable_error:
// GATT read failed - schedule a retry if we haven't exceeded the max retry count
if (++client->meta_read_retries < PPOGATT_META_READ_RETRY_COUNT_MAX) {
Expand Down
106 changes: 106 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Running Tests

## Cross-Platform Test Fixtures

Graphics test fixtures are platform-specific due to differences in:
- Font rendering libraries (FreeType, HarfBuzz)
- Standard library implementations
- ARM toolchain behavior

Test fixtures are named with the format: `test_name~platform-os.pbi`
- `~spalding-linux.pbi` - Generated on Linux (CI environment)
- `~spalding-darwin.pbi` - Generated on macOS (local development)

## Local Development

### macOS Developers

**Option 1: Use Docker (Recommended)**

Run tests in Docker to match the CI environment exactly:

```bash
# Run all tests
./tests/run-tests-docker.sh

# Run specific tests
./tests/run-tests-docker.sh -M "test_kickstart"

# Use specific board
TEST_BOARD=snowy_bb2 ./tests/run-tests-docker.sh
```

This ensures your test results match CI exactly.

**Option 2: Generate macOS Fixtures**

If you prefer to run tests natively on macOS:

```bash
# Configure and build
./waf configure --board=snowy_bb2
./waf test

# This will generate macOS-specific fixtures (~spalding-darwin.pbi)
# which will be used instead of the Linux fixtures
```

Note: macOS-generated fixtures will differ from Linux fixtures. This is expected
and doesn't indicate a problem with your changes. Use Docker to verify against CI.

### Linux Developers

Run tests normally - your environment matches CI:

```bash
./waf configure --board=snowy_bb2
./waf test
```

## Updating Fixtures

When you intentionally change rendering behavior:

1. **Run tests in Docker** to generate new Linux fixtures:
```bash
./tests/run-tests-docker.sh
```

2. **Copy the generated fixtures** from the failed test directory:
```bash
cp build/test/tests/failed/*-expected.pbi tests/fixtures/graphics/
```

3. **Update filenames** to include the `-linux` suffix if needed:
```bash
# Rename from ~spalding.pbi to ~spalding-linux.pbi
```

4. **Commit and push** the updated fixtures

## CI Environment

- Container: `ghcr.io/coredevices/pebbleos-docker:v3`
- OS: Ubuntu 24.04 (Linux)
- Board: snowy_bb2
- Compiler: arm-none-eabi-gcc 14.2.Rel1

## Troubleshooting

### Tests pass locally but fail on CI

Run tests in Docker to reproduce CI results:
```bash
./tests/run-tests-docker.sh
```

### Tests fail locally but pass on CI

Generate macOS-specific fixtures or use Docker for local development.

### Fixture naming confusion

The test framework automatically selects the correct fixture based on your OS:
- On Linux: Uses `~spalding-linux.pbi`
- On macOS: Uses `~spalding-darwin.pbi`
- Falls back to `~spalding.pbi` if OS-specific doesn't exist
30 changes: 30 additions & 0 deletions tests/fakes/fake_GAPAPI.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "fake_GAPAPI.h"

#ifdef GAPAPI_AVAILABLE

#include "bluetopia_interface.h"

#include <string.h>
Expand Down Expand Up @@ -348,3 +350,31 @@ void fake_GAPAPI_init(void) {
memset(&s_scan_resp_data, 0, sizeof(s_scan_resp_data));
s_scan_resp_data_length = 0;
}
#else
// When GAPAPI is not available, provide dummy implementations
static bool s_is_le_advertising_enabled = false;

void gap_le_set_advertising_disabled(void) {
s_is_le_advertising_enabled = false;
}

bool gap_le_is_advertising_enabled(void) {
return s_is_le_advertising_enabled;
}

void gap_le_assert_advertising_interval(uint16_t expected_min_slots, uint16_t expected_max_slots) {
// Dummy implementation - does nothing
}

unsigned int gap_le_get_advertising_data(Advertising_Data_t *ad_data_out) {
return 0;
}

unsigned int gap_le_get_scan_response_data(Scan_Response_Data_t *scan_resp_data_out) {
return 0;
}

void fake_GAPAPI_init(void) {
s_is_le_advertising_enabled = false;
}
#endif // GAPAPI_AVAILABLE
79 changes: 64 additions & 15 deletions tests/fakes/fake_GAPAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,92 @@

#pragma once

#include "GAPAPI.h"
#ifdef __has_include
#if __has_include("GAPAPI.h")
#include "GAPAPI.h"
#define GAPAPI_AVAILABLE
#endif
#else
#ifdef COMPONENT_BTSTACK
#include "GAPAPI.h"
#define GAPAPI_AVAILABLE
#endif
#endif

#include <bluetooth/bluetooth_types.h>

#include <stdbool.h>
#include <stdint.h>

//! Provided to simulate stopping advertising because of an inbound connection.
// If GAPAPI.h is not available, provide dummy type definitions
#ifndef GAPAPI_AVAILABLE
typedef struct GAPLEConnection GAPLEConnection; // Forward declaration (already defined elsewhere)
typedef struct GAP_LE_Event_Data_t GAP_LE_Event_Data_t;

// Advertising and scan response data are 31-byte arrays per Bluetooth spec
#define GAP_ADVERTISING_DATA_SIZE 31
typedef uint8_t Advertising_Data_t[GAP_ADVERTISING_DATA_SIZE];
typedef uint8_t Scan_Response_Data_t[GAP_ADVERTISING_DATA_SIZE];

// HCI error codes
#define HCI_ERROR_CODE_SUCCESS 0x00
#define HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST 0x16

// Boolean constants
#define TRUE true
#define FALSE false

// Bluetooth address types
typedef uint8_t BD_ADDR_t[6];
typedef uint8_t Encryption_Key_t[16];

// GAP API types
typedef struct {
uint8_t IO_Capability;
uint8_t OOB_Data_Flag;
uint8_t Authentication_Requirements;
uint8_t Max_Encryption_Key_Size;
uint8_t Link_Key_Request_Notification_Flag;
} GAP_LE_Pairing_Capabilities_t;

// GAP API function declarations
int GAP_LE_Advertising_Enable(unsigned int BluetoothStackID, unsigned int Enable,
Advertising_Data_t *Advertising_Data,
Scan_Response_Data_t *Scan_Response_Data,
void (*ConnectionCallback)(unsigned int, void *, unsigned long),
unsigned int CallbackParameter);

const GAP_LE_Pairing_Capabilities_t* gap_le_pairing_capabilities(void);

#endif // GAPAPI_AVAILABLE

// These functions are always available (either from GAPAPI or from the fake)
void gap_le_set_advertising_disabled(void);

bool gap_le_is_advertising_enabled(void);

void gap_le_assert_advertising_interval(uint16_t expected_min_slots, uint16_t expected_max_slots);

unsigned int gap_le_get_advertising_data(Advertising_Data_t *ad_data_out);
unsigned int gap_le_get_scan_response_data(Scan_Response_Data_t *scan_resp_data_out);
int GAP_LE_Set_Advertising_Data(unsigned int BluetoothStackID, unsigned int Length,
Advertising_Data_t *Advertising_Data);
int GAP_LE_Set_Scan_Response_Data(unsigned int BluetoothStackID, unsigned int Length,
Scan_Response_Data_t *Scan_Response_Data);
void fake_GAPAPI_init(void);

// Fake GAP API functions (available even when real GAPAPI is not)
void fake_gap_put_connection_event(uint8_t status, bool is_master, const BTDeviceInternal *device);

void fake_gap_put_disconnection_event(uint8_t status, uint8_t reason, bool is_master,
const BTDeviceInternal *device);

void fake_GAPAPI_put_encryption_change_event(bool encrypted, uint8_t status, bool is_master,
const BTDeviceInternal *device);

void fake_gap_le_put_cancel_create_event(const BTDeviceInternal *device, bool is_master);

void fake_GAPAPI_set_encrypted_for_device(const BTDeviceInternal *device);

const Encryption_Key_t *fake_GAPAPI_get_fake_irk(void);

const BD_ADDR_t *fake_GAPAPI_get_bd_addr_not_resolving_to_fake_irk(void);

const BTDeviceInternal *fake_GAPAPI_get_device_not_resolving_to_fake_irk(void);

const BD_ADDR_t *fake_GAPAPI_get_bd_addr_resolving_to_fake_irk(void);

const BTDeviceInternal *fake_GAPAPI_get_device_resolving_to_fake_irk(void);

void fake_GAPAPI_init(void);
#ifdef GAPAPI_AVAILABLE
// Additional functions when GAPAPI is available
// (none needed currently)
#endif
Loading
Loading