* [PATCH BlueZ v5 01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 18:23 ` Functional/integration testing bluez.test.bot
2026-05-13 16:17 ` [PATCH BlueZ v5 02/16] emulator: btvirt: allow specifying where server unix sockets are made Pauli Virtanen
` (15 subsequent siblings)
16 siblings, 1 reply; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Don't try to parse packet before whole header is received.
If received data has unknown packet type, reset buffer so that we don't
get stuck.
---
emulator/server.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/emulator/server.c b/emulator/server.c
index fa2bc07be..f14e14cd2 100644
--- a/emulator/server.c
+++ b/emulator/server.c
@@ -136,12 +136,20 @@ again:
client->pkt_len = 0;
break;
case HCI_ACLDATA_PKT:
+ if (count < HCI_ACL_HDR_SIZE + 1) {
+ client->pkt_offset += len;
+ return;
+ }
acl_hdr = (hci_acl_hdr*)(ptr + 1);
client->pkt_expect = HCI_ACL_HDR_SIZE + acl_hdr->dlen + 1;
client->pkt_data = malloc(client->pkt_expect);
client->pkt_len = 0;
break;
case HCI_ISODATA_PKT:
+ if (count < HCI_ISO_HDR_SIZE + 1) {
+ client->pkt_offset += len;
+ return;
+ }
iso_hdr = (hci_iso_hdr *)(ptr + 1);
client->pkt_expect = HCI_ISO_HDR_SIZE +
iso_hdr->dlen + 1;
@@ -151,6 +159,7 @@ again:
default:
printf("packet error, unknown type: %d\n",
client->pkt_type);
+ client->pkt_offset = 0;
return;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* RE: Functional/integration testing
2026-05-13 16:17 ` [PATCH BlueZ v5 01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed Pauli Virtanen
@ 2026-05-13 18:23 ` bluez.test.bot
0 siblings, 0 replies; 20+ messages in thread
From: bluez.test.bot @ 2026-05-13 18:23 UTC (permalink / raw)
To: linux-bluetooth, pav
[-- Attachment #1: Type: text/plain, Size: 4544 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1094320
---Test result---
Test Summary:
CheckPatch FAIL 6.03 seconds
GitLint FAIL 4.35 seconds
BuildEll PASS 20.01 seconds
BluezMake PASS 608.25 seconds
MakeCheck PASS 19.22 seconds
MakeDistcheck PASS 232.69 seconds
CheckValgrind PASS 272.79 seconds
CheckSmatch WARNING 322.12 seconds
bluezmakeextell PASS 165.37 seconds
IncrementalBuild PASS 658.64 seconds
ScanBuild PASS 916.62 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[BlueZ,v5,12/16] test: add functional/integration testing framework
ERROR:EXECUTE_PERMISSIONS: do not set execute permissions for source files
#422: FILE: test/test-functional
ERROR:EXECUTE_PERMISSIONS: do not set execute permissions for source files
#449: FILE: test/test-functional-attach
/github/workspace/src/patch/14571682.patch total: 2 errors, 0 warnings, 279 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14571682.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v5,01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed
WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
1: T1 Title exceeds max length (82>80): "[BlueZ,v5,01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed"
[BlueZ,v5,02/16] emulator: btvirt: allow specifying where server unix sockets are made
WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
1: T1 Title exceeds max length (86>80): "[BlueZ,v5,02/16] emulator: btvirt: allow specifying where server unix sockets are made"
[BlueZ,v5,08/16] test-runner: use virtio-serial for implementing -u device forwarding
WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
1: T1 Title exceeds max length (85>80): "[BlueZ,v5,08/16] test-runner: use virtio-serial for implementing -u device forwarding"
[BlueZ,v5,10/16] doc: enable KVM paravirtualization & clock support in tester kernel config
WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
1: T1 Title exceeds max length (91>80): "[BlueZ,v5,10/16] doc: enable KVM paravirtualization & clock support in tester kernel config"
##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
emulator/btdev.c:478:29: warning: Variable length array is used.
https://github.com/bluez/bluez/pull/2123
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH BlueZ v5 02/16] emulator: btvirt: allow specifying where server unix sockets are made
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 03/16] emulator: btvirt: support SCO data packets Pauli Virtanen
` (14 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Make --server to take optional path name where to create the various
server sockets.
---
emulator/main.c | 37 ++++++++++++++++++++++++-------------
1 file changed, 24 insertions(+), 13 deletions(-)
diff --git a/emulator/main.c b/emulator/main.c
index 456fcd98e..09d6e9adb 100644
--- a/emulator/main.c
+++ b/emulator/main.c
@@ -18,6 +18,7 @@
#include <stdbool.h>
#include <getopt.h>
#include <sys/uio.h>
+#include <limits.h>
#include "src/shared/mainloop.h"
#include "src/shared/util.h"
@@ -46,7 +47,7 @@ static void usage(void)
printf("options:\n"
"\t-d Enable debug\n"
"\t-S Create local serial port\n"
- "\t-s Create local server sockets\n"
+ "\t-s[path=/tmp] Create local server sockets\n"
"\t-t[port=45550] Create a TCP server\n"
"\t-l[num] Number of local controllers\n"
"\t-L Create LE only controller\n"
@@ -60,7 +61,7 @@ static void usage(void)
static const struct option main_options[] = {
{ "debug", no_argument, NULL, 'd' },
{ "serial", no_argument, NULL, 'S' },
- { "server", no_argument, NULL, 's' },
+ { "server", optional_argument, NULL, 's' },
{ "tcp", optional_argument, NULL, 't' },
{ "local", optional_argument, NULL, 'l' },
{ "le", no_argument, NULL, 'L' },
@@ -88,6 +89,7 @@ int main(int argc, char *argv[])
struct server *server5;
bool debug_enabled = false;
bool server_enabled = false;
+ const char *server_path = "/tmp";
uint16_t tcp_port = 0;
bool serial_enabled = false;
int letest_count = 0;
@@ -100,7 +102,7 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "dSst::l::LBAU::T::vh",
+ opt = getopt_long(argc, argv, "dSs::t::l::LBAU::T::vh",
main_options, NULL);
if (opt < 0)
break;
@@ -114,6 +116,8 @@ int main(int argc, char *argv[])
break;
case 's':
server_enabled = true;
+ if (optarg)
+ server_path = optarg;
break;
case 't':
if (optarg)
@@ -196,28 +200,35 @@ int main(int argc, char *argv[])
}
if (server_enabled) {
- server1 = server_open_unix(SERVER_TYPE_BREDRLE,
- "/tmp/bt-server-bredrle");
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "%s/%s", server_path,
+ "bt-server-bredrle");
+ server1 = server_open_unix(SERVER_TYPE_BREDRLE, path);
if (!server1)
fprintf(stderr, "Failed to open BR/EDR/LE server\n");
- server2 = server_open_unix(SERVER_TYPE_BREDR,
- "/tmp/bt-server-bredr");
+ snprintf(path, sizeof(path), "%s/%s", server_path,
+ "bt-server-bredr");
+ server2 = server_open_unix(SERVER_TYPE_BREDR, path);
if (!server2)
fprintf(stderr, "Failed to open BR/EDR server\n");
- server3 = server_open_unix(SERVER_TYPE_AMP,
- "/tmp/bt-server-amp");
+ snprintf(path, sizeof(path), "%s/%s", server_path,
+ "bt-server-amp");
+ server3 = server_open_unix(SERVER_TYPE_AMP, path);
if (!server3)
fprintf(stderr, "Failed to open AMP server\n");
- server4 = server_open_unix(SERVER_TYPE_LE,
- "/tmp/bt-server-le");
+ snprintf(path, sizeof(path), "%s/%s", server_path,
+ "bt-server-le");
+ server4 = server_open_unix(SERVER_TYPE_LE, path);
if (!server4)
fprintf(stderr, "Failed to open LE server\n");
- server5 = server_open_unix(SERVER_TYPE_MONITOR,
- "/tmp/bt-server-mon");
+ snprintf(path, sizeof(path), "%s/%s", server_path,
+ "bt-server-mon");
+ server5 = server_open_unix(SERVER_TYPE_MONITOR, path);
if (!server5)
fprintf(stderr, "Failed to open monitor server\n");
}
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 03/16] emulator: btvirt: support SCO data packets
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 02/16] emulator: btvirt: allow specifying where server unix sockets are made Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 04/16] emulator: btdev: clear more state on Reset Pauli Virtanen
` (13 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Support also SCO data packets in btvirt.
---
emulator/server.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/emulator/server.c b/emulator/server.c
index f14e14cd2..7790867b7 100644
--- a/emulator/server.c
+++ b/emulator/server.c
@@ -119,6 +119,7 @@ again:
hci_command_hdr *cmd_hdr;
hci_acl_hdr *acl_hdr;
hci_iso_hdr *iso_hdr;
+ hci_sco_hdr *sco_hdr;
if (!client->pkt_data) {
client->pkt_type = ptr[0];
@@ -156,6 +157,17 @@ again:
client->pkt_data = malloc(client->pkt_expect);
client->pkt_len = 0;
break;
+ case HCI_SCODATA_PKT:
+ if (count < HCI_SCO_HDR_SIZE + 1) {
+ client->pkt_offset += len;
+ return;
+ }
+ sco_hdr = (hci_sco_hdr *)(ptr + 1);
+ client->pkt_expect = HCI_SCO_HDR_SIZE +
+ sco_hdr->dlen + 1;
+ client->pkt_data = malloc(client->pkt_expect);
+ client->pkt_len = 0;
+ break;
default:
printf("packet error, unknown type: %d\n",
client->pkt_type);
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 04/16] emulator: btdev: clear more state on Reset
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (2 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 03/16] emulator: btvirt: support SCO data packets Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 05/16] test-runner: enable path argument for --unix Pauli Virtanen
` (12 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
On controller Reset command, initialize most fields in struct btdev to
zero, similarly to the state just after btdev_create().
This excludes some fields like command bitmasks, which hciemu may have
adjusted.
To make this easier, add struct_group() macro similar to what kernel
uses.
---
emulator/btdev.c | 117 ++++++++++++++++++++++++++++-------------------
1 file changed, 70 insertions(+), 47 deletions(-)
diff --git a/emulator/btdev.c b/emulator/btdev.c
index 3a295b679..ad2e025d1 100644
--- a/emulator/btdev.c
+++ b/emulator/btdev.c
@@ -50,6 +50,12 @@
#define has_bredr(btdev) (!((btdev)->features[4] & 0x20))
#define has_le(btdev) (!!((btdev)->features[4] & 0x40))
+#define struct_group(NAME, MEMBERS...) \
+ union { \
+ struct { MEMBERS }; \
+ struct { MEMBERS } NAME; \
+ }
+
#define ACL_HANDLE BIT(0)
#define SCO_HANDLE BIT(8)
#define CIS_HANDLE SCO_HANDLE
@@ -149,15 +155,6 @@ struct btdev {
struct queue *conns;
- bool auth_init;
- uint8_t link_key[16];
- uint16_t pin[16];
- uint8_t pin_len;
- uint8_t io_cap;
- uint8_t auth_req;
- bool ssp_auth_complete;
- uint8_t ssp_status;
-
btdev_command_func command_handler;
void *command_data;
@@ -196,6 +193,18 @@ struct btdev {
const struct btdev_cmd *emu_cmds;
bool aosp_capable;
+ /* State zeroed on reset */
+ struct_group(reset_group,
+
+ bool auth_init;
+ uint8_t link_key[16];
+ uint16_t pin[16];
+ uint8_t pin_len;
+ uint8_t io_cap;
+ uint8_t auth_req;
+ bool ssp_auth_complete;
+ uint8_t ssp_status;
+
uint16_t default_link_policy;
uint8_t event_mask[8];
uint8_t event_mask_page2[8];
@@ -249,25 +258,26 @@ struct btdev {
struct le_cig le_cig[CIG_SIZE];
uint8_t le_iso_path[2];
- /* Real time length of AL array */
- uint8_t le_al_len;
- /* Real time length of RL array */
- uint8_t le_rl_len;
- struct btdev_al le_al[AL_SIZE];
- struct btdev_rl le_rl[RL_SIZE];
uint8_t le_rl_enable;
- uint16_t le_rl_timeout;
struct pending_conn pending_conn[MAX_PENDING_CONN];
- uint8_t le_local_sk256[32];
-
uint16_t sync_train_interval;
uint32_t sync_train_timeout;
uint8_t sync_train_service_data;
uint16_t le_ext_adv_type;
+ ); /* reset_group */
+
+ /* Real time length of AL array */
+ uint8_t le_al_len;
+ /* Real time length of RL array */
+ uint8_t le_rl_len;
+ struct btdev_al le_al[AL_SIZE];
+ struct btdev_rl le_rl[RL_SIZE];
+ uint16_t le_rl_timeout;
+
struct queue *le_ext_adv;
struct queue *le_per_adv;
struct queue *le_big;
@@ -617,15 +627,52 @@ static void le_big_free(void *data)
free(big);
}
+static void btdev_init_param(struct btdev *btdev)
+{
+ unsigned int i;
+
+ btdev->page_scan_interval = 0x0800;
+ btdev->page_scan_window = 0x0012;
+ btdev->page_scan_type = 0x00;
+
+ btdev->sync_train_interval = 0x0080;
+ btdev->sync_train_timeout = 0x0002ee00;
+ btdev->sync_train_service_data = 0x00;
+
+ btdev->acl_mtu = 192;
+ btdev->acl_max_pkt = 1;
+
+ btdev->sco_mtu = 72;
+ btdev->sco_max_pkt = 1;
+
+ btdev->iso_mtu = 251;
+ btdev->iso_max_pkt = 1;
+
+ for (i = 0; i < ARRAY_SIZE(btdev->le_cig); ++i)
+ btdev->le_cig[i].params.cig_id = 0xff;
+
+ btdev->country_code = 0x00;
+}
+
static void btdev_reset(struct btdev *btdev)
{
/* FIXME: include here clearing of all states that should be
* cleared upon HCI_Reset
*/
- btdev->le_scan_enable = 0x00;
- btdev->le_adv_enable = 0x00;
- btdev->le_pa_enable = 0x00;
+ if (btdev->inquiry_id > 0) {
+ timeout_remove(btdev->inquiry_id);
+ btdev->inquiry_id = 0;
+ }
+
+ queue_remove_all(btdev->conns, NULL, NULL, conn_remove);
+ queue_remove_all(btdev->le_ext_adv, NULL, NULL, le_ext_adv_free);
+ queue_remove_all(btdev->le_per_adv, NULL, NULL, free);
+ queue_remove_all(btdev->le_big, NULL, NULL, le_big_free);
+
+ memset(&btdev->reset_group, 0, sizeof(btdev->reset_group));
+
+ btdev_init_param(btdev);
al_clear(btdev);
rl_clear(btdev);
@@ -633,10 +680,7 @@ static void btdev_reset(struct btdev *btdev)
btdev->le_al_len = AL_SIZE;
btdev->le_rl_len = RL_SIZE;
- queue_remove_all(btdev->conns, NULL, NULL, conn_remove);
- queue_remove_all(btdev->le_ext_adv, NULL, NULL, le_ext_adv_free);
- queue_remove_all(btdev->le_per_adv, NULL, NULL, free);
- queue_remove_all(btdev->le_big, NULL, NULL, le_big_free);
+ btdev->le_rl_timeout = 0x0384;
}
static int cmd_reset(struct btdev *dev, const void *data, uint8_t len)
@@ -8130,7 +8174,6 @@ struct btdev *btdev_create(enum btdev_type type, uint16_t id)
{
struct btdev *btdev;
int index;
- unsigned int i;
btdev = malloc(sizeof(*btdev));
if (!btdev)
@@ -8195,27 +8238,7 @@ struct btdev *btdev_create(enum btdev_type type, uint16_t id)
break;
}
- btdev->page_scan_interval = 0x0800;
- btdev->page_scan_window = 0x0012;
- btdev->page_scan_type = 0x00;
-
- btdev->sync_train_interval = 0x0080;
- btdev->sync_train_timeout = 0x0002ee00;
- btdev->sync_train_service_data = 0x00;
-
- btdev->acl_mtu = 192;
- btdev->acl_max_pkt = 1;
-
- btdev->sco_mtu = 72;
- btdev->sco_max_pkt = 1;
-
- btdev->iso_mtu = 251;
- btdev->iso_max_pkt = 1;
-
- for (i = 0; i < ARRAY_SIZE(btdev->le_cig); ++i)
- btdev->le_cig[i].params.cig_id = 0xff;
-
- btdev->country_code = 0x00;
+ btdev_init_param(btdev);
index = add_btdev(btdev);
if (index < 0) {
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 05/16] test-runner: enable path argument for --unix
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (3 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 04/16] emulator: btdev: clear more state on Reset Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 06/16] test-runner: Add -o/--option option Pauli Virtanen
` (11 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Allow specifying the path for the controller socket to be used.
---
tools/test-runner.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/tools/test-runner.c b/tools/test-runner.c
index 48b7c1589..331cb6eb1 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -54,6 +54,7 @@ static bool start_monitor = false;
static bool qemu_host_cpu = false;
static int num_devs = 0;
static int num_emulator = 0;
+static const char *device_path = "/tmp/bt-server-bredr";
static const char *qemu_binary = NULL;
static const char *kernel_image = NULL;
static char *audio_server;
@@ -313,11 +314,10 @@ static void start_qemu(void)
argv[pos++] = (char *) cmdline;
for (i = 0; i < num_devs; i++) {
- const char *path = "/tmp/bt-server-bredr";
char *chrdev, *serdev;
- chrdev = alloca(48 + strlen(path));
- sprintf(chrdev, "socket,path=%s,id=bt%d", path, i);
+ chrdev = alloca(48 + strlen(device_path));
+ sprintf(chrdev, "socket,path=%s,id=bt%d", device_path, i);
serdev = alloca(48);
sprintf(serdev, "pci-serial,chardev=bt%d", i);
@@ -1198,7 +1198,7 @@ static void usage(void)
"\t-m, --monitor Start btmon\n"
"\t-l, --emulator[=num] Start btvirt\n"
"\t-A, --audio[=path] Start audio server\n"
- "\t-u, --unix [path] Provide serial device\n"
+ "\t-u, --unix[=path] Provide serial device\n"
"\t-U, --usb [qemu_args] Provide USB device\n"
"\t-q, --qemu <path> QEMU binary\n"
"\t-H, --qemu-host-cpu Use host CPU (requires KVM support)\n"
@@ -1211,7 +1211,7 @@ static const struct option main_options[] = {
{ "auto", no_argument, NULL, 'a' },
{ "dbus", no_argument, NULL, 'b' },
{ "dbus-session", no_argument, NULL, 's' },
- { "unix", no_argument, NULL, 'u' },
+ { "unix", optional_argument, NULL, 'u' },
{ "daemon", no_argument, NULL, 'd' },
{ "emulator", no_argument, NULL, 'l' },
{ "monitor", no_argument, NULL, 'm' },
@@ -1239,7 +1239,7 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "aubdsl::mq:Hk:A::U:vh",
+ opt = getopt_long(argc, argv, "au::bdsl::mq:Hk:A::U:vh",
main_options, NULL);
if (opt < 0)
break;
@@ -1250,6 +1250,8 @@ int main(int argc, char *argv[])
break;
case 'u':
num_devs = 1;
+ if (optarg)
+ device_path = optarg;
break;
case 'b':
start_dbus = true;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 06/16] test-runner: Add -o/--option option
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (4 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 05/16] test-runner: enable path argument for --unix Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 07/16] test-runner: allow source tree root for -k Pauli Virtanen
` (10 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Allow passing arbitrary arguments to QEMU.
---
tools/test-runner.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/tools/test-runner.c b/tools/test-runner.c
index 331cb6eb1..3de3a9d74 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -41,6 +41,7 @@
#endif
#define CMDLINE_MAX (2048 * 10)
+#define EXTRA_OPT_MAX 64
static const char *own_binary;
static char **test_argv;
@@ -59,6 +60,8 @@ static const char *qemu_binary = NULL;
static const char *kernel_image = NULL;
static char *audio_server;
static char *usb_dev;
+static char *extra_opts[EXTRA_OPT_MAX];
+static int num_extra_opts;
static const char *qemu_table[] = {
"qemu-system-x86_64",
@@ -291,7 +294,8 @@ static void start_qemu(void)
argv = alloca(sizeof(qemu_argv) +
(sizeof(char *) * (6 + (num_devs * 4))) +
- (sizeof(char *) * (usb_dev ? 4 : 0)));
+ (sizeof(char *) * (usb_dev ? 4 : 0)) +
+ (sizeof(char *) * num_extra_opts));
memcpy(argv, qemu_argv, sizeof(qemu_argv));
pos = (sizeof(qemu_argv) / sizeof(char *)) - 1;
@@ -335,6 +339,9 @@ static void start_qemu(void)
argv[pos++] = usb_dev;
}
+ for (i = 0; i < num_extra_opts; ++i)
+ argv[pos++] = extra_opts[i];
+
argv[pos] = NULL;
execve(argv[0], argv, qemu_envp);
@@ -1199,10 +1206,11 @@ static void usage(void)
"\t-l, --emulator[=num] Start btvirt\n"
"\t-A, --audio[=path] Start audio server\n"
"\t-u, --unix[=path] Provide serial device\n"
- "\t-U, --usb [qemu_args] Provide USB device\n"
+ "\t-U, --usb <qemu_args> Provide USB device\n"
"\t-q, --qemu <path> QEMU binary\n"
"\t-H, --qemu-host-cpu Use host CPU (requires KVM support)\n"
"\t-k, --kernel <image> Kernel image (bzImage)\n"
+ "\t-o, --option <opt> Additional argument passed to QEMU\n"
"\t-h, --help Show help options\n");
}
@@ -1220,6 +1228,7 @@ static const struct option main_options[] = {
{ "kernel", required_argument, NULL, 'k' },
{ "audio", optional_argument, NULL, 'A' },
{ "usb", required_argument, NULL, 'U' },
+ { "option", required_argument, NULL, 'o' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ }
@@ -1239,7 +1248,7 @@ int main(int argc, char *argv[])
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "au::bdsl::mq:Hk:A::U:vh",
+ opt = getopt_long(argc, argv, "au::bdsl::mq:Hk:A::U:o:vh",
main_options, NULL);
if (opt < 0)
break;
@@ -1284,6 +1293,13 @@ int main(int argc, char *argv[])
case 'U':
usb_dev = optarg;
break;
+ case 'o':
+ if (num_extra_opts >= EXTRA_OPT_MAX) {
+ fprintf(stderr, "Too many -o\n");
+ return EXIT_FAILURE;
+ }
+ extra_opts[num_extra_opts++] = optarg;
+ break;
case 'v':
printf("%s\n", VERSION);
return EXIT_SUCCESS;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 07/16] test-runner: allow source tree root for -k
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (5 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 06/16] test-runner: Add -o/--option option Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 08/16] test-runner: use virtio-serial for implementing -u device forwarding Pauli Virtanen
` (9 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Allow passing source tree root for -k option, look up kernel below it.
---
tools/test-runner.c | 42 ++++++++++++++++++++++++++++--------------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/tools/test-runner.c b/tools/test-runner.c
index 3de3a9d74..b3e0b0cfe 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -93,18 +93,31 @@ static const char *kernel_table[] = {
NULL
};
-static const char *find_kernel(void)
+static bool find_kernel(const char *root, char path[PATH_MAX])
{
+ struct stat st;
int i;
- for (i = 0; kernel_table[i]; i++) {
- struct stat st;
-
- if (!stat(kernel_table[i], &st))
- return kernel_table[i];
+ if (root) {
+ snprintf(path, PATH_MAX, "%s", root);
+ if (stat(path, &st))
+ return false;
+ if (!(st.st_mode & S_IFDIR))
+ return true;
}
- return NULL;
+ for (i = 0; kernel_table[i]; i++) {
+ if (root)
+ snprintf(path, PATH_MAX, "%s/%s", root,
+ kernel_table[i]);
+ else
+ snprintf(path, PATH_MAX, "%s",
+ kernel_table[i]);
+ if (!stat(path, &st))
+ return true;
+ }
+
+ return false;
}
static const struct {
@@ -1209,7 +1222,7 @@ static void usage(void)
"\t-U, --usb <qemu_args> Provide USB device\n"
"\t-q, --qemu <path> QEMU binary\n"
"\t-H, --qemu-host-cpu Use host CPU (requires KVM support)\n"
- "\t-k, --kernel <image> Kernel image (bzImage)\n"
+ "\t-k, --kernel <image> Kernel bzImage or source tree path\n"
"\t-o, --option <opt> Additional argument passed to QEMU\n"
"\t-h, --help Show help options\n");
}
@@ -1236,6 +1249,8 @@ static const struct option main_options[] = {
int main(int argc, char *argv[])
{
+ char kernel_path[PATH_MAX];
+
if (getpid() == 1 && getppid() == 0) {
prepare_sandbox();
run_tests();
@@ -1335,14 +1350,13 @@ int main(int argc, char *argv[])
}
}
- if (!kernel_image) {
- kernel_image = find_kernel();
- if (!kernel_image) {
- fprintf(stderr, "No default kernel image found\n");
- return EXIT_FAILURE;
- }
+ if (!find_kernel(kernel_image, kernel_path)) {
+ fprintf(stderr, "No kernel image found\n");
+ return EXIT_FAILURE;
}
+ kernel_image = kernel_path;
+
printf("Using QEMU binary %s\n", qemu_binary);
printf("Using kernel image %s\n", kernel_image);
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 08/16] test-runner: use virtio-serial for implementing -u device forwarding
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (6 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 07/16] test-runner: allow source tree root for -k Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 09/16] doc: enable CONFIG_VIRTIO_CONSOLE in tester config Pauli Virtanen
` (8 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Using pci-serial to forward eg. btvirt sockets is unreliable, as qemu or
kernel seems to be sometimes dropping part of the sent data or insert
spurious \0 bytes, leading to sporadic errors like:
kernel: Bluetooth: hci0: command 0x0c52 tx timeout
kernel: Bluetooth: hci0: Opcode 0x0c52 failed: -110
btvirt: packet error, unknown type: 0
This appears to occur most often when host system is under load, e.g.
due to multiple test-runners running at the same time. The problem is
not specific to btvirt, but seems to be in the qemu serial device layer
vs. kernel interaction.
Change test-runner to use virtserialport to forward the btvirt
connection inside the VM, as virtio-serial doesn't appear to have these
problems.
---
tools/test-runner.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/tools/test-runner.c b/tools/test-runner.c
index b3e0b0cfe..0e3bfb8b7 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -306,7 +306,7 @@ static void start_qemu(void)
testargs);
argv = alloca(sizeof(qemu_argv) +
- (sizeof(char *) * (6 + (num_devs * 4))) +
+ (sizeof(char *) * (8 + (num_devs * 4))) +
(sizeof(char *) * (usb_dev ? 4 : 0)) +
(sizeof(char *) * num_extra_opts));
memcpy(argv, qemu_argv, sizeof(qemu_argv));
@@ -330,14 +330,19 @@ static void start_qemu(void)
argv[pos++] = "-append";
argv[pos++] = (char *) cmdline;
+ if (num_devs) {
+ argv[pos++] = "-device";
+ argv[pos++] = "virtio-serial";
+ }
+
for (i = 0; i < num_devs; i++) {
char *chrdev, *serdev;
chrdev = alloca(48 + strlen(device_path));
sprintf(chrdev, "socket,path=%s,id=bt%d", device_path, i);
- serdev = alloca(48);
- sprintf(serdev, "pci-serial,chardev=bt%d", i);
+ serdev = alloca(64);
+ sprintf(serdev, "virtconsole,chardev=bt%d,name=bt.%d", i, i);
argv[pos++] = "-chardev";
argv[pos++] = chrdev;
@@ -910,7 +915,7 @@ static void run_command(char *cmdname, char *home)
}
if (num_devs) {
- const char *node = "/dev/ttyS1";
+ const char *node = "/dev/hvc0";
unsigned int basic_flags, extra_flags;
printf("Attaching BR/EDR controller to %s\n", node);
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 09/16] doc: enable CONFIG_VIRTIO_CONSOLE in tester config
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (7 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 08/16] test-runner: use virtio-serial for implementing -u device forwarding Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 10/16] doc: enable KVM paravirtualization & clock support in tester kernel config Pauli Virtanen
` (7 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Enable kernel option that allows using -device virtserialport in qemu.
This is easier to make work reliably than pci-serial channel.
---
doc/ci.config | 1 +
doc/test-runner.rst | 1 +
doc/tester.config | 1 +
3 files changed, 3 insertions(+)
diff --git a/doc/ci.config b/doc/ci.config
index 31e49ba96..a48c1af9d 100644
--- a/doc/ci.config
+++ b/doc/ci.config
@@ -6,6 +6,7 @@
CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_CONSOLE=y
CONFIG_NET=y
CONFIG_INET=y
diff --git a/doc/test-runner.rst b/doc/test-runner.rst
index 64715e2e7..d030787a4 100644
--- a/doc/test-runner.rst
+++ b/doc/test-runner.rst
@@ -45,6 +45,7 @@ option (like the Bluetooth subsystem) can be enabled on top of this.
CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
+ CONFIG_VIRTIO_CONSOLE=y
CONFIG_NET=y
CONFIG_INET=y
diff --git a/doc/tester.config b/doc/tester.config
index 4ee306405..015e7cc1a 100644
--- a/doc/tester.config
+++ b/doc/tester.config
@@ -1,6 +1,7 @@
CONFIG_PCI=y
CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_CONSOLE=y
CONFIG_NET=y
CONFIG_INET=y
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 10/16] doc: enable KVM paravirtualization & clock support in tester kernel config
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (8 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 09/16] doc: enable CONFIG_VIRTIO_CONSOLE in tester config Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 11/16] doc: add functional/integration testing documentation Pauli Virtanen
` (6 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Enable KVM guest and PTP options in tester kernel config.
This allows synchronizing tester VM guest with host clock, needed for
testers that want to compare timestamps outside the VM guest.
---
doc/ci.config | 8 ++++++++
doc/test-runner.rst | 16 ++++++++++++++++
doc/tester.config | 8 ++++++++
3 files changed, 32 insertions(+)
diff --git a/doc/ci.config b/doc/ci.config
index a48c1af9d..bb3cb221f 100644
--- a/doc/ci.config
+++ b/doc/ci.config
@@ -8,6 +8,14 @@ CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_CONSOLE=y
+CONFIG_HYPERVISOR_GUEST=y
+CONFIG_PARAVIRT=y
+CONFIG_KVM_GUEST=y
+
+CONFIG_PTP_1588_CLOCK=y
+CONFIG_PTP_1588_CLOCK_KVM=y
+CONFIG_PTP_1588_CLOCK_VMCLOCK=y
+
CONFIG_NET=y
CONFIG_INET=y
diff --git a/doc/test-runner.rst b/doc/test-runner.rst
index d030787a4..60f18683c 100644
--- a/doc/test-runner.rst
+++ b/doc/test-runner.rst
@@ -122,6 +122,22 @@ options may be useful:
CONFIG_DEBUG_MUTEXES=y
CONFIG_KASAN=y
+Other
+-----
+
+For tests requiring accurate time inside the VM, possible with KVM:
+
+.. code-block::
+
+ CONFIG_HYPERVISOR_GUEST=y
+ CONFIG_PARAVIRT=y
+ CONFIG_KVM_GUEST=y
+
+ CONFIG_PTP_1588_CLOCK=y
+ CONFIG_PTP_1588_CLOCK_KVM=y
+ CONFIG_PTP_1588_CLOCK_VMCLOCK=y
+
+
EXAMPLES
========
diff --git a/doc/tester.config b/doc/tester.config
index 015e7cc1a..0cf5e2723 100644
--- a/doc/tester.config
+++ b/doc/tester.config
@@ -3,6 +3,14 @@ CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_CONSOLE=y
+CONFIG_HYPERVISOR_GUEST=y
+CONFIG_PARAVIRT=y
+CONFIG_KVM_GUEST=y
+
+CONFIG_PTP_1588_CLOCK=y
+CONFIG_PTP_1588_CLOCK_KVM=y
+CONFIG_PTP_1588_CLOCK_VMCLOCK=y
+
CONFIG_NET=y
CONFIG_INET=y
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 11/16] doc: add functional/integration testing documentation
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (9 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 10/16] doc: enable KVM paravirtualization & clock support in tester kernel config Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 12/16] test: add functional/integration testing framework Pauli Virtanen
` (5 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add documentation for functional/integration test suite.
---
doc/test-functional.rst | 299 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 299 insertions(+)
create mode 100644 doc/test-functional.rst
diff --git a/doc/test-functional.rst b/doc/test-functional.rst
new file mode 100644
index 000000000..bda7366e0
--- /dev/null
+++ b/doc/test-functional.rst
@@ -0,0 +1,299 @@
+===============
+test-functional
+===============
+
+**test-functional** [*OPTIONS*]
+
+DESCRIPTION
+===========
+
+**test-functional(1)** is used for functional testing of BlueZ and
+kernel using multiple virtual machine environments, connected by real
+or virtual controllers.
+
+It uses https://pypi.org/project/pytest-bluezenv as VM-based test
+framework. For details, see its documentation.
+
+QUICK EXAMPLE
+=============
+
+Install `qemu-system-x86_64` first. Then,
+
+.. code-block::
+
+ $ ./configure --enable-functional-testing --enable-testing --enable-tools
+ $ make -j8
+ $ python3 -mpip install -r test/functional/requirements.txt
+ $ test/test-functional --kernel-build -v
+
+OPTIONS
+=======
+
+The `test-functional` script simply runs `Pytest
+<https://pytest.org>`__ which can take the following options:
+https://docs.pytest.org/en/stable/reference/reference.html#command-line-flags
+
+The following additional options apply:
+
+:--kernel=<image>: Kernel image (or built Linux source tree root) to
+ use. See **test-runner(1)** and `tester.config` for required
+ kernel config.
+
+ If not provided, value from `FUNCTIONAL_TESTING_KERNEL`
+ environment variable is used. If none, no image is used.
+
+:--usb=hci0,hci1: USB controllers to use in tests that require use of
+ real controllers.
+
+ If not provided, value from `FUNCTIONAL_TESTING_CONTROLLERS`
+ environment variable is used. If none, all USB controllers
+ with suitable permissions are considered.
+
+:--btmon: Launch btmon on all hosts to log events, and dump traffic to
+ test-functional-\*.btsnoop
+
+:--force-usb: Force tests to use USB controllers instead of `btvirt`.
+
+:--vm-timeout=<seconds>: Specify timeout for communication with VM hosts.
+
+:--log-filter=[+-]<pattern>,[+-]<pattern>,...: Allow/deny lists
+ for filtering logging output. The pattern is a shell glob matching
+ to the logger names.
+
+:--build-dir=<path>: Path to build directory where to search for BlueZ
+ executables.
+
+:--list: Output brief lists of existing tests.
+
+:--kernel-build=no/use/auto/force: Build a suitable kernel image from source.
+
+:--kernel-upstream=<GIT_URL>: URL for Git clone of kernel sources.
+
+:--kernel-branch=<GIT_BRANCH>: Git branch to build from.
+
+Tests that require kernel image or USB controllers are skipped if none
+are available. Normally, tests use `btvirt`.
+
+VM instances share a directory ``/run/shared`` with host machine,
+located on host usually in ``/tmp/bluez-func-test-*/shared-*``. Core
+dumps etc. are copied out from it before test instance is shut down.
+
+REQUIREMENTS
+============
+
+General
+-------
+
+The following are needed:
+
+- QEmu (x86_64)
+- ``dbus-daemon`` available
+
+Recommended:
+
+- KVM-enabled x86_64 host system
+- Preferably built BlueZ source tree
+- ``chronyd`` available
+- ``util-linux`` tools available
+- ``agetty`` available
+
+Python
+------
+
+The following Python packages are required:
+
+.. code-block::
+
+ pytest>=8
+ pytest-bluezenv==0.1.5
+
+To install them via pip::
+
+ python3 -m pip install -r test/functional/requirements.txt
+
+On Fedora / RHEL, the dependencies aside from `pytest-bluezenv` can be
+installed via::
+
+ sudo dnf install python3-pytest python3-pexpect python3-dbus
+
+Kernel
+------
+
+The **test-functional(1)** tool requires a kernel image with similar
+config as **test-runner(1)**. If given `--kernel-build` option, a
+suitable image is built from sources downloaded under
+`test/.pytest_cache`.
+
+Simplest setup is
+
+.. code-block::
+
+ cp ../bluez/doc/tester.config .config
+ make olddefconfig
+ make -j8
+
+To get log timestamps right, the kernel should have the following
+configuration enabled:
+
+.. code-block::
+
+ CONFIG_HYPERVISOR_GUEST=y
+ CONFIG_PARAVIRT=y
+ CONFIG_KVM_GUEST=y
+
+ CONFIG_PTP_1588_CLOCK=y
+ CONFIG_PTP_1588_CLOCK_KVM=y
+ CONFIG_PTP_1588_CLOCK_VMCLOCK=y
+
+USB
+---
+
+Some tests may require a hardware controller instead of the virtual `btvirt` one.
+
+EXAMPLES
+========
+
+Run all tests
+-------------
+
+.. code-block::
+
+ $ test/test-functional --kernel=/pathto/bzImage
+
+ $ export FUNCTIONAL_TESTING_KERNEL=/pathto/bzImage
+ $ test/test-functional
+
+Show output during run
+----------------------
+
+.. code-block::
+
+ $ test/test-functional --log-cli-level=0
+
+Show only specific loggers:
+
+.. code-block::
+
+ $ test/test-functional --log-cli-level=0 --log-filter=rpc,host
+
+ $ test/test-functional --log-cli-level=0 --log-filter=*.bluetoothctl
+
+Filter out loggers:
+
+.. code-block::
+
+ $ test/test-functional --log-cli-level=0 --log-filter=-host
+
+ $ test/test-functional --log-cli-level=0 --log-filter=host,-host.*.1
+
+Run selected tests
+------------------
+
+.. code-block::
+
+ $ test/test-functional test/functional/test_cli_simple.py::test_bluetoothctl_script_show
+
+ $ test/test-functional -k test_bluetoothctl_script_show
+
+ $ test/test-functional -k 'test_btmgmt or test_bluetoothctl'
+
+Don't run tests with a given marker:
+
+.. code-block::
+
+ $ test/test-functional -m "not pipewire"
+
+Don't run known-failing tests:
+
+.. code-block::
+
+ $ test/test-functional -m "not xfail"
+
+Note that otherwise known-failing tests would be run, but with
+failures suppressed.
+
+Run previously failed and stop on failure
+-----------------------------------------
+
+.. code-block::
+
+ $ test/test-functional -x --ff
+
+List all tests
+--------------
+
+.. code-block::
+
+ $ test/test-functional --list
+
+Show errors from know-failing test
+----------------------------------
+
+.. code-block::
+
+ $ test/test-functional --runxfail -k test_btmgmt_info
+
+Redirect USB devices
+--------------------
+
+.. code-block::
+
+ $ test/test-functional --usb=hci0,hci1
+
+ $ export FUNCTIONAL_TESTING_CONTROLLERS=hci0,hci1
+ $ test/test-functional -vv
+
+This does not require running as root. Changing device permissions is
+sufficient. In verbose mode (``-vv``) some instructions are printed.
+
+Run all tests using the USB controllers:
+
+.. code-block::
+
+ $ test/test-functional --usb=hci0,hci1 --force-usb
+
+Run tests in parallel
+---------------------
+
+pytest-xdist is required for parallel execution. To run:
+
+.. code-block::
+
+ $ test/test-functional -n auto
+
+To reduce VM setup/teardowns:
+
+.. code-block::
+
+ $ test/test-functional -n auto --dist loadgroup
+
+Logging in to a test VM instance
+--------------------------------
+
+While test is running:
+
+.. code-block::
+
+ $ test/test-functional-attach
+
+For this to be useful, usually, you need to pause the test
+e.g. by running with ``--trace`` option.
+
+To do it manually, when starting the tester will log a line like::
+
+ TTY: socat /tmp/bluez-func-test-q658swgi/bluez-func-test-tty-0 STDIO,rawer
+
+with the location of the socket where the serial is connected to.
+
+WRITING TESTS
+=============
+
+The functional tests are written in files (test modules) names
+`test/functional/test_*.py`. They are written using standard Pytest
+style. See https://docs.pytest.org/en/stable/getting-started.html
+
+See https://pypi.org/project/pytest-bluezenv/ for documentation of
+how to write VM-using tests.
+
+Use `Black <https://black.readthedocs.io/en/stable/>`__ to autoformat
+Python test code.
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 12/16] test: add functional/integration testing framework
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (10 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 11/16] doc: add functional/integration testing documentation Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 13/16] build: add functional testing target Pauli Virtanen
` (4 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add framework for writing tests simulating "real" environments where
BlueZ and other parts of the stack run on different virtual machine
hosts that communicate with each other.
Add some smoke tests for bluetoothctl and btmgmt.
The implementation for the VM setup is maintained separately in the
pytest-bluezenv plugin, https://pypi.org/project/pytest-bluezenv
Implements:
- RPC communication with tester instances running each of the VM hosts,
so that tests can be written on the parent host which coordinates the
execution.
- Extensible way to add stateful test-specific code inside the VM
instances
- Logging control: output from different processes running inside the VM
are separated and can be filtered.
- Test runner framework with Pytest, factored into a pytest plugin
- Grouping tests to minimize VM reboots
- Redirecting USB controllers to use for testing
There is no requirement that the tests spawn VM instances.
---
test/functional/__init__.py | 2 +
test/functional/conftest.py | 48 ++++++++
test/functional/requirements.txt | 2 +
test/functional/test_bluetoothctl_vm.py | 152 ++++++++++++++++++++++++
test/functional/test_btmgmt_vm.py | 30 +++++
test/pytest.ini | 17 +++
test/test-functional | 21 ++++
test/test-functional-attach | 7 ++
8 files changed, 279 insertions(+)
create mode 100644 test/functional/__init__.py
create mode 100644 test/functional/conftest.py
create mode 100644 test/functional/requirements.txt
create mode 100644 test/functional/test_bluetoothctl_vm.py
create mode 100644 test/functional/test_btmgmt_vm.py
create mode 100644 test/pytest.ini
create mode 100755 test/test-functional
create mode 100755 test/test-functional-attach
diff --git a/test/functional/__init__.py b/test/functional/__init__.py
new file mode 100644
index 000000000..fe1c85178
--- /dev/null
+++ b/test/functional/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
diff --git a/test/functional/conftest.py b/test/functional/conftest.py
new file mode 100644
index 000000000..196afa08d
--- /dev/null
+++ b/test/functional/conftest.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+import os
+import re
+from pathlib import Path
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--list",
+ action="store_true",
+ default=None,
+ help=("List tests"),
+ )
+
+
+def pytest_configure(config):
+ if config.option.list:
+ config.option.reportchars = "A"
+ config.option.no_header = True
+ config.option.verbose = -2
+
+
+COLLECT_ERRORS = []
+
+
+def pytest_collectreport(report):
+ if report.outcome != "passed":
+ COLLECT_ERRORS.append((report.outcome, report.fspath))
+
+
+def pytest_collection_finish(session):
+ if session.config.option.list:
+ cwd = Path(".").resolve()
+ root = session.config.rootpath.absolute()
+
+ regex = re.compile(r"\[.*")
+ names = set(
+ (root.joinpath(item.location[0]), regex.sub("", item.location[2]))
+ for item in session.items
+ )
+
+ for path, name in sorted(names):
+ print(f"{path.resolve().relative_to(cwd, walk_up=True)}::{name}")
+ for outcome, name in COLLECT_ERRORS:
+ print(f"{outcome.upper()} {name}")
+ print()
+ os._exit(0)
diff --git a/test/functional/requirements.txt b/test/functional/requirements.txt
new file mode 100644
index 000000000..98b0c31d4
--- /dev/null
+++ b/test/functional/requirements.txt
@@ -0,0 +1,2 @@
+pytest>=8
+pytest-bluezenv==0.1.5
diff --git a/test/functional/test_bluetoothctl_vm.py b/test/functional/test_bluetoothctl_vm.py
new file mode 100644
index 000000000..0ba75e9a9
--- /dev/null
+++ b/test/functional/test_bluetoothctl_vm.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for bluetoothctl using VM instances
+"""
+import sys
+import re
+import pytest
+import subprocess
+import tempfile
+import warnings
+
+import time
+import logging
+
+
+from pytest_bluezenv import host_config, find_exe, run, Bluetoothd, Bluetoothctl
+
+pytestmark = [pytest.mark.vm]
+
+bluetoothctl = find_exe("client", "bluetoothctl")
+
+bluetoothd_reuse_config = host_config([Bluetoothd()], reuse=True)
+
+
+@host_config(
+ [Bluetoothctl()],
+ [Bluetoothctl()],
+)
+def test_bluetoothctl_pair_bredr(hosts):
+ host0, host1 = hosts
+
+ host0.bluetoothctl.send("scan on\n")
+ host0.bluetoothctl.expect(f"Controller {host0.bdaddr.upper()} Discovering: yes")
+
+ host1.bluetoothctl.send("pairable on\n")
+ host1.bluetoothctl.expect("Changing pairable on succeeded")
+ host1.bluetoothctl.send("discoverable on\n")
+ host1.bluetoothctl.expect(f"Controller {host1.bdaddr.upper()} Discoverable: yes")
+
+ host0.bluetoothctl.expect(f"Device {host1.bdaddr.upper()}")
+ host0.bluetoothctl.send(f"pair {host1.bdaddr}\n")
+
+ idx, m = host0.bluetoothctl.expect(r"Confirm passkey (\d+).*:")
+ key = m[0].decode("utf-8")
+
+ host1.bluetoothctl.expect(f"Confirm passkey {key}")
+
+ host0.bluetoothctl.send("yes\n")
+ host1.bluetoothctl.send("yes\n")
+
+ host0.bluetoothctl.expect("Pairing successful")
+
+
+@host_config(
+ [Bluetoothd(conf="[General]\nControllerMode = le\n"), Bluetoothctl()],
+ [Bluetoothd(conf="[General]\nControllerMode = le\n"), Bluetoothctl()],
+)
+def test_bluetoothctl_pair_le(hosts):
+ host0, host1 = hosts
+
+ host0.bluetoothctl.send("scan on\n")
+ host0.bluetoothctl.expect(f"Controller {host0.bdaddr.upper()} Discovering: yes")
+
+ host1.bluetoothctl.send("advertise on\n")
+ host1.bluetoothctl.expect("Advertising object registered")
+
+ host0.bluetoothctl.expect(f"Device {host1.bdaddr.upper()}")
+ host0.bluetoothctl.send(f"pair {host1.bdaddr.upper()}\n")
+
+ # BUG!: if controller is power cycled off/on at boot (before bluetoothd)
+ # BUG!: which is what the tester here does,
+ # BUG!: bluetoothd MGMT command to enable Secure Connections Host Support
+ # BUG!: fails and we are left with legacy passkey. It seems we get randomly
+ # BUG!: one of these depending on what state controller/kernel were before
+ # BUG!: btmgmt power off/on
+
+ idx, m = host0.bluetoothctl.expect(
+ [r"\[agent\].*Passkey:.*m(\d+)", r"Confirm passkey (\d+).*:"]
+ )
+ key = m[0].decode("utf-8")
+
+ if idx == 0:
+ warnings.warn(
+ "BUG: we got passkey authentication, bluetoothd/kernel should be fixed"
+ )
+ host1.bluetoothctl.expect(r"\[agent\] Enter passkey \(number in 0-999999\):")
+ host1.bluetoothctl.send(f"{key}\n")
+ else:
+ host1.bluetoothctl.expect(f"Confirm passkey {key}")
+
+ host0.bluetoothctl.send("yes\n")
+ host1.bluetoothctl.send("yes\n")
+
+ host0.bluetoothctl.expect("Pairing successful")
+
+
+def run_bluetoothctl(*args):
+ return run(
+ [bluetoothctl] + list(args),
+ stdout=subprocess.PIPE,
+ stdin=subprocess.DEVNULL,
+ encoding="utf-8",
+ )
+
+
+def run_bluetoothctl_script(script):
+ with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as f:
+ f.write(script)
+ f.write("\nquit")
+ f.flush()
+ return run_bluetoothctl("--init-script", f.name)
+
+
+@bluetoothd_reuse_config
+def test_bluetoothctl_show(hosts):
+ (host,) = hosts
+
+ result = host.call(run_bluetoothctl, f"show")
+ assert result.returncode == 0
+ assert f"Controller {host.bdaddr.upper()}" in result.stdout
+ assert "Powered: " in result.stdout
+ assert "Discoverable: no" in result.stdout
+
+
+@bluetoothd_reuse_config
+def test_bluetoothctl_list(hosts):
+ (host,) = hosts
+
+ result = host.call(run_bluetoothctl, "list")
+ assert result.returncode == 0
+ assert re.search(rf"{host.bdaddr.upper()}.*\[default\]", result.stdout)
+
+
+@bluetoothd_reuse_config
+def test_bluetoothctl_script_show(hosts):
+ (host,) = hosts
+
+ result = host.call(run_bluetoothctl_script, f"show")
+ assert result.returncode == 0
+ assert f"Controller {host.bdaddr.upper()}" in result.stdout
+ assert "Powered: " in result.stdout
+ assert "Discoverable: no" in result.stdout
+
+
+@bluetoothd_reuse_config
+def test_bluetoothctl_script_list(hosts):
+ (host,) = hosts
+
+ result = host.call(run_bluetoothctl_script, f"list")
+ assert result.returncode == 0
+ assert re.search(rf"{host.bdaddr.upper()}.*\[default\]", result.stdout)
diff --git a/test/functional/test_btmgmt_vm.py b/test/functional/test_btmgmt_vm.py
new file mode 100644
index 000000000..3d11d7616
--- /dev/null
+++ b/test/functional/test_btmgmt_vm.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for btmgmt using VM instances
+"""
+import sys
+import pytest
+import subprocess
+import tempfile
+
+from pytest_bluezenv import host_config, find_exe, run
+
+pytestmark = [pytest.mark.vm]
+
+btmgmt = find_exe("tools", "btmgmt")
+
+
+@host_config([])
+def test_btmgmt_info(hosts):
+ (host,) = hosts
+
+ result = host.call(
+ run,
+ [btmgmt, "--index", "0", "info"],
+ stdout=subprocess.PIPE,
+ stdin=subprocess.DEVNULL,
+ encoding="utf-8",
+ )
+ assert result.returncode == 0
+ assert f"addr {host.bdaddr.upper()}" in result.stdout
diff --git a/test/pytest.ini b/test/pytest.ini
new file mode 100644
index 000000000..0d4e48d69
--- /dev/null
+++ b/test/pytest.ini
@@ -0,0 +1,17 @@
+[pytest]
+log_format = %(asctime)s %(levelname)-6s %(name)-20s: %(message)s
+log_date_format = %Y-%m-%d %H:%M:%S.%f
+log_level = 0
+log_file = test-functional.log
+markers =
+ vm: tests requiring VM image
+
+addopts =
+ -p pytest_bluezenv
+
+# Default timeout
+vm_timeout = 30
+
+# Default sources for kernel-build when requested
+kernel_upstream = https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/
+kernel_branch = master
diff --git a/test/test-functional b/test/test-functional
new file mode 100755
index 000000000..9f90207b2
--- /dev/null
+++ b/test/test-functional
@@ -0,0 +1,21 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# See doc/test-functional.rst
+#
+TESTDIR="$(dirname "$0")"
+SRCDIR="$TESTDIR/.."
+
+BUILDDIR=
+for d in "$SRCDIR" "$SRCDIR/build" "$SRCDIR/builddir"; do
+ if [ -f "$d/src/bluetoothd" ]; then
+ BUILDDIR="$d"
+ break
+ fi
+done
+
+if [ -n "$BUILDDIR" -a -d "$BUILDDIR" ]; then
+ exec python3 -m pytest "$TESTDIR/functional" --bluez-src-dir "$SRCDIR" --bluez-build-dir "$BUILDDIR" "$@"
+else
+ exec python3 -m pytest "$TESTDIR/functional" --bluez-src-dir "$SRCDIR" "$@"
+fi
diff --git a/test/test-functional-attach b/test/test-functional-attach
new file mode 100755
index 000000000..6e65464f7
--- /dev/null
+++ b/test/test-functional-attach
@@ -0,0 +1,7 @@
+#!/bin/sh
+#
+# test-functional-attach
+#
+# Start Tmux and connect to active test-functional VM hosts.
+#
+exec python3 -mpytest_bluezenv attach "$@"
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 13/16] build: add functional testing target
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (11 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 12/16] test: add functional/integration testing framework Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 14/16] test: functional: impose Python code formatting Pauli Virtanen
` (3 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
This adds check-functional: target that runs the functional test suite.
Also add a --enable-functional-testing=<kernel-image> argument for
configure that can be used to include it in the check: make target,
possibly with a predefined kernel image.
---
Makefile.am | 10 ++++++++++
configure.ac | 22 ++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index 76c4ab5d4..7920cae68 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -812,6 +812,16 @@ endif
TESTS = $(unit_tests)
AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69
+check-functional: all
+ python3 -m pytest "$(srcdir)/test/functional" -v \
+ --kernel="$(FUNCTIONAL_TESTING_KERNEL)" \
+ --bluez-build-dir="$(top_builddir)" \
+ --bluez-src-dir="$(srcdir)"
+
+if FUNCTIONAL_TESTING
+check: check-functional
+endif
+
if DBUS_RUN_SESSION
AM_TESTS_ENVIRONMENT += dbus-run-session --
endif
diff --git a/configure.ac b/configure.ac
index 52de7d665..254d90318 100644
--- a/configure.ac
+++ b/configure.ac
@@ -405,6 +405,28 @@ if (test "${enable_testing}" = "yes"); then
#include <linux/net_tstamp.h>]])
fi
+AC_ARG_ENABLE(functional-testing, AS_HELP_STRING([--enable-functional-testing],
+ [enable functional testing tools]),
+ [enable_functional_testing=yes; functional_testing_kernel=${enableval}],
+ [enable_functional_testing=no])
+AM_CONDITIONAL(FUNCTIONAL_TESTING, test "${enable_functional_testing}" = "yes")
+AC_ARG_VAR(FUNCTIONAL_TESTING_KERNEL, [vmlinux image to use for functional testing])
+FUNCTIONAL_TESTING_KERNEL=${functional_testing_kernel}
+
+if (test "${enable_functional_testing}" = "yes"); then
+ if (test "${enable_client}" = "no" || \
+ test "${enable_tools}" != "yes" || \
+ test "${enable_testing}" != "yes"); then
+ AC_MSG_ERROR([--enable-functional-testing requires --enable-client --enable-tools --enable-testing])
+ fi
+ AC_MSG_CHECKING([pytest and dependencies])
+ python3 -m pip install --dry-run --no-index -r "${srcdir}/test/functional/requirements.txt" >/dev/null
+ if (test "$?" != "0"); then
+ AC_MSG_ERROR([pytest or dependencies missing])
+ fi
+ AC_MSG_RESULT([ok])
+fi
+
AC_ARG_ENABLE(experimental, AS_HELP_STRING([--enable-experimental],
[enable experimental tools]),
[enable_experimental=${enableval}])
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 14/16] test: functional: impose Python code formatting
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (12 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 13/16] build: add functional testing target Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 15/16] test: functional: add some Agent1 interface tests Pauli Virtanen
` (2 subsequent siblings)
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Check Python code formatting of the functional test suite.
---
test/functional/test_tests.py | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 test/functional/test_tests.py
diff --git a/test/functional/test_tests.py b/test/functional/test_tests.py
new file mode 100644
index 000000000..561b04703
--- /dev/null
+++ b/test/functional/test_tests.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for the test suite itself
+"""
+import sys
+import subprocess
+import warnings
+from pathlib import Path
+
+import pytest
+
+
+def test_formatting():
+ pytest.importorskip("black")
+
+ result = subprocess.run(
+ [sys.executable, "-mblack", "--check", "--diff", "-q", Path(__file__).parent],
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ if result.returncode != 0:
+ warnings.warn(f"Formatting incorrect:\n{result.stdout}")
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 15/16] test: functional: add some Agent1 interface tests
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (13 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 14/16] test: functional: impose Python code formatting Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-13 16:17 ` [PATCH BlueZ v5 16/16] test: functional: add basic obex file transfer tests Pauli Virtanen
2026-05-14 17:50 ` [PATCH BlueZ v5 00/16] Functional/integration testing patchwork-bot+bluetooth
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add test
test/functional/test_agent.py::test_agent_pair_bredr
---
test/functional/test_agent.py | 46 +++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
create mode 100644 test/functional/test_agent.py
diff --git a/test/functional/test_agent.py b/test/functional/test_agent.py
new file mode 100644
index 000000000..24593090b
--- /dev/null
+++ b/test/functional/test_agent.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for bluetoothctl using VM instances
+"""
+import sys
+import re
+import pytest
+import subprocess
+import tempfile
+
+import time
+import logging
+
+
+from pytest_bluezenv import host_config, Agent, wait_until
+
+pytestmark = [pytest.mark.vm]
+
+
+@host_config([Agent()], [Agent()])
+@pytest.mark.parametrize("success", [True, False], ids=["accept", "reject"])
+def test_agent_pair_bredr(hosts, success):
+ host0, host1 = hosts
+
+ host0.agent.adapter_method("StartDiscovery")
+ host0.agent.expect("org.bluez.Adapter1.StartDiscovery:reply")
+
+ host1.agent.adapter_set("Pairable", True)
+ host1.agent.adapter_set("Discoverable", True)
+
+ wait_until(host0.agent.has_device, host1.bdaddr)
+
+ host0.agent.device_method(host1.bdaddr, "Pair")
+
+ confirm_0 = host0.agent.expect("org.bluez.Agent1.RequestConfirmation")
+ confirm_1 = host1.agent.expect("org.bluez.Agent1.RequestConfirmation")
+ assert confirm_0.passkey == confirm_1.passkey
+ host0.agent.reply()
+
+ if success:
+ host1.agent.reply()
+ host0.agent.expect("org.bluez.Device1.Pair:reply")
+ else:
+ host1.agent.reply_error()
+ host0.agent.expect("org.bluez.Device1.Pair:error")
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH BlueZ v5 16/16] test: functional: add basic obex file transfer tests
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (14 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 15/16] test: functional: add some Agent1 interface tests Pauli Virtanen
@ 2026-05-13 16:17 ` Pauli Virtanen
2026-05-14 17:50 ` [PATCH BlueZ v5 00/16] Functional/integration testing patchwork-bot+bluetooth
16 siblings, 0 replies; 20+ messages in thread
From: Pauli Virtanen @ 2026-05-13 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add tests for Obex DBus API and obexctl
test/functional/test_obex.py::test_obex_ftp_get
test/functional/test_obex.py::test_obex_ftp_list
test/functional/test_obex.py::test_obexctl_list
---
test/functional/test_obex.py | 285 +++++++++++++++++++++++++++++++++++
1 file changed, 285 insertions(+)
create mode 100644 test/functional/test_obex.py
diff --git a/test/functional/test_obex.py b/test/functional/test_obex.py
new file mode 100644
index 000000000..fcb9105e5
--- /dev/null
+++ b/test/functional/test_obex.py
@@ -0,0 +1,285 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+"""
+Tests for Obex
+"""
+import sys
+import os
+import re
+import pytest
+import subprocess
+import tempfile
+import time
+import logging
+import json
+import dbus
+import threading
+from pathlib import Path
+
+import pytest
+
+from pytest_bluezenv import (
+ HostPlugin,
+ Agent,
+ host_config,
+ find_exe,
+ Bluetoothd,
+ Bluetoothctl,
+ Obexd,
+ LogStream,
+ wait_until,
+ mainloop_wrap,
+ mainloop_assert,
+ Event,
+ EventPluginMixin,
+ dbus_service_event_method,
+ Pexpect,
+ utils,
+)
+
+pytestmark = [pytest.mark.vm]
+
+log = logging.getLogger(__name__)
+
+
+BUS_NAME = "org.bluez.obex"
+PATH = "/org/bluez/obex"
+AGENT_MANAGER_INTERFACE = "org.bluez.obex.AgentManager1"
+AGENT_INTERFACE = "org.bluez.obex.Agent1"
+CLIENT_INTERFACE = "org.bluez.obex.Client1"
+SESSION_INTERFACE = "org.bluez.obex.Session1"
+FILE_TRANSFER_INTERFACE = "org.bluez.obex.FileTransfer1"
+TRANSFER_INTERFACE = "org.bluez.obex.Transfer1"
+
+FTP_UUID = "00001106-0000-1000-8000-00805f9b34fb"
+
+
+class ObexAgent(HostPlugin, EventPluginMixin):
+ depends = [Bluetoothd()]
+ name = "obex_agent"
+
+ def __init__(self, path="/obexagent"):
+ self.path = path
+
+ @mainloop_wrap
+ def setup(self, impl):
+ EventPluginMixin.setup(self, impl)
+
+ self.bus = dbus.SessionBus()
+ self.bus.set_exit_on_disconnect(False)
+
+ self.agent = ObexAgentObject(self.bus, self.path, self.events)
+
+ bluez = self.bus.get_object(BUS_NAME, PATH)
+ self.manager = dbus.Interface(bluez, AGENT_MANAGER_INTERFACE)
+ self.manager.RegisterAgent(self.path)
+
+ log.info("Obex agent registered")
+
+ def cleanup(self):
+ path = Path("/run/obex")
+ for f in path.iterdir():
+ f.unlink()
+
+
+def agent_method(*a, **kw):
+ return dbus_service_event_method(AGENT_INTERFACE, *a, **kw)
+
+
+class ObexAgentObject(dbus.service.Object):
+ @mainloop_assert
+ def __init__(self, bus, path, events):
+ self.events = events
+ super().__init__(bus, path)
+
+ AuthorizePush = agent_method("AuthorizePush", ("path",), "o", "s", sync=False)
+ Cancel = agent_method("Cancel")
+
+
+def write_obex_file(name, content):
+ with open(f"/run/obex/{name}", "w") as f:
+ f.write(content)
+
+
+def read_file(name):
+ with open(name, "r") as f:
+ return f.read()
+
+
+#
+# Direct Obex Python client API tests
+#
+
+
+class ObexClient(HostPlugin, EventPluginMixin):
+ name = "obex"
+
+ @mainloop_wrap
+ def setup(self, impl):
+ EventPluginMixin.setup(self, impl)
+
+ self.transferred = 0
+ self.transfer_path = None
+ self.transfer_size = 0
+
+ self.bus = dbus.SessionBus()
+ self.bus.set_exit_on_disconnect(False)
+ self.log = logging.getLogger(self.name)
+ self.client = dbus.Interface(
+ self.bus.get_object(BUS_NAME, PATH), CLIENT_INTERFACE
+ )
+
+ self.bus.add_signal_receiver(
+ self.properties_changed,
+ dbus_interface="org.freedesktop.DBus.Properties",
+ signal_name="PropertiesChanged",
+ path_keyword="path",
+ )
+
+ @mainloop_wrap
+ def connect(self, bdaddr):
+ def reply(path):
+ obj = self.bus.get_object(BUS_NAME, path)
+ self.session = dbus.Interface(obj, SESSION_INTERFACE)
+ self.ftp = dbus.Interface(obj, FILE_TRANSFER_INTERFACE)
+
+ self._object_method(
+ self.client, "CreateSession", bdaddr, {"Target": "ftp"}, reply_handler=reply
+ )
+
+ @mainloop_assert
+ def properties_changed(self, interface, properties, invalidated, path):
+ if path != self.transfer_path:
+ return
+
+ if "Status" in properties and (
+ properties["Status"] == "complete" or properties["Status"] == "error"
+ ):
+ self.events.put(
+ Event(
+ f"{FILE_TRANSFER_INTERFACE}:{properties['Status']}",
+ properties=properties,
+ )
+ )
+ self.log.debug(f"Transfer {properties['Status']}")
+
+ if "Transferred" not in properties:
+ return
+
+ value = properties["Transferred"]
+ speed = (value - self.transferred) / 1000
+ self.log.debug(
+ f"Transfer progress {value}/{self.transfer_size} at {speed} kBps"
+ )
+ self.transferred = value
+
+ @mainloop_wrap
+ def ftp_list_folder(self):
+ return self.ftp.ListFolder()
+
+ @mainloop_wrap
+ def ftp_get_file(self, dst, src):
+ path, properties = self.ftp.GetFile(dst, src)
+ self.transfer_path = path
+ self.transfer_size = properties["Size"]
+ return properties["Filename"]
+
+
+@pytest.fixture
+def paired_hosts(hosts):
+ from .test_agent import test_agent_pair_bredr
+
+ if hosts[0].agent.has_device(hosts[1].bdaddr):
+ return hosts
+
+ test_agent_pair_bredr(hosts, True)
+ return hosts
+
+
+obex_host_config = host_config(
+ [Agent(), Obexd(), ObexClient(), Pexpect()],
+ [Agent(), Obexd(), ObexAgent()],
+ reuse=True,
+)
+
+
+@pytest.fixture
+def obex_hosts(paired_hosts):
+ host0, host1 = paired_hosts
+
+ if hasattr(host0, "session"):
+ return paired_hosts
+
+ host0.obex.connect(host1.bdaddr)
+
+ service = host1.agent.expect("org.bluez.Agent1.AuthorizeService")
+ assert service.uuid == FTP_UUID
+ host1.agent.reply()
+
+ host0.obex.expect("org.bluez.obex.Client1.CreateSession:reply")
+
+ yield paired_hosts
+
+ host1.obex_agent.cleanup()
+
+
+@obex_host_config
+def test_obex_ftp_list(obex_hosts):
+ host0, host1 = obex_hosts
+
+ host1.call(write_obex_file, "test", "1234")
+
+ (item,) = host0.obex.ftp_list_folder()
+ assert item["Type"] == "file"
+ assert item["Name"] == "test"
+ assert item["Size"] == 4
+
+
+@obex_host_config
+def test_obex_ftp_get(obex_hosts):
+ host0, host1 = obex_hosts
+
+ host1.call(write_obex_file, "test", "1234")
+
+ filename = host0.obex.ftp_get_file("", "test")
+ host0.obex.expect("org.bluez.obex.FileTransfer1:complete")
+ assert host0.call(read_file, filename) == "1234"
+
+
+#
+# obexctl tests
+#
+
+
+@pytest.fixture
+def obexctl(obex_hosts):
+ host0, host1 = obex_hosts
+
+ exe = find_exe("tools", "obexctl")
+ obexctl = host0.pexpect.spawn([exe])
+
+ obexctl.expect("Client /org/bluez/obex")
+ obexctl.send(f"connect {host1.bdaddr} {FTP_UUID}\n")
+
+ service = host1.agent.expect("org.bluez.Agent1.AuthorizeService")
+ assert service.uuid == FTP_UUID
+ host1.agent.reply()
+
+ obexctl.expect("Connection successful")
+ obexctl.send(f"select /org/bluez/obex/client/session1\n")
+
+ yield obexctl
+
+ obexctl.close()
+
+
+@obex_host_config
+def test_obexctl_list(obex_hosts, obexctl):
+ host0, host1 = obex_hosts
+
+ host1.call(write_obex_file, "test", "1234")
+
+ obexctl.send(f"ls\n")
+ obexctl.expect(f"Type: file")
+ obexctl.expect(f"Name: test")
+ obexctl.expect(f"Size: 4")
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH BlueZ v5 00/16] Functional/integration testing
2026-05-13 16:17 [PATCH BlueZ v5 00/16] Functional/integration testing Pauli Virtanen
` (15 preceding siblings ...)
2026-05-13 16:17 ` [PATCH BlueZ v5 16/16] test: functional: add basic obex file transfer tests Pauli Virtanen
@ 2026-05-14 17:50 ` patchwork-bot+bluetooth
2026-05-14 19:11 ` Luiz Augusto von Dentz
16 siblings, 1 reply; 20+ messages in thread
From: patchwork-bot+bluetooth @ 2026-05-14 17:50 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Wed, 13 May 2026 19:17:17 +0300 you wrote:
> Add framework for writing tests simulating "real" environments where
> BlueZ and other parts of the stack run on different virtual machine
> hosts that communicate with each other.
>
> *** v5 ***
>
> https://github.com/pv/bluez/compare/func-test-v4-r..func-test-v5
>
> [...]
Here is the summary with links:
- [BlueZ,v5,01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=e19c771841e9
- [BlueZ,v5,02/16] emulator: btvirt: allow specifying where server unix sockets are made
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=5ee43f9993dd
- [BlueZ,v5,03/16] emulator: btvirt: support SCO data packets
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=cc077e4a12dd
- [BlueZ,v5,04/16] emulator: btdev: clear more state on Reset
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=f1a303e1d07a
- [BlueZ,v5,05/16] test-runner: enable path argument for --unix
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ff2ecb82e4c3
- [BlueZ,v5,06/16] test-runner: Add -o/--option option
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=82c635c879bf
- [BlueZ,v5,07/16] test-runner: allow source tree root for -k
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=1929b3a99d2f
- [BlueZ,v5,08/16] test-runner: use virtio-serial for implementing -u device forwarding
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=908c14fc3abb
- [BlueZ,v5,09/16] doc: enable CONFIG_VIRTIO_CONSOLE in tester config
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7856de4d69d6
- [BlueZ,v5,10/16] doc: enable KVM paravirtualization & clock support in tester kernel config
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=48cb22a572c7
- [BlueZ,v5,11/16] doc: add functional/integration testing documentation
(no matching commit)
- [BlueZ,v5,12/16] test: add functional/integration testing framework
(no matching commit)
- [BlueZ,v5,13/16] build: add functional testing target
(no matching commit)
- [BlueZ,v5,14/16] test: functional: impose Python code formatting
(no matching commit)
- [BlueZ,v5,15/16] test: functional: add some Agent1 interface tests
(no matching commit)
- [BlueZ,v5,16/16] test: functional: add basic obex file transfer tests
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH BlueZ v5 00/16] Functional/integration testing
2026-05-14 17:50 ` [PATCH BlueZ v5 00/16] Functional/integration testing patchwork-bot+bluetooth
@ 2026-05-14 19:11 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 20+ messages in thread
From: Luiz Augusto von Dentz @ 2026-05-14 19:11 UTC (permalink / raw)
To: patchwork-bot+bluetooth; +Cc: Pauli Virtanen, linux-bluetooth
Hi Pauli,
On Thu, May 14, 2026 at 1:51 PM <patchwork-bot+bluetooth@kernel.org> wrote:
>
> Hello:
>
> This series was applied to bluetooth/bluez.git (master)
> by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
>
> On Wed, 13 May 2026 19:17:17 +0300 you wrote:
> > Add framework for writing tests simulating "real" environments where
> > BlueZ and other parts of the stack run on different virtual machine
> > hosts that communicate with each other.
> >
> > *** v5 ***
> >
> > https://github.com/pv/bluez/compare/func-test-v4-r..func-test-v5
> >
> > [...]
>
> Here is the summary with links:
> - [BlueZ,v5,01/16] emulator: btvirt: check pkt lengths, don't get stuck on malformed
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=e19c771841e9
> - [BlueZ,v5,02/16] emulator: btvirt: allow specifying where server unix sockets are made
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=5ee43f9993dd
> - [BlueZ,v5,03/16] emulator: btvirt: support SCO data packets
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=cc077e4a12dd
> - [BlueZ,v5,04/16] emulator: btdev: clear more state on Reset
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=f1a303e1d07a
> - [BlueZ,v5,05/16] test-runner: enable path argument for --unix
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ff2ecb82e4c3
> - [BlueZ,v5,06/16] test-runner: Add -o/--option option
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=82c635c879bf
> - [BlueZ,v5,07/16] test-runner: allow source tree root for -k
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=1929b3a99d2f
> - [BlueZ,v5,08/16] test-runner: use virtio-serial for implementing -u device forwarding
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=908c14fc3abb
> - [BlueZ,v5,09/16] doc: enable CONFIG_VIRTIO_CONSOLE in tester config
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7856de4d69d6
> - [BlueZ,v5,10/16] doc: enable KVM paravirtualization & clock support in tester kernel config
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=48cb22a572c7
Push the ones that didn't introduce new functionality, there were some
complaints from the likes of checkpatch regarding scripts with execute
permission, and Id like to go more in detail about how to write tests
and what is the RPC message protocol since we could in teory use the
same protocol used for autopts for example.
> - [BlueZ,v5,11/16] doc: add functional/integration testing documentation
> (no matching commit)
> - [BlueZ,v5,12/16] test: add functional/integration testing framework
> (no matching commit)
> - [BlueZ,v5,13/16] build: add functional testing target
> (no matching commit)
> - [BlueZ,v5,14/16] test: functional: impose Python code formatting
> (no matching commit)
> - [BlueZ,v5,15/16] test: functional: add some Agent1 interface tests
> (no matching commit)
> - [BlueZ,v5,16/16] test: functional: add basic obex file transfer tests
> (no matching commit)
>
> You are awesome, thank you!
> --
> Deet-doot-dot, I am a bot.
> https://korg.docs.kernel.org/patchwork/pwbot.html
>
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 20+ messages in thread