Skip to contentSkip to conversation

Functional/integration testing

PATCH Superseded Pauli Virtanen 3 participants 17 messages Feb 28 – Mar 2

Subsystems (2)

v1 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

cover Functional/integration testing

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.

Implements:

- RPC communication with tester instances running each of the VM hosts. Tests run on parent host, which instructs VM hosts what to do.

- 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 (more convenient than Python/unittest)

- Automatic grouping of tests to minimize VM reboots

- Redirecting USB controllers to use for testing in addition to btvirt

- Fairly straightforward, ~1600 sloc for the framework

There is no requirement that the tests spawn VM instances, the test runner can be used for any tests written in Python.

See doc/test-functional.rst for various examples.

Also unit/func_test/test_bluetoothctl_vm.py has some simple cases, and unit/func_test/test_pipewire.py for a more complicated setup

    host0(qemu): Pipewire <-> BlueZ <-> kernel
    <-> btvirt
    host1(qemu): kernel <-> BlueZ <-> Pipewire

The framework allows easily passing any data and code between the parent and VM hosts, so writing tests is straightforward.

***

Some examples:

$ unit/test-functional --list -q

unit/func_test/lib/tests/test_rpc.py::test_basic unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_script_show[hosts1-vm1] unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] unit/func_test/test_pipewire.py::test_pipewire[hosts3-vm2]

$ unit/test-functional -v --no-header
======================================= test session starts ========================================
collected 5 items
unit/func_test/lib/tests/test_rpc.py::test_basic PASSED                                      [ 20%]
unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_script_show[hosts1-vm1] SKIPPED    [ 40%]
unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] SKIPPED (No kernel image)     [ 60%]

unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] SKIPPED (No k...) [ 80%]
unit/func_test/test_pipewire.py::test_pipewire[hosts3-vm2] SKIPPED (No kernel image) [100%]

=================================== 1 passed, 4 skipped in 0.19s ===================================

$ unit/test-functional --kernel=../linux
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items
unit/func_test/lib/tests/test_rpc.py .                                   [ 20%]
unit/func_test/test_bluetoothctl_vm.py .                                 [ 40%]
unit/func_test/test_btmgmt_vm.py .                                       [ 60%]
unit/func_test/test_bluetoothctl_vm.py .                                 [ 80%]
unit/func_test/test_pipewire.py .                                        [100%]

============================== 5 passed in 41.92s ==============================

$ unit/test-functional --kernel=../linux -k test_btmgmt
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py . [100%]

======================= 1 passed, 4 deselected in 9.15s ========================

$ grep btmgmt test-functional.log
13:15:42 INFO   rpc.host.0.0        :  client: call_plugin ('call', '__call__', <function run at 0x7f27b81ce140>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:15:42 INFO   host.0.0.rpc        :  server: call_plugin ('call', '__call__', <function run at 0x7fd5e35a1010>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:15:42 INFO   host.0.0.run        :      $ /home/pauli/prj/external/bluez/build/tools/btmgmt --index 0 info
$ unit/test-functional --kernel=../linux -k test_btmgmt --log-cli-level=0
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] -------------------------------- live log setup --------------------------------

13:00:31 INFO   func_test.lib.env   :  Starting btvirt: /usr/bin/stdbuf -o L -e L /home/pauli/prj/external/bluez/build/emulator/btvirt --server=/tmp/bluez-func-test-8t6ychy8
13:00:31 OUT    btvirt              :  Bluetooth emulator ver 5.86
13:00:31 INFO   func_test.lib.env   :  Starting host: /home/pauli/prj/external/bluez/build/tools/test-runner --kernel=../linux/arch/x86/boot/bzImage -u/tmp/bluez-func-test-8t6ychy8/bt-server-bredrle -o -chardev -o socket,id=ser0,path=/tmp/bluez-func-test-8t6ychy8/bluez-func-test-rpc-0,server=on,wait=off -o -device -o virtio-serial -o -device -o virtserialport,chardev=ser0,name=bluez-func-test-rpc -H -- /usr/bin/python3 -P /home/pauli/prj/external/bluez/unit/func_test/lib/runner.py /dev/ttyS2
13:00:31 OUT    btvirt              :  Request for /tmp/bluez-func-test-8t6ychy8/bt-server-bredrle
13:00:32 OUT    host.0.0            :  early console in extract_kernel
13:00:32 OUT    host.0.0            :  input_data: 0x000000000425c2c4
...
13:00:39 INFO   rpc.host.0.0        :  client: call_plugin ('call', '__call__', <function run at 0x7f7547472140>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:00:39 DEBUG  host.0.0.rpc        :  server: done
13:00:39 INFO   host.0.0.rpc        :  server: call_plugin ('call', '__call__', <function run at 0x7f77dcc81010>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:00:39 INFO   host.0.0.run        :      $ /home/pauli/prj/external/bluez/build/tools/btmgmt --index 0 info
13:00:40 OUT    host.0.0.run.out    :  hci0:	Primary controller
13:00:40 OUT    host.0.0.run.out    :  	addr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000
13:00:40 OUT    host.0.0.run.out    :  	supported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver
13:00:40 OUT    host.0.0.run.out    :  	current settings: br/edr
13:00:40 OUT    host.0.0.run.out    :  	name
13:00:40 OUT    host.0.0.run.out    :  	short name
13:00:40 INFO   host.0.0.run        :  (return code 0)
13:00:40 DEBUG  rpc.host.0.0        :  client-reply

PASSED [100%]
13:00:40 OUT host.0.0 : qemu-system-x86_64: terminating on signal 15 from pid 149047 (python3)
======================= 1 passed, 4 deselected in 8.84s ========================

$ unit/test-functional --kernel=../linux -k test_bluetoothctl_pair --log-cli-level=0 --log-filter=*.bluetoothctl,rpc.* --force-usb
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] -------------------------------- live log setup --------------------------------

13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bdaddr object at 0x7f268712d160>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Call object at 0x7f268712d2b0>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.DbusSystem object at 0x7f2687aa30e0>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bluetoothd object at 0x7f2687aa3230>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bluetoothctl object at 0x7f268712d010>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bdaddr object at 0x7f26871542d0>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Call object at 0x7f2687154410>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.DbusSystem object at 0x7f2687aa30e0>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bluetoothd object at 0x7f2687aa3230>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bluetoothctl object at 0x7f2687154190>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: wait_load () {}
13:03:21 DEBUG  rpc.host.0.0        :  client-reply
13:03:21 INFO   rpc.host.0.1        :  client: wait_load () {}
13:03:21 DEBUG  rpc.host.0.1        :  client-reply
-------------------------------- live log call ---------------------------------
13:03:21 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'show\n') {}
13:03:21 DEBUG  rpc.host.0.0        :  client-reply
13:03:21 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Powered: yes') {}
...
13:03:23 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'pair 70:1a:b8:73:99:bb\n') {}
13:03:23 OUT    host.0.0.bluetoothctl:  pair 70:1a:b8:73:99:bb
13:03:23 DEBUG  rpc.host.0.0        :  client-reply
13:03:23 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Confirm passkey (\\d+).*:') {}
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> pair 70:1a:b8:73:99:bb
13:03:23 OUT    host.0.0.bluetoothctl:  Attempting to pair with 70:1A:B8:73:99:BB
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 device_flags_changed: 70:1A:B8:73:99:BB (BR/EDR)
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]>      supp: 0x00000007  curr: 0x00000000
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 type 7 discovering off
13:03:25 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 70:1A:B8:73:99:BB type BR/EDR connected eir_len 12
13:03:25 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB Connected: yes
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> Request confirmation
13:03:25 DEBUG  rpc.host.0.0        :  client-reply
13:03:25 INFO   rpc.host.0.1        :  client: call_plugin ('bluetoothctl', 'expect', 'Confirm passkey 237345') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> hci0 84:5C:F3:77:31:19 type BR/EDR connected eir_len 12
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> [NEW] Device 84:5C:F3:77:31:19 BlueZ 5.86
13:03:25 DEBUG  rpc.host.0.1        :  client-reply
13:03:25 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'yes\n') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> [BlueZ 5.86]> Request confirmation
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [agent] Confirm passkey 237345 (yes/no): yes
13:03:25 DEBUG  rpc.host.0.0        :  client-reply
13:03:25 INFO   rpc.host.0.1        :  client: call_plugin ('bluetoothctl', 'send', 'yes\n') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [BlueZ 5.86]> [agent] Confirm passkey 237345 (yes/no): yes
13:03:25 DEBUG  rpc.host.0.1        :  client-reply
13:03:25 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Pairing successful') {}
13:03:25 OUT    host.0.0.bluetoothctl:  yes
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> hci0 new_link_key 70:1A:B8:73:99:BB type 0x08 pin_len 0 store_hint 1
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB Bonded: yes
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB AddressType: public
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
13:03:26 DEBUG  rpc.host.0.0        :  client-reply

PASSED [100%]
------------------------------ live log teardown -------------------------------
13:03:26 OUT host.0.0.bluetoothctl: [BlueZ 5.86]> [CHG] Device 70:1A:B8:98:FF:qemu-system-x86_64: terminating on signal 15 from pid 149357 (python3)

======================= 1 passed, 4 deselected in 13.22s =======================

$ COLUMNS=80 unit/test-functional -k test_btmgmt --kernel=../linux --trace
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py

Quoted 2 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

PDB runcall (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(19)test_btmgmt_info()

-> (host,) = hosts
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(21)test_btmgmt_info()

-> result = host.call(
(Pdb) p host.bdaddr
'00:aa:01:00:00:42'
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(22)test_btmgmt_info()

-> run,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(23)test_btmgmt_info()

-> [btmgmt, "--index", "0", "info"],
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(24)test_btmgmt_info()

-> stdout=subprocess.PIPE,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(25)test_btmgmt_info()

-> stdin=subprocess.DEVNULL,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(26)test_btmgmt_info()

-> encoding="utf-8",
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(21)test_btmgmt_info()

-> result = host.call(
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(28)test_btmgmt_info()

-> assert result.returncode == 0
(Pdb) p result
CompletedProcess(args=['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info'], returncode=0, stdout='hci0:\tPrimary controller\n\taddr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000\n\tsupported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver \n\tcurrent settings: br/edr \n\tname \n\tshort name \n') (Pdb) print(result.stdout)
hci0: Primary controller
addr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000
supported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver
current settings: br/edr
name
short name
(Pdb) q

!!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Quitting debugger !!!!!!!!!!!!!!!!!!! ======================= 4 deselected in 75.91s (0:01:15) =======================

***

Pauli Virtanen (11):
emulator: btvirt: check pkt lengths, don't get stuck on malformed
emulator: btvirt: allow specifying where server unix sockets are made
test-runner: enable path argument for --unix
test-runner: Add -o/--option option
test-runner: allow source tree root for -k
doc: enable CONFIG_VIRTIO_CONSOLE in tester config
test-runner: use virtio-serial for implementing -u device forwarding
doc: add functional/integration testing documentation
unit: add functional/integration testing framework
unit: func_test: add Pipewire-using smoke tests
build: add functional testing target

 Makefile.am                            |   7 +
 configure.ac                           |  17 +
 doc/ci.config                          |   1 +
 doc/test-functional.rst                | 374 +++++++++++++++++++
 doc/test-runner.rst                    |   1 +
 doc/tester.config                      |   1 +
 emulator/main.c                        |  37 +-
 emulator/server.c                      |   9 +
 tools/test-runner.c                    | 376 ++++++++++++++-----
 unit/func_test/__init__.py             |   0
 unit/func_test/conftest.py             | 277 ++++++++++++++
 unit/func_test/lib/__init__.py         |   5 +
 unit/func_test/lib/env.py              | 484 +++++++++++++++++++++++++
 unit/func_test/lib/host_plugins.py     | 269 ++++++++++++++
 unit/func_test/lib/rpc.py              | 293 +++++++++++++++
 unit/func_test/lib/runner.py           |  10 +
 unit/func_test/lib/tests/__init__.py   |   0
 unit/func_test/lib/tests/test_rpc.py   |  49 +++
 unit/func_test/lib/utils.py            | 266 ++++++++++++++
 unit/func_test/requirements.txt        |   3 +
 unit/func_test/test_bluetoothctl_vm.py |  76 ++++
 unit/func_test/test_btmgmt_vm.py       |  29 ++
 unit/func_test/test_pipewire.py        | 137 +++++++
 unit/pytest.ini                        |   7 +
 unit/test-functional                   |   8 +
 25 files changed, 2631 insertions(+), 105 deletions(-)
 create mode 100644 doc/test-functional.rst
 create mode 100644 unit/func_test/__init__.py
 create mode 100644 unit/func_test/conftest.py
 create mode 100644 unit/func_test/lib/__init__.py
 create mode 100644 unit/func_test/lib/env.py
 create mode 100644 unit/func_test/lib/host_plugins.py
 create mode 100644 unit/func_test/lib/rpc.py
 create mode 100644 unit/func_test/lib/runner.py
 create mode 100644 unit/func_test/lib/tests/__init__.py
 create mode 100644 unit/func_test/lib/tests/test_rpc.py
 create mode 100644 unit/func_test/lib/utils.py
 create mode 100644 unit/func_test/requirements.txt
 create mode 100644 unit/func_test/test_bluetoothctl_vm.py
 create mode 100644 unit/func_test/test_btmgmt_vm.py
 create mode 100644 unit/func_test/test_pipewire.py
 create mode 100644 unit/pytest.ini
 create mode 100755 unit/test-functional
Signature · 1 line
2.53.0
#1 lore raw

PATCH 1/11 emulator: btvirt: check pkt lengths, don't get stuck on malformed

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(+)
emulator/server.c +9 -0
index fa2bc07be..f14e14cd2 100644
@@ -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;
 			}
 
Signature · 1 line
2.53.0
#2 lore raw

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=1059428

---Test result---

Test Summary:

CheckPatch                    PENDING   0.31 seconds
GitLint                       PENDING   0.27 seconds
BuildEll                      PASS      21.22 seconds
BluezMake                     FAIL      631.87 seconds
MakeCheck                     FAIL      13.18 seconds
MakeDistcheck                 FAIL      82.97 seconds
CheckValgrind                 FAIL      215.70 seconds
CheckSmatch                   FAIL      350.74 seconds
bluezmakeextell               FAIL      177.68 seconds
IncrementalBuild              PENDING   0.28 seconds
ScanBuild                     FAIL      489.22 seconds

Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:

##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:

##############################
Test: BluezMake - FAIL
Desc: Build BlueZ
Output:

tools/mgmt-tester.c: In function ‘main’:
tools/mgmt-tester.c:12984:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 12984 | int main(int argc, char *argv[])

      |     ^~~~
unit/test-avdtp.c: In function ‘main’:
unit/test-avdtp.c:766:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
  766 | int main(int argc, char *argv[])
      |     ^~~~
unit/test-avrcp.c: In function ‘main’:
unit/test-avrcp.c:989:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
  989 | int main(int argc, char *argv[])
      |     ^~~~

/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:6194: tools/test-runner] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4224: all] Error 2
##############################
Test: MakeCheck - FAIL
Desc: Run Bluez Make Check
Output:

/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:6194: tools/test-runner] Error 1
make: *** [Makefile:10903: check] Error 2
##############################
Test: MakeDistcheck - FAIL
Desc: Run Bluez Make Distcheck
Output:

/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/bluez-5.86/_build/sub/../../tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:6194: tools/test-runner] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [Makefile:4224: all] Error 2
make: *** [Makefile:10824: distcheck] Error 1
##############################
Test: CheckValgrind - FAIL
Desc: Run Bluez Make Check with Valgrind
Output:

tools/mgmt-tester.c: In function ‘main’:
tools/mgmt-tester.c:12984:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 12984 | int main(int argc, char *argv[])

      |     ^~~~

/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:6194: tools/test-runner] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:10903: check] Error 2
##############################
Test: CheckSmatch - FAIL
Desc: Run smatch tool with source
Output:

src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:846:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1339:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1370:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:312:25: warning: array of flexible structures
src/shared/bap.c: note: in included file:
./src/shared/ascs.h:88:25: warning: array of flexible structures
src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:846:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1339:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1370:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:312:25: warning: array of flexible structures
src/shared/bap.c: note: in included file:
./src/shared/ascs.h:88:25: warning: array of flexible structures
src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' tools/mesh-cfgtest.c:1453:17: warning: unknown escape sequence: '\%'
tools/sco-tester.c: note: in included file:
./lib/bluetooth/bluetooth.h:232:15: warning: array of flexible structures ./lib/bluetooth/bluetooth.h:237:31: warning: array of flexible structures tools/bneptest.c:634:39: warning: unknown escape sequence: '\%'
tools/seq2bseq.c:57:26: warning: Variable length array is used.
tools/obex-client-tool.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' src/advertising.c: note: in included file:
./src/shared/mgmt.h:95:25: error: redefinition of unsigned int enum mgmt_io_capability src/adv_monitor.c: note: in included file:
./src/shared/mgmt.h:95:25: error: redefinition of unsigned int enum mgmt_io_capability unit/avctp.c:505:34: warning: Variable length array is used.
unit/avctp.c:556:34: warning: Variable length array is used.
unit/test-avrcp.c:373:26: warning: Variable length array is used. unit/test-avrcp.c:398:26: warning: Variable length array is used. unit/test-avrcp.c:414:24: warning: Variable length array is used. unit/avrcp-lib.c:1085:34: warning: Variable length array is used. unit/avrcp-lib.c:1583:34: warning: Variable length array is used. unit/avrcp-lib.c:1612:34: warning: Variable length array is used. unit/avrcp-lib.c:1638:34: warning: Variable length array is used.
src/advertising.c: note: in included file:
./src/shared/mgmt.h:95:25: error: redefinition of unsigned int enum mgmt_io_capability src/adv_monitor.c: note: in included file:
./src/shared/mgmt.h:95:25: error: redefinition of unsigned int enum mgmt_io_capability mesh/mesh-io-mgmt.c:525:67: warning: Variable length array is used.
client/display.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:846:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1339:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1370:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:312:25: warning: array of flexible structures
src/shared/bap.c: note: in included file:
./src/shared/ascs.h:88:25: warning: array of flexible structures
src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' monitor/packet.c:1993:26: warning: Variable length array is used.
monitor/packet.c: note: in included file:
monitor/bt.h:3866:52: warning: array of flexible structures
monitor/bt.h:3854:40: warning: array of flexible structures
monitor/msft.c: note: in included file:
monitor/msft.h:88:44: warning: array of flexible structures
tools/rctest.c:631:33: warning: non-ANSI function declaration of function 'automated_send_recv' tools/hex2hcd.c:136:26: warning: Variable length array is used.
tools/meshctl.c:324:33: warning: non-ANSI function declaration of function 'forget_mesh_devices' tools/mesh-gatt/node.c:456:39: warning: non-ANSI function declaration of function 'node_get_local_node' tools/mesh-gatt/net.c:1239:30: warning: non-ANSI function declaration of function 'get_next_seq' tools/mesh-gatt/net.c:2193:29: warning: non-ANSI function declaration of function 'net_get_default_ttl' tools/mesh-gatt/net.c:2207:26: warning: non-ANSI function declaration of function 'net_get_seq_num' tools/mesh-gatt/prov.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' tools/mesh-gatt/onoff-model.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' ell/log.c:431:65: warning: non-ANSI function declaration of function 'register_debug_section' ell/log.c:439:68: warning: non-ANSI function declaration of function 'free_debug_sections' ell/random.c:60:42: warning: non-ANSI function declaration of function 'l_getrandom_is_supported' ell/cipher.c:660:28: warning: non-ANSI function declaration of function 'init_supported' ell/checksum.c:382:28: warning: non-ANSI function declaration of function 'init_supported' ell/checksum.c:444:47: warning: non-ANSI function declaration of function 'l_checksum_cmac_aes_supported' ell/cipher.c:519:24: warning: Variable length array is used.
ell/cert-crypto.c:36:33: warning: Variable length array is used. ell/cert-crypto.c:142:36: warning: Variable length array is used. ell/cert-crypto.c:198:36: warning: Variable length array is used. ell/cert-crypto.c:251:31: warning: Variable length array is used.
ell/key.c:550:25: warning: Variable length array is used.
ell/dbus-service.c:548:49: warning: non-ANSI function declaration of function '_dbus_object_tree_new' ell/dbus-filter.c:233:46: warning: Variable length array is used.
ell/tls.c:45:25: warning: Variable length array is used.
ell/tls.c:86:22: warning: Variable length array is used.
ell/tls.c:86:46: warning: Variable length array is used.
ell/tls.c:1819:26: warning: Variable length array is used.
ell/tls-suites.c:1079:25: warning: Variable length array is used. ell/tls-suites.c:1081:34: warning: Variable length array is used. ell/tls-suites.c:1084:41: warning: Variable length array is used. ell/tls-suites.c:1133:41: warning: Variable length array is used. emulator/btdev.c:470:29: warning: Variable length array is used. emulator/bthost.c:700:28: warning: Variable length array is used. emulator/bthost.c:701:32: warning: Variable length array is used. emulator/bthost.c:918:28: warning: Variable length array is used. emulator/bthost.c:952:28: warning: Variable length array is used. emulator/bthost.c:953:32: warning: Variable length array is used.
/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:6194: tools/test-runner] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4224: all] Error 2
##############################
Test: bluezmakeextell - FAIL
Desc: Build Bluez with External ELL
Output:

/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:6194: tools/test-runner] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4224: all] Error 2
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:

##############################
Test: ScanBuild - FAIL
Desc: Run Scan Build
Output:

src/shared/gatt-client.c:455:21: warning: Use of memory after it is freed

        gatt_db_unregister(op->client->db, op->db_id);
                           ^~~~~~~~~~
src/shared/gatt-client.c:700:2: warning: Use of memory after it is freed
        discovery_op_complete(op, false, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1000:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1106:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1300:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1365:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1640:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1645:2: warning: Use of memory after it is freed
        discover_all(op);
        ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1701:56: warning: Use of memory after it is freed
        notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
                                          ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2154:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2162:8: warning: Use of memory after it is freed
                                                        discovery_op_ref(op),
                                                        ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3340:2: warning: Use of memory after it is freed
        complete_write_long_op(req, success, 0, false);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3362:2: warning: Use of memory after it is freed
        request_unref(req);
        ^~~~~~~~~~~~~~~~~~
13 warnings generated.
src/shared/bap.c:1529:8: warning: Use of memory after it is freed
        bap = bt_bap_ref_safe(bap);
              ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2340:20: warning: Use of memory after it is freed
        return queue_find(stream->bap->streams, NULL, stream);
                          ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
src/shared/gatt-client.c:455:21: warning: Use of memory after it is freed
        gatt_db_unregister(op->client->db, op->db_id);
                           ^~~~~~~~~~
src/shared/gatt-client.c:700:2: warning: Use of memory after it is freed
        discovery_op_complete(op, false, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1000:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1106:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1300:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1365:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1640:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1645:2: warning: Use of memory after it is freed
        discover_all(op);
        ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1701:56: warning: Use of memory after it is freed
        notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
                                          ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2154:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2162:8: warning: Use of memory after it is freed
                                                        discovery_op_ref(op),
                                                        ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3340:2: warning: Use of memory after it is freed
        complete_write_long_op(req, success, 0, false);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3362:2: warning: Use of memory after it is freed
        request_unref(req);
        ^~~~~~~~~~~~~~~~~~
13 warnings generated.
tools/hciattach.c:817:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 10)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:865:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 4)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:887:8: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
                if ((n = read_hci_event(fd, resp, 10)) < 0) {
                     ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:909:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 4)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:930:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 4)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/hciattach.c:974:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n'
        if ((n = read_hci_event(fd, resp, 6)) < 0) {
             ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 warnings generated.
src/shared/bap.c:1529:8: warning: Use of memory after it is freed
        bap = bt_bap_ref_safe(bap);
              ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2340:20: warning: Use of memory after it is freed
        return queue_find(stream->bap->streams, NULL, stream);
                          ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
src/oui.c:50:2: warning: Value stored to 'hwdb' is never read
        hwdb = udev_hwdb_unref(hwdb);
        ^      ~~~~~~~~~~~~~~~~~~~~~
src/oui.c:53:2: warning: Value stored to 'udev' is never read
        udev = udev_unref(udev);
        ^      ~~~~~~~~~~~~~~~~
2 warnings generated.
tools/rfcomm.c:234:3: warning: Value stored to 'i' is never read
                i = execvp(cmdargv[0], cmdargv);
                ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/rfcomm.c:234:7: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                i = execvp(cmdargv[0], cmdargv);
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/rfcomm.c:354:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd'
                if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
                     ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/rfcomm.c:497:14: warning: Assigned value is garbage or undefined
        req.channel = raddr.rc_channel;
                    ^ ~~~~~~~~~~~~~~~~
tools/rfcomm.c:515:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd'
                if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) {
                     ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 warnings generated.
tools/hcidump.c:180:9: warning: Potential leak of memory pointed to by 'dp'
                                if (fds[i].fd == sock)
                                    ^~~
tools/hcidump.c:248:17: warning: Assigned value is garbage or undefined
                                dh->ts_sec  = htobl(frm.ts.tv_sec);
                                            ^ ~~~~~~~~~~~~~~~~~~~~
tools/hcidump.c:326:9: warning: 1st function call argument is an uninitialized value
                                if (be32toh(dp.flags) & 0x02) {
                                    ^~~~~~~~~~~~~~~~~
/usr/include/endian.h:46:22: note: expanded from macro 'be32toh'
#  define be32toh(x) __bswap_32 (x)
                     ^~~~~~~~~~~~~~
tools/hcidump.c:341:20: warning: 1st function call argument is an uninitialized value
                                frm.data_len = be32toh(dp.len);
                                               ^~~~~~~~~~~~~~~
/usr/include/endian.h:46:22: note: expanded from macro 'be32toh'
#  define be32toh(x) __bswap_32 (x)
                     ^~~~~~~~~~~~~~
tools/hcidump.c:346:14: warning: 1st function call argument is an uninitialized value
                                opcode = be32toh(dp.flags) & 0xffff;
                                         ^~~~~~~~~~~~~~~~~
/usr/include/endian.h:46:22: note: expanded from macro 'be32toh'
#  define be32toh(x) __bswap_32 (x)
                     ^~~~~~~~~~~~~~
tools/hcidump.c:384:17: warning: Assigned value is garbage or undefined
                        frm.data_len = btohs(dh.len);
                                     ^ ~~~~~~~~~~~~~
tools/hcidump.c:394:11: warning: Assigned value is garbage or undefined
                frm.len = frm.data_len;
                        ^ ~~~~~~~~~~~~
tools/hcidump.c:398:9: warning: 1st function call argument is an uninitialized value
                        ts = be64toh(ph.ts);
                             ^~~~~~~~~~~~~~
/usr/include/endian.h:51:22: note: expanded from macro 'be64toh'
#  define be64toh(x) __bswap_64 (x)
                     ^~~~~~~~~~~~~~
tools/hcidump.c:403:13: warning: 1st function call argument is an uninitialized value
                        frm.in = be32toh(dp.flags) & 0x01;
                                 ^~~~~~~~~~~~~~~~~
/usr/include/endian.h:46:22: note: expanded from macro 'be32toh'
#  define be32toh(x) __bswap_32 (x)
                     ^~~~~~~~~~~~~~
tools/hcidump.c:408:11: warning: Assigned value is garbage or undefined
                        frm.in = dh.in;
                               ^ ~~~~~
tools/hcidump.c:437:7: warning: Null pointer passed to 1st parameter expecting 'nonnull'
        fd = open(file, open_flags, 0644);
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 warnings generated.
tools/ciptool.c:351:7: warning: 5th function call argument is an uninitialized value
        sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK));
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
src/sdp-xml.c:126:10: warning: Assigned value is garbage or undefined
                buf[1] = data[i + 1];
                       ^ ~~~~~~~~~~~
src/sdp-xml.c:306:11: warning: Assigned value is garbage or undefined
                        buf[1] = data[i + 1];
                               ^ ~~~~~~~~~~~
src/sdp-xml.c:344:11: warning: Assigned value is garbage or undefined
                        buf[1] = data[i + 1];
                               ^ ~~~~~~~~~~~
3 warnings generated.
tools/sdptool.c:941:26: warning: Result of 'malloc' is converted to a pointer of type 'uint32_t', which is incompatible with sizeof operand type 'int'
                        uint32_t *value_int = malloc(sizeof(int));
                        ~~~~~~~~~~            ^~~~~~ ~~~~~~~~~~~
tools/sdptool.c:980:4: warning: 1st function call argument is an uninitialized value
                        free(allocArray[i]);
                        ^~~~~~~~~~~~~~~~~~~
tools/sdptool.c:3777:2: warning: Potential leak of memory pointed to by 'si.name'
        return add_service(0, &si);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~
tools/sdptool.c:4112:4: warning: Potential leak of memory pointed to by 'context.svc'
                        return -1;
                        ^~~~~~~~~
4 warnings generated.
tools/avtest.c:243:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:253:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:262:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~

tools/avtest.c:276:5: warning: Value stored to 'len' is never read
len = write(sk, buf,
^ ~~~~~~~~~~~~~~
tools/avtest.c:283:5: warning: Value stored to 'len' is never read
len = write(sk, buf,
^ ~~~~~~~~~~~~~~
tools/avtest.c:290:5: warning: Value stored to 'len' is never read
len = write(sk, buf,
^ ~~~~~~~~~~~~~~
tools/avtest.c:297:5: warning: Value stored to 'len' is never read
len = write(sk, buf,
^ ~~~~~~~~~~~~~~
tools/avtest.c:309:5: warning: Value stored to 'len' is never read

                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:313:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:322:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:326:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:335:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:342:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:364:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:368:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:377:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 3);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:381:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:394:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 4);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:398:5: warning: Value stored to 'len' is never read
                                len = write(sk, buf, 2);
                                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:405:4: warning: Value stored to 'len' is never read
                        len = write(sk, buf, 2);
                        ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:415:4: warning: Value stored to 'len' is never read
                        len = write(sk, buf, 2);
                        ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:580:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 2);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:588:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, invalid ? 2 : 3);
                ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/avtest.c:602:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 4 + media_transport_size);
                ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/avtest.c:615:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:625:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:637:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:652:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:664:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:673:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 3);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:680:3: warning: Value stored to 'len' is never read
                len = write(sk, buf, 2);
                ^     ~~~~~~~~~~~~~~~~~
tools/avtest.c:716:2: warning: Value stored to 'len' is never read
        len = write(sk, buf, AVCTP_HEADER_LENGTH + sizeof(play_pressed));
        ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 warnings generated.
tools/btproxy.c:836:15: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                        tcp_port = atoi(optarg);
                                   ^~~~~~~~~~~~
tools/btproxy.c:839:8: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                        if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3))
                            ^~~~~~~~~~~~~~
2 warnings generated.
tools/create-image.c:76:3: warning: Value stored to 'fd' is never read
                fd = -1;
                ^    ~~
tools/create-image.c:84:3: warning: Value stored to 'fd' is never read
                fd = -1;
                ^    ~~
tools/create-image.c:92:3: warning: Value stored to 'fd' is never read
                fd = -1;
                ^    ~~
tools/create-image.c:105:2: warning: Value stored to 'fd' is never read
        fd = -1;
        ^    ~~
4 warnings generated.
tools/btgatt-client.c:1822:2: warning: Value stored to 'argv' is never read
        argv += optind;
        ^       ~~~~~~
1 warning generated.
tools/check-selftest.c:42:3: warning: Value stored to 'ptr' is never read
                ptr = fgets(result, sizeof(result), fp);
                ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
tools/btgatt-server.c:1208:2: warning: Value stored to 'argv' is never read
        argv -= optind;
        ^       ~~~~~~
1 warning generated.
tools/gatt-service.c:294:2: warning: 2nd function call argument is an uninitialized value
        chr_write(chr, value, len);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
tools/obex-server-tool.c:133:13: warning: Null pointer passed to 1st parameter expecting 'nonnull'
        data->fd = open(name, O_WRONLY | O_CREAT | O_NOCTTY, 0600);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/obex-server-tool.c:192:13: warning: Null pointer passed to 1st parameter expecting 'nonnull'
        data->fd = open(name, O_RDONLY | O_NOCTTY, 0);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
client/btpclient/btpclientctl.c:402:3: warning: Value stored to 'bit' is never read
                bit = 0;
                ^     ~
client/btpclient/btpclientctl.c:1655:2: warning: Null pointer passed to 2nd parameter expecting 'nonnull'
        memcpy(cp->data, ad_data, ad_len);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
src/sdp-client.c:353:14: warning: Access to field 'cb' results in a dereference of a null pointer
        (*ctxt)->cb = cb;
        ~~~~~~~~~~~~^~~~
1 warning generated.
src/sdpd-request.c:211:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint16_t'
                                pElem = malloc(sizeof(uint16_t));
                                        ^~~~~~ ~~~~~~~~~~~~~~~~
src/sdpd-request.c:239:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint32_t'
                                pElem = malloc(sizeof(uint32_t));
                                        ^~~~~~ ~~~~~~~~~~~~~~~~
2 warnings generated.
src/gatt-database.c:1175:10: warning: Value stored to 'bits' during its initialization is never read
        uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING,
                ^~~~     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
src/gatt-client.c:1569:2: warning: Use of memory after it is freed
        notify_client_unref(client);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
unit/avrcp-lib.c:1968:3: warning: 1st function call argument is an uninitialized value
                g_free(text[i]);
                ^~~~~~~~~~~~~~~
1 warning generated.
unit/avdtp.c:756:25: warning: Use of memory after it is freed
                session->prio_queue = g_slist_remove(session->prio_queue, req);
                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
unit/avdtp.c:763:24: warning: Use of memory after it is freed
                session->req_queue = g_slist_remove(session->req_queue, req);
                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
profiles/audio/avdtp.c:895:25: warning: Use of memory after it is freed
                session->prio_queue = g_slist_remove(session->prio_queue, req);
                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
profiles/audio/avdtp.c:902:24: warning: Use of memory after it is freed
                session->req_queue = g_slist_remove(session->req_queue, req);
                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
profiles/audio/a2dp.c:442:8: warning: Use of memory after it is freed
                if (!cb->resume_cb)
                     ^~~~~~~~~~~~~
profiles/audio/a2dp.c:3354:20: warning: Access to field 'starting' results in a dereference of a null pointer (loaded from variable 'stream')
                stream->starting = TRUE;
                ~~~~~~           ^
profiles/audio/a2dp.c:3357:8: warning: Access to field 'suspending' results in a dereference of a null pointer (loaded from variable 'stream')
                if (!stream->suspending && stream->suspend_timer) {
                     ^~~~~~~~~~~~~~~~~~
profiles/audio/a2dp.c:3417:22: warning: Access to field 'suspending' results in a dereference of a null pointer (loaded from variable 'stream')
                stream->suspending = TRUE;
                ~~~~~~             ^
4 warnings generated.
profiles/audio/avrcp.c:1961:2: warning: Value stored to 'operands' is never read
        operands += sizeof(*pdu);
        ^           ~~~~~~~~~~~~
1 warning generated.
attrib/gatt.c:970:2: warning: Potential leak of memory pointed to by 'long_write'
        return prepare_write(long_write);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
src/sdpd-request.c:211:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint16_t'
                                pElem = malloc(sizeof(uint16_t));
                                        ^~~~~~ ~~~~~~~~~~~~~~~~
src/sdpd-request.c:239:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint32_t'
                                pElem = malloc(sizeof(uint32_t));
                                        ^~~~~~ ~~~~~~~~~~~~~~~~
2 warnings generated.
src/sdp-client.c:353:14: warning: Access to field 'cb' results in a dereference of a null pointer
        (*ctxt)->cb = cb;
        ~~~~~~~~~~~~^~~~
1 warning generated.
src/gatt-database.c:1175:10: warning: Value stored to 'bits' during its initialization is never read
        uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING,
                ^~~~     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
src/sdp-xml.c:126:10: warning: Assigned value is garbage or undefined
                buf[1] = data[i + 1];
                       ^ ~~~~~~~~~~~
src/sdp-xml.c:306:11: warning: Assigned value is garbage or undefined
                        buf[1] = data[i + 1];
                               ^ ~~~~~~~~~~~
src/sdp-xml.c:344:11: warning: Assigned value is garbage or undefined
                        buf[1] = data[i + 1];
                               ^ ~~~~~~~~~~~
3 warnings generated.
src/gatt-client.c:1569:2: warning: Use of memory after it is freed
        notify_client_unref(client);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
gobex/gobex-header.c:95:2: warning: Null pointer passed to 2nd parameter expecting 'nonnull'
        memcpy(to, from, count);
        ^~~~~~~~~~~~~~~~~~~~~~~

1 warning generated.
gobex/gobex-transfer.c:423:7: warning: Use of memory after it is freed
if (!g_slist_find(transfers, transfer))
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
mesh/main.c:162:3: warning: Value stored to 'optarg' is never read

                optarg += strlen("auto");
                ^         ~~~~~~~~~~~~~~
1 warning generated.
lib/bluetooth/hci.c:97:4: warning: Value stored to 'ptr' is never read
                        ptr += sprintf(ptr, "%s", m->str);
                        ^      ~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
client/player.c:2363:8: warning: Null pointer passed to 2nd parameter expecting 'nonnull'
                if (!strcmp(ep->path, pattern))
                     ^~~~~~~~~~~~~~~~~~~~~~~~~
client/player.c:3640:16: warning: Null pointer passed to 1st parameter expecting 'nonnull'
        codec->name = strdup(name);
                      ^~~~~~~~~~~~
2 warnings generated.
gdbus/watch.c:226:3: warning: Attempt to free released memory
                g_free(l->data);
                ^~~~~~~~~~~~~~~
1 warning generated.
lib/bluetooth/sdp.c:509:17: warning: Dereference of undefined pointer value
                uint8_t dtd = *(uint8_t *) dtds[i];
                              ^~~~~~~~~~~~~~~~~~~~
lib/bluetooth/sdp.c:539:17: warning: Dereference of undefined pointer value
                uint8_t dtd = *(uint8_t *) dtds[i];
                              ^~~~~~~~~~~~~~~~~~~~
lib/bluetooth/sdp.c:1885:26: warning: Potential leak of memory pointed to by 'ap'
        for (; pdlist; pdlist = pdlist->next) {
                                ^~~~~~
lib/bluetooth/sdp.c:1899:6: warning: Potential leak of memory pointed to by 'pds'
                ap = sdp_list_append(ap, pds);
                ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
lib/bluetooth/sdp.c:1944:10: warning: Potential leak of memory pointed to by 'u'
                        *seqp = sdp_list_append(*seqp, u);
                        ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/bluetooth/sdp.c:2049:4: warning: Potential leak of memory pointed to by 'lang'
                        sdp_list_free(*langSeq, free);
                        ^~~~~~~~~~~~~
lib/bluetooth/sdp.c:2138:9: warning: Potential leak of memory pointed to by 'profDesc'
        return 0;
               ^
lib/bluetooth/sdp.c:3270:8: warning: Potential leak of memory pointed to by 'pSvcRec'
                pSeq = sdp_list_append(pSeq, pSvcRec);
                ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/bluetooth/sdp.c:3271:9: warning: Potential leak of memory pointed to by 'pSeq'
                pdata += sizeof(uint32_t);
                ~~~~~~^~~~~~~~~~~~~~~~~~~
lib/bluetooth/sdp.c:4607:13: warning: Potential leak of memory pointed to by 'rec_list'
                        } while (scanned < attr_list_len && pdata_len > 0);
                                 ^~~~~~~
lib/bluetooth/sdp.c:4903:40: warning: Potential leak of memory pointed to by 'tseq'
        for (d = sdpdata->val.dataseq; d; d = d->next) {
                                              ^
lib/bluetooth/sdp.c:4939:8: warning: Potential leak of memory pointed to by 'subseq'
                tseq = sdp_list_append(tseq, subseq);
                ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12 warnings generated.
src/shared/gatt-client.c:455:21: warning: Use of memory after it is freed
        gatt_db_unregister(op->client->db, op->db_id);
                           ^~~~~~~~~~
src/shared/gatt-client.c:700:2: warning: Use of memory after it is freed
        discovery_op_complete(op, false, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1000:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1106:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1300:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1365:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1640:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1645:2: warning: Use of memory after it is freed
        discover_all(op);
        ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1701:56: warning: Use of memory after it is freed
        notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
                                          ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2154:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2162:8: warning: Use of memory after it is freed
                                                        discovery_op_ref(op),
                                                        ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3340:2: warning: Use of memory after it is freed
        complete_write_long_op(req, success, 0, false);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3362:2: warning: Use of memory after it is freed
        request_unref(req);
        ^~~~~~~~~~~~~~~~~~
13 warnings generated.
src/shared/bap.c:1529:8: warning: Use of memory after it is freed
        bap = bt_bap_ref_safe(bap);
              ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2340:20: warning: Use of memory after it is freed
        return queue_find(stream->bap->streams, NULL, stream);
                          ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
monitor/l2cap.c:1676:4: warning: Value stored to 'data' is never read
                        data += len;
                        ^       ~~~
monitor/l2cap.c:1677:4: warning: Value stored to 'size' is never read
                        size -= len;
                        ^       ~~~
2 warnings generated.
monitor/hwdb.c:59:2: warning: Value stored to 'hwdb' is never read
        hwdb = udev_hwdb_unref(hwdb);
        ^      ~~~~~~~~~~~~~~~~~~~~~
monitor/hwdb.c:64:2: warning: Value stored to 'udev' is never read
        udev = udev_unref(udev);
        ^      ~~~~~~~~~~~~~~~~
monitor/hwdb.c:106:2: warning: Value stored to 'hwdb' is never read
        hwdb = udev_hwdb_unref(hwdb);
        ^      ~~~~~~~~~~~~~~~~~~~~~
monitor/hwdb.c:111:2: warning: Value stored to 'udev' is never read
        udev = udev_unref(udev);
        ^      ~~~~~~~~~~~~~~~~
4 warnings generated.
tools/bluemoon.c:1102:8: warning: Null pointer passed to 1st parameter expecting 'nonnull'
                        if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3))
                            ^~~~~~~~~~~~~~
1 warning generated.
tools/meshctl.c:326:19: warning: Access to field 'mesh_devices' results in a dereference of a null pointer (loaded from variable 'default_ctrl')
        g_list_free_full(default_ctrl->mesh_devices, g_free);
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~
tools/meshctl.c:762:2: warning: 2nd function call argument is an uninitialized value
        bt_shell_printf("Attempting to disconnect from %s\n", addr);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/meshctl.c:1957:2: warning: Value stored to 'len' is never read
        len = len + extra + strlen("local_node.json");
        ^     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 warnings generated.
In file included from tools/mesh-gatt/crypto.c:32:
./src/shared/util.h:244:9: warning: 1st function call argument is an uninitialized value
        return be32_to_cpu(get_unaligned((const uint32_t *) ptr));
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./src/shared/util.h:33:26: note: expanded from macro 'be32_to_cpu'
#define be32_to_cpu(val) bswap_32(val)
                         ^~~~~~~~~~~~~
/usr/include/byteswap.h:34:21: note: expanded from macro 'bswap_32'
#define bswap_32(x) __bswap_32 (x)
                    ^~~~~~~~~~~~~~
In file included from tools/mesh-gatt/crypto.c:32:
./src/shared/util.h:254:9: warning: 1st function call argument is an uninitialized value
        return be64_to_cpu(get_unaligned((const uint64_t *) ptr));
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./src/shared/util.h:34:26: note: expanded from macro 'be64_to_cpu'
#define be64_to_cpu(val) bswap_64(val)
                         ^~~~~~~~~~~~~
/usr/include/byteswap.h:37:21: note: expanded from macro 'bswap_64'
#define bswap_64(x) __bswap_64 (x)
                    ^~~~~~~~~~~~~~
2 warnings generated.
ell/util.c:853:8: warning: The left operand of '>' is a garbage value
        if (x > UINT8_MAX)
            ~ ^
ell/util.c:871:8: warning: The left operand of '>' is a garbage value
        if (x > UINT16_MAX)
            ~ ^
2 warnings generated.
ell/pem.c:131:8: warning: Dereference of null pointer (loaded from variable 'eol')
                        if (*eol == '\r' || *eol == '\n')
                            ^~~~
ell/pem.c:166:18: warning: Dereference of null pointer (loaded from variable 'eol')
                if (buf_len && *eol == '\r' && *buf_ptr == '\n') {
                               ^~~~
ell/pem.c:166:34: warning: Dereference of null pointer (loaded from variable 'buf_ptr')
                if (buf_len && *eol == '\r' && *buf_ptr == '\n') {
                                               ^~~~~~~~

ell/pem.c:304:11: warning: 1st function call argument is an uninitialized value
result = pem_load_buffer(file.data, file.st.st_size,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ell/pem.c:469:9: warning: 1st function call argument is an uninitialized value
list = l_pem_load_certificate_list_from_data(file.data,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 warnings generated.
ell/cert.c:645:41: warning: Access to field 'asn1_len' results in a dereference of a null pointer (loaded from variable 'cert')

        key = l_key_new(L_KEY_RSA, cert->asn1, cert->asn1_len);
                                               ^~~~~~~~~~~~~~

1 warning generated.
ell/gvariant-util.c:143:18: warning: The left operand of '>' is a garbage value
if (alignment > max_alignment)
~~~~~~~~~ ^
ell/gvariant-util.c:456:5: warning: Dereference of null pointer

                        !children[0].fixed_size) {
                         ^~~~~~~~~~~~~~~~~~~~~~
2 warnings generated.
ell/ecc-external.c:77:11: warning: Assigned value is garbage or undefined
                dest[i] = src[i];
                        ^ ~~~~~~
ell/ecc-external.c:160:18: warning: The right operand of '-' is a garbage value
                diff = left[i] - right[i] - borrow;
                               ^ ~~~~~~~~
ell/ecc-external.c:227:14: warning: 2nd function call argument is an uninitialized value
                        product = mul_64_64(left[i], right[k - i]);
                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ell/ecc-external.c:408:9: warning: Assigned value is garbage or undefined
        tmp[1] = product[3];
               ^ ~~~~~~~~~~
ell/ecc-external.c:435:22: warning: The left operand of '&' is a garbage value
        tmp[1] = product[3] & 0xffffffff00000000ull;
                 ~~~~~~~~~~ ^
ell/ecc-external.c:483:22: warning: The left operand of '&' is a garbage value
        tmp[1] = product[5] & 0xffffffff00000000ull;
                 ~~~~~~~~~~ ^
ell/ecc-external.c:688:28: warning: The left operand of '>>' is a garbage value
                tmp[i] = (product[8 + i] >> 9) | (product[9 + i] << 55);
                          ~~~~~~~~~~~~~~ ^
7 warnings generated.
In file included from tools/parser/l2cap.c:24:
tools/parser/parser.h:121:16: warning: Dereference of null pointer
                                time_t t = f->ts.tv_sec;
                                           ^~~~~~~~~~~~
tools/parser/parser.h:129:18: warning: Dereference of null pointer
                                                (long long)f->ts.tv_sec,
                                                           ^~~~~~~~~~~~
/usr/include/x86_64-linux-gnu/bits/stdio2.h:111:42: note: expanded from macro 'printf'
  __printf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
                                         ^~~~~~~~~~~
In file included from tools/parser/l2cap.c:24:
tools/parser/parser.h:132:18: warning: Access to field 'in' results in a dereference of a null pointer (loaded from variable 'f')
                printf("%c ", (f->in ? '>' : '<'));
                               ^~~~~
/usr/include/x86_64-linux-gnu/bits/stdio2.h:111:42: note: expanded from macro 'printf'
  __printf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
                                         ^~~~~~~~~~~
3 warnings generated.
In file included from tools/parser/sdp.c:24:
tools/parser/parser.h:121:16: warning: Dereference of null pointer
                                time_t t = f->ts.tv_sec;
                                           ^~~~~~~~~~~~
tools/parser/parser.h:129:18: warning: Dereference of null pointer
                                                (long long)f->ts.tv_sec,
                                                           ^~~~~~~~~~~~
/usr/include/x86_64-linux-gnu/bits/stdio2.h:111:42: note: expanded from macro 'printf'
  __printf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
                                         ^~~~~~~~~~~
In file included from tools/parser/sdp.c:24:
tools/parser/parser.h:132:18: warning: Access to field 'in' results in a dereference of a null pointer (loaded from variable 'f')
                printf("%c ", (f->in ? '>' : '<'));
                               ^~~~~
/usr/include/x86_64-linux-gnu/bits/stdio2.h:111:42: note: expanded from macro 'printf'
  __printf_chk (__USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
                                         ^~~~~~~~~~~
3 warnings generated.
In file included from tools/parser/ppp.c:22:
tools/parser/parser.h:159:2: warning: Undefined or garbage value returned to caller
        return *u8_ptr;
        ^~~~~~~~~~~~~~
tools/parser/ppp.c:108:30: warning: The left operand of '&' is a garbage value
        if (*((uint8_t *) frm->ptr) & 0x80)
            ~~~~~~~~~~~~~~~~~~~~~~~ ^
2 warnings generated.
emulator/serial.c:150:2: warning: Assigned value is garbage or undefined
        enum btdev_type uninitialized_var(type);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
emulator/serial.c:150:36: warning: Value stored to 'type' during its initialization is never read
        enum btdev_type uninitialized_var(type);
                                          ^~~~
emulator/serial.c:36:30: note: expanded from macro 'uninitialized_var'
#define uninitialized_var(x) x = x
                             ^   ~
emulator/serial.c:213:2: warning: Assigned value is garbage or undefined
        enum btdev_type uninitialized_var(dev_type);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
emulator/serial.c:213:36: warning: Value stored to 'dev_type' during its initialization is never read
        enum btdev_type uninitialized_var(dev_type);
                                          ^~~~~~~~
emulator/serial.c:36:30: note: expanded from macro 'uninitialized_var'
#define uninitialized_var(x) x = x
                             ^   ~
4 warnings generated.
emulator/server.c:218:2: warning: Assigned value is garbage or undefined
        enum btdev_type uninitialized_var(type);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
emulator/server.c:218:36: warning: Value stored to 'type' during its initialization is never read
        enum btdev_type uninitialized_var(type);
                                          ^~~~
emulator/server.c:36:30: note: expanded from macro 'uninitialized_var'
#define uninitialized_var(x) x = x
                             ^   ~
2 warnings generated.
emulator/b1ee.c:258:3: warning: Potential leak of memory pointed to by 'server_port'
                int opt;
                ^~~~~~~
emulator/b1ee.c:258:3: warning: Potential leak of memory pointed to by 'sniffer_port'
                int opt;
                ^~~~~~~
emulator/b1ee.c:289:2: warning: Value stored to 'argc' is never read
        argc = argc - optind;
        ^      ~~~~~~~~~~~~~

3 warnings generated.
/usr/bin/ld: tools/test-runner.o: in function `start_controller_forward':
/github/workspace/src/src/tools/test-runner.c:989: undefined reference to `openpty'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:6194: tools/test-runner] Error 1
make[1]: *** Waiting for unfinished jobs....
emulator/btdev.c:6620:20: warning: Access to field 'link' results in a dereference of a null pointer (loaded from variable 'acl')

                le_past_received(acl->link, pa);
                                 ^~~~~~~~~
emulator/btdev.c:6720:25: warning: Access to field 'link' results in a dereference of a null pointer (loaded from variable 'acl')
                le_past_info_received(acl->link, ea);
                                      ^~~~~~~~~

2 warnings generated.
make: *** [Makefile:4224: all] Error 2

---
Regards,
Linux Bluetooth

#3 lore raw

PATCH 2/11 emulator: btvirt: allow specifying where server unix sockets are made

In reply to 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(-)
emulator/main.c +24 -13
index 456fcd98e..09d6e9adb 100644
@@ -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");
 	}
Signature · 1 line
2.53.0
#4 lore raw

PATCH 3/11 test-runner: enable path argument for --unix

In reply to 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(-)
tools/test-runner.c +8 -6
index 48b7c1589..331cb6eb1 100644
@@ -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;
Signature · 1 line
2.53.0
#5 lore raw

PATCH 4/11 test-runner: Add -o/--option option

In reply to Pauli Virtanen

Allow passing arbitrary arguments to QEMU.

---
 tools/test-runner.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)
tools/test-runner.c +19 -3
index 331cb6eb1..3de3a9d74 100644
@@ -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;
Signature · 1 line
2.53.0
#6 lore raw

PATCH 5/11 test-runner: allow source tree root for -k

In reply to 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(-)
tools/test-runner.c +28 -14
index 3de3a9d74..b3e0b0cfe 100644
@@ -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);
 
Signature · 1 line
2.53.0
#7 lore raw

PATCH 6/11 doc: enable CONFIG_VIRTIO_CONSOLE in tester config

In reply to 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(+)
doc/ci.config +1 -0
index 31e49ba96..a48c1af9d 100644
@@ -6,6 +6,7 @@
 
 CONFIG_VIRTIO=y
 CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_CONSOLE=y
 
 CONFIG_NET=y
 CONFIG_INET=y
doc/test-runner.rst +1 -0
index 64715e2e7..d030787a4 100644
@@ -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
doc/tester.config +1 -0
index 4ee306405..015e7cc1a 100644
@@ -1,6 +1,7 @@
 CONFIG_PCI=y
 CONFIG_VIRTIO=y
 CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_CONSOLE=y
 
 CONFIG_NET=y
 CONFIG_INET=y
Signature · 1 line
2.53.0
#8 lore raw

PATCH 7/11 test-runner: use virtio-serial for implementing -u device forwarding

In reply to 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.

Since it's not a TTY device, we have to do vport <-> tty-with-hci-ldisc forwarding of the data in test-runner, so this becomes a bit more involved.

---
 tools/test-runner.c | 300 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 230 insertions(+), 70 deletions(-)
tools/test-runner.c +230 -70
index b3e0b0cfe..576313b79 100644
@@ -24,6 +24,9 @@
 #include <getopt.h>
 #include <poll.h>
 #include <limits.h>
+#include <dirent.h>
+#include <pty.h>
+#include <stdint.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -306,7 +309,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 +333,17 @@ static void start_qemu(void)
 	argv[pos++] = "-append";
 	argv[pos++] = (char *) cmdline;
 
+	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, "virtserialport,chardev=bt%d,name=bt.%d", i, i);
 
 		argv[pos++] = "-chardev";
 		argv[pos++] = chrdev;
@@ -360,65 +366,12 @@ static void start_qemu(void)
 	execve(argv[0], argv, qemu_envp);
 }
 
-static int open_serial(const char *path)
-{
-	struct termios ti;
-	int fd, saved_ldisc, ldisc = N_HCI;
-
-	fd = open(path, O_RDWR | O_NOCTTY);
-	if (fd < 0) {
-		perror("Failed to open serial port");
-		return -1;
-	}
-
-	if (tcflush(fd, TCIOFLUSH) < 0) {
-		perror("Failed to flush serial port");
-		close(fd);
-		return -1;
-	}
-
-	if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
-		perror("Failed get serial line discipline");
-		close(fd);
-		return -1;
-	}
-
-	/* Switch TTY to raw mode */
-	memset(&ti, 0, sizeof(ti));
-	cfmakeraw(&ti);
-
-	ti.c_cflag |= (B115200 | CLOCAL | CREAD);
-
-	/* Set flow control */
-	ti.c_cflag |= CRTSCTS;
-
-	if (tcsetattr(fd, TCSANOW, &ti) < 0) {
-		perror("Failed to set serial port settings");
-		close(fd);
-		return -1;
-	}
-
-	if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
-		perror("Failed set serial line discipline");
-		close(fd);
-		return -1;
-	}
-
-	printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc);
-
-	return fd;
-}
-
-static int attach_proto(const char *path, unsigned int proto,
+static int attach_proto(int fd, unsigned int proto,
 					unsigned int mandatory_flags,
 					unsigned int optional_flags)
 {
 	unsigned int flags = mandatory_flags | optional_flags;
-	int fd, dev_id;
-
-	fd = open_serial(path);
-	if (fd < 0)
-		return -1;
+	int dev_id;
 
 	if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) {
 		if (errno == EINVAL) {
@@ -895,13 +848,222 @@ static int start_audio_server(pid_t pids[2])
 	return 0;
 }
 
+static bool find_attach_dev(char path[PATH_MAX])
+{
+	const char *vport_path = "/sys/class/virtio-ports";
+	struct dirent *entry;
+	DIR *dir;
+
+	dir = opendir(vport_path);
+	if (!dir)
+		return false;
+
+	while ((entry = readdir(dir)) != NULL) {
+		FILE *f;
+		char buf[64];
+		size_t size;
+
+		snprintf(path, PATH_MAX, "%s/%s/name", vport_path,
+								entry->d_name);
+		f = fopen(path, "r");
+		if (!f)
+			continue;
+
+		size = fread(buf, 1, sizeof(buf) - 1, f);
+		buf[size] = 0;
+
+		fclose(f);
+
+		if (strncmp(buf, "bt.", 3) == 0) {
+			snprintf(path, PATH_MAX, "/dev/%s", entry->d_name);
+			closedir(dir);
+			return true;
+		}
+	}
+
+	closedir(dir);
+	return false;
+}
+
+static void copy_fd_bidi(int src, int dst)
+{
+	fd_set rfds, wfds;
+	int fd[2] = { src, dst };
+	uint8_t buf[2][4096];
+	size_t size[2] = { 0, 0 };
+	size_t pos[2] = { 0, 0 };
+	int i, ret;
+
+	/* Simple copying of data src <-> dst to both directions */
+
+	for (i = 0; i < 2; ++i) {
+		int flags = fcntl(fd[i], F_GETFL);
+
+		if (fcntl(fd[i], F_SETFL, flags | O_NONBLOCK) < 0) {
+			perror("fcntl");
+			goto error;
+		}
+	}
+
+	while (1) {
+		FD_ZERO(&rfds);
+		FD_ZERO(&wfds);
+
+		for (i = 0; i < 2; ++i) {
+			if (size[i])
+				FD_SET(fd[i], &wfds);
+			else
+				FD_SET(fd[1 - i], &rfds);
+		}
+
+		ret = select(FD_SETSIZE, &rfds, &wfds, NULL, NULL);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			perror("select");
+			goto error;
+		}
+
+		for (i = 0; i < 2; ++i) {
+			ssize_t s;
+
+			if (!size[i] && FD_ISSET(fd[1 - i], &rfds)) {
+				s = read(fd[1 - i], buf[i], sizeof(buf[i]));
+				if (s >= 0) {
+					size[i] = s;
+					pos[i] = 0;
+				} else if (errno == EINTR) {
+					/* ok */
+				} else {
+					perror("read");
+					goto error;
+				}
+
+			}
+
+			if (size[i]) {
+				s = write(fd[i], buf[i] + pos[i], size[i]);
+				if (s >= 0) {
+					size[i] -= s;
+					pos[i] += s;
+				} else if (errno == EINTR || errno == EAGAIN
+						|| errno == EWOULDBLOCK) {
+					/* ok */
+				} else {
+					perror("write");
+					goto error;
+				}
+			}
+		}
+	}
+	return;
+
+error:
+	fprintf(stderr, "Bluetooth controller forward terminated with error\n");
+	exit(1);
+}
+
+static int start_controller_forward(const char *path, pid_t *controller_pid)
+{
+	struct termios ti;
+	pid_t pid;
+	int src = -1, dst = -1, fd = -1;
+	int ret, saved_ldisc, ldisc = N_HCI;
+
+	/* virtio-serial ports cannot be used for HCI line disciple, so
+	 * openpty() serial device and forward data to/from it.
+	 */
+
+	src = open(path, O_RDWR);
+	if (src < 0)
+		goto error;
+
+	/* Raw mode TTY */
+	memset(&ti, 0, sizeof(ti));
+	cfmakeraw(&ti);
+	ti.c_cflag |= B115200 | CLOCAL | CREAD;
+
+	/* With flow control */
+	ti.c_cflag |= CRTSCTS;
+
+	ret = openpty(&dst, &fd, NULL, &ti, NULL);
+	if (ret < 0)
+		goto error;
+
+	if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
+		perror("Failed get serial line discipline");
+		goto error;
+	}
+
+	if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
+		perror("Failed set serial line discipline");
+		goto error;
+	}
+
+	printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc);
+
+	pid = fork();
+	if (pid < 0) {
+		perror("Failed to fork new process");
+		goto error;
+	} else if (pid == 0) {
+		close(fd);
+		copy_fd_bidi(src, dst);
+		exit(0);
+	}
+
+	*controller_pid = pid;
+
+	close(src);
+	close(dst);
+	return fd;
+
+error:
+	if (src >= 0)
+		close(src);
+	if (dst >= 0)
+		close(dst);
+	if (fd >= 0)
+		close(fd);
+	return -1;
+}
+
+static int attach_controller(pid_t *controller_pid)
+{
+	unsigned int basic_flags, extra_flags;
+	char path[PATH_MAX];
+	int fd;
+
+	*controller_pid = -1;
+
+	if (!find_attach_dev(path)) {
+		printf("Failed to find Bluetooth controller virtio\n");
+		return -1;
+	}
+
+	printf("Forwarding Bluetooth controller from %s\n", path);
+
+	fd = start_controller_forward(path, controller_pid);
+	if (fd < 0) {
+		printf("Failed to forward Bluetooth controller\n");
+		return -1;
+	}
+
+	basic_flags = (1 << HCI_UART_RESET_ON_INIT);
+	extra_flags = (1 << HCI_UART_VND_DETECT);
+
+	printf("Attaching Bluetooth controller\n");
+
+	return attach_proto(fd, HCI_UART_H4, basic_flags, extra_flags);
+}
+
 static void run_command(char *cmdname, char *home)
 {
 	char *argv[9], *envp[3];
 	int pos = 0, idx = 0;
 	int serial_fd;
 	pid_t pid, dbus_pid, daemon_pid, monitor_pid, emulator_pid,
-	      dbus_session_pid, audio_pid[2];
+		dbus_session_pid, audio_pid[2], controller_pid;
 	int i;
 
 	if (!home) {
@@ -910,18 +1072,11 @@ static void run_command(char *cmdname, char *home)
 	}
 
 	if (num_devs) {
-		const char *node = "/dev/ttyS1";
-		unsigned int basic_flags, extra_flags;
-
-		printf("Attaching BR/EDR controller to %s\n", node);
-
-		basic_flags = (1 << HCI_UART_RESET_ON_INIT);
-		extra_flags = (1 << HCI_UART_VND_DETECT);
-
-		serial_fd = attach_proto(node, HCI_UART_H4, basic_flags,
-								extra_flags);
-	} else
+		serial_fd = attach_controller(&controller_pid);
+	} else {
 		serial_fd = -1;
+		controller_pid = -1;
+	}
 
 	if (start_dbus) {
 		create_dbus_system_conf();
@@ -1063,6 +1218,11 @@ start_next:
 			monitor_pid = -1;
 		}
 
+		if (corpse == controller_pid) {
+			printf("Controller terminated\n");
+			controller_pid = -1;
+		}
+
 		for (i = 0; i < 2; ++i) {
 			if (corpse == audio_pid[i]) {
 				printf("Audio server %d terminated\n", i);
Signature · 1 line
2.53.0
#9 lore raw

Re: [PATCH BlueZ 07/11] test-runner: use virtio-serial for implementing -u device forwarding

Hi Pauli,

Quoted 8 lines · 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

Interesting, so this comes down the pci-serial being unreliable? There have been occurrences of this even without attaching any controller with the VM, LL Privacy tests in mgmt-tester do occasionally timeout like above, but perhaps that is a different issue.

Quoted 5 lines

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.

Quoted 7 lines

Change test-runner to use virtserialport to forward the btvirt connection inside the VM, as virtio-serial doesn't appear to have these problems.

Since it's not a TTY device, we have to do vport <-> tty-with-hci-ldisc forwarding of the data in test-runner, so this becomes a bit more involved.

I wonder if we could just use virtio_bt
(https://github.com/torvalds/linux/blob/master/drivers/bluetooth/virtio_bt.c)
here, instead of using a serial, so we don't have to copy the data both ways? Or perhaps the problem is that this was never completed upstream @Marcel Holtmann?

Quoted 378 lines
---
 tools/test-runner.c | 300 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 230 insertions(+), 70 deletions(-)
tools/test-runner.c +230 -71
index b3e0b0cfe..576313b79 100644
@@ -24,6 +24,9 @@
 #include <getopt.h>
 #include <poll.h>
 #include <limits.h>
+#include <dirent.h>
+#include <pty.h>
+#include <stdint.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -306,7 +309,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 +333,17 @@ static void start_qemu(void)
        argv[pos++] = "-append";
        argv[pos++] = (char *) cmdline;
 
+       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, "virtserialport,chardev=bt%d,name=bt.%d", i, i);
 
                argv[pos++] = "-chardev";
                argv[pos++] = chrdev;
@@ -360,65 +366,12 @@ static void start_qemu(void)
        execve(argv[0], argv, qemu_envp);
 }
 
-static int open_serial(const char *path)
-{
-       struct termios ti;
-       int fd, saved_ldisc, ldisc = N_HCI;
-
-       fd = open(path, O_RDWR | O_NOCTTY);
-       if (fd < 0) {
-               perror("Failed to open serial port");
-               return -1;
-       }
-
-       if (tcflush(fd, TCIOFLUSH) < 0) {
-               perror("Failed to flush serial port");
-               close(fd);
-               return -1;
-       }
-
-       if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
-               perror("Failed get serial line discipline");
-               close(fd);
-               return -1;
-       }
-
-       /* Switch TTY to raw mode */
-       memset(&ti, 0, sizeof(ti));
-       cfmakeraw(&ti);
-
-       ti.c_cflag |= (B115200 | CLOCAL | CREAD);
-
-       /* Set flow control */
-       ti.c_cflag |= CRTSCTS;
-
-       if (tcsetattr(fd, TCSANOW, &ti) < 0) {
-               perror("Failed to set serial port settings");
-               close(fd);
-               return -1;
-       }
-
-       if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
-               perror("Failed set serial line discipline");
-               close(fd);
-               return -1;
-       }
-
-       printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc);
-
-       return fd;
-}
-
-static int attach_proto(const char *path, unsigned int proto,
+static int attach_proto(int fd, unsigned int proto,
                                        unsigned int mandatory_flags,
                                        unsigned int optional_flags)
 {
        unsigned int flags = mandatory_flags | optional_flags;
-       int fd, dev_id;
-
-       fd = open_serial(path);
-       if (fd < 0)
-               return -1;
+       int dev_id;
 
        if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) {
                if (errno == EINVAL) {
@@ -895,13 +848,222 @@ static int start_audio_server(pid_t pids[2])
        return 0;
 }
 
+static bool find_attach_dev(char path[PATH_MAX])
+{
+       const char *vport_path = "/sys/class/virtio-ports";
+       struct dirent *entry;
+       DIR *dir;
+
+       dir = opendir(vport_path);
+       if (!dir)
+               return false;
+
+       while ((entry = readdir(dir)) != NULL) {
+               FILE *f;
+               char buf[64];
+               size_t size;
+
+               snprintf(path, PATH_MAX, "%s/%s/name", vport_path,
+                                                               entry->d_name);
+               f = fopen(path, "r");
+               if (!f)
+                       continue;
+
+               size = fread(buf, 1, sizeof(buf) - 1, f);
+               buf[size] = 0;
+
+               fclose(f);
+
+               if (strncmp(buf, "bt.", 3) == 0) {
+                       snprintf(path, PATH_MAX, "/dev/%s", entry->d_name);
+                       closedir(dir);
+                       return true;
+               }
+       }
+
+       closedir(dir);
+       return false;
+}
+
+static void copy_fd_bidi(int src, int dst)
+{
+       fd_set rfds, wfds;
+       int fd[2] = { src, dst };
+       uint8_t buf[2][4096];
+       size_t size[2] = { 0, 0 };
+       size_t pos[2] = { 0, 0 };
+       int i, ret;
+
+       /* Simple copying of data src <-> dst to both directions */
+
+       for (i = 0; i < 2; ++i) {
+               int flags = fcntl(fd[i], F_GETFL);
+
+               if (fcntl(fd[i], F_SETFL, flags | O_NONBLOCK) < 0) {
+                       perror("fcntl");
+                       goto error;
+               }
+       }
+
+       while (1) {
+               FD_ZERO(&rfds);
+               FD_ZERO(&wfds);
+
+               for (i = 0; i < 2; ++i) {
+                       if (size[i])
+                               FD_SET(fd[i], &wfds);
+                       else
+                               FD_SET(fd[1 - i], &rfds);
+               }
+
+               ret = select(FD_SETSIZE, &rfds, &wfds, NULL, NULL);
+               if (ret < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       perror("select");
+                       goto error;
+               }
+
+               for (i = 0; i < 2; ++i) {
+                       ssize_t s;
+
+                       if (!size[i] && FD_ISSET(fd[1 - i], &rfds)) {
+                               s = read(fd[1 - i], buf[i], sizeof(buf[i]));
+                               if (s >= 0) {
+                                       size[i] = s;
+                                       pos[i] = 0;
+                               } else if (errno == EINTR) {
+                                       /* ok */
+                               } else {
+                                       perror("read");
+                                       goto error;
+                               }
+
+                       }
+
+                       if (size[i]) {
+                               s = write(fd[i], buf[i] + pos[i], size[i]);
+                               if (s >= 0) {
+                                       size[i] -= s;
+                                       pos[i] += s;
+                               } else if (errno == EINTR || errno == EAGAIN
+                                               || errno == EWOULDBLOCK) {
+                                       /* ok */
+                               } else {
+                                       perror("write");
+                                       goto error;
+                               }
+                       }
+               }
+       }
+       return;
+
+error:
+       fprintf(stderr, "Bluetooth controller forward terminated with error\n");
+       exit(1);
+}
+
+static int start_controller_forward(const char *path, pid_t *controller_pid)
+{
+       struct termios ti;
+       pid_t pid;
+       int src = -1, dst = -1, fd = -1;
+       int ret, saved_ldisc, ldisc = N_HCI;
+
+       /* virtio-serial ports cannot be used for HCI line disciple, so
+        * openpty() serial device and forward data to/from it.
+        */
+
+       src = open(path, O_RDWR);
+       if (src < 0)
+               goto error;
+
+       /* Raw mode TTY */
+       memset(&ti, 0, sizeof(ti));
+       cfmakeraw(&ti);
+       ti.c_cflag |= B115200 | CLOCAL | CREAD;
+
+       /* With flow control */
+       ti.c_cflag |= CRTSCTS;
+
+       ret = openpty(&dst, &fd, NULL, &ti, NULL);
+       if (ret < 0)
+               goto error;
+
+       if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
+               perror("Failed get serial line discipline");
+               goto error;
+       }
+
+       if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
+               perror("Failed set serial line discipline");
+               goto error;
+       }
+
+       printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc);
+
+       pid = fork();
+       if (pid < 0) {
+               perror("Failed to fork new process");
+               goto error;
+       } else if (pid == 0) {
+               close(fd);
+               copy_fd_bidi(src, dst);
+               exit(0);
+       }
+
+       *controller_pid = pid;
+
+       close(src);
+       close(dst);
+       return fd;
+
+error:
+       if (src >= 0)
+               close(src);
+       if (dst >= 0)
+               close(dst);
+       if (fd >= 0)
+               close(fd);
+       return -1;
+}
+
+static int attach_controller(pid_t *controller_pid)
+{
+       unsigned int basic_flags, extra_flags;
+       char path[PATH_MAX];
+       int fd;
+
+       *controller_pid = -1;
+
+       if (!find_attach_dev(path)) {
+               printf("Failed to find Bluetooth controller virtio\n");
+               return -1;
+       }
+
+       printf("Forwarding Bluetooth controller from %s\n", path);
+
+       fd = start_controller_forward(path, controller_pid);
+       if (fd < 0) {
+               printf("Failed to forward Bluetooth controller\n");
+               return -1;
+       }
+
+       basic_flags = (1 << HCI_UART_RESET_ON_INIT);
+       extra_flags = (1 << HCI_UART_VND_DETECT);
+
+       printf("Attaching Bluetooth controller\n");
+
+       return attach_proto(fd, HCI_UART_H4, basic_flags, extra_flags);
+}
+
 static void run_command(char *cmdname, char *home)
 {
        char *argv[9], *envp[3];
        int pos = 0, idx = 0;
        int serial_fd;
        pid_t pid, dbus_pid, daemon_pid, monitor_pid, emulator_pid,
-             dbus_session_pid, audio_pid[2];
+               dbus_session_pid, audio_pid[2], controller_pid;
        int i;
 
        if (!home) {
@@ -910,18 +1072,11 @@ static void run_command(char *cmdname, char *home)
        }
 
        if (num_devs) {
-               const char *node = "/dev/ttyS1";
-               unsigned int basic_flags, extra_flags;
-
-               printf("Attaching BR/EDR controller to %s\n", node);
-
-               basic_flags = (1 << HCI_UART_RESET_ON_INIT);
-               extra_flags = (1 << HCI_UART_VND_DETECT);
-
-               serial_fd = attach_proto(node, HCI_UART_H4, basic_flags,
-                                                               extra_flags);
-       } else
+               serial_fd = attach_controller(&controller_pid);
+       } else {
                serial_fd = -1;
+               controller_pid = -1;
+       }
 
        if (start_dbus) {
                create_dbus_system_conf();
@@ -1063,6 +1218,11 @@ start_next:
                        monitor_pid = -1;
                }
 
+               if (corpse == controller_pid) {
+                       printf("Controller terminated\n");
+                       controller_pid = -1;
+               }
+
                for (i = 0; i < 2; ++i) {
                        if (corpse == audio_pid[i]) {
                                printf("Audio server %d terminated\n", i);
--

2.53.0

Signature · 1 line
Luiz Augusto von Dentz
#10 lore raw

Re: [PATCH BlueZ 07/11] test-runner: use virtio-serial for implementing -u device forwarding

Hi,

ma, 2026-03-02 kello 16:03 -0500, Luiz Augusto von Dentz kirjoitti:

Quoted 11 lines · Pauli Virtanen
Quoted 8 lines

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

Interesting, so this comes down the pci-serial being unreliable?

The sporadic corruptions are not specific to btvirt (occur also with the test rpc channel here if setup via serial chardev), are not present with virtio-serial, and appear only when the parent machine is under load. I did not find the root cause for this.

It sounds strange to me, I'd expect qemu serial be heavily used.

It could be test-runner.c is setting up the serial channel wrong for the -u option, but I did not find if it could be fixed.

Quoted 4 lines

There
have been occurrences of this even without attaching any controller with the VM, LL Privacy tests in mgmt-tester do occasionally timeout like above, but perhaps that is a different issue.

mgmt-tester btdev connects via vhci and runs inside the vm, so this sounds like different issue.

Quoted 13 lines
Quoted 5 lines

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.

Quoted 7 lines

Change test-runner to use virtserialport to forward the btvirt connection inside the VM, as virtio-serial doesn't appear to have these problems.

Since it's not a TTY device, we have to do vport <-> tty-with-hci-ldisc forwarding of the data in test-runner, so this becomes a bit more involved.

Quoted 5 lines

I wonder if we could just use virtio_bt
(https://github.com/torvalds/linux/blob/master/drivers/bluetooth/virtio_bt.c)
here, instead of using a serial, so we don't have to copy the data both ways? Or perhaps the problem is that this was never completed upstream @Marcel Holtmann?

At first sight it looks like Qemu does not support virtio-bluetooth.

Quoted 379 lines
Quoted 378 lines
---
 tools/test-runner.c | 300 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 230 insertions(+), 70 deletions(-)
tools/test-runner.c +230 -71
index b3e0b0cfe..576313b79 100644
@@ -24,6 +24,9 @@
 #include <getopt.h>
 #include <poll.h>
 #include <limits.h>
+#include <dirent.h>
+#include <pty.h>
+#include <stdint.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -306,7 +309,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 +333,17 @@ static void start_qemu(void)
        argv[pos++] = "-append";
        argv[pos++] = (char *) cmdline;
 
+       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, "virtserialport,chardev=bt%d,name=bt.%d", i, i);
 
                argv[pos++] = "-chardev";
                argv[pos++] = chrdev;
@@ -360,65 +366,12 @@ static void start_qemu(void)
        execve(argv[0], argv, qemu_envp);
 }
 
-static int open_serial(const char *path)
-{
-       struct termios ti;
-       int fd, saved_ldisc, ldisc = N_HCI;
-
-       fd = open(path, O_RDWR | O_NOCTTY);
-       if (fd < 0) {
-               perror("Failed to open serial port");
-               return -1;
-       }
-
-       if (tcflush(fd, TCIOFLUSH) < 0) {
-               perror("Failed to flush serial port");
-               close(fd);
-               return -1;
-       }
-
-       if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
-               perror("Failed get serial line discipline");
-               close(fd);
-               return -1;
-       }
-
-       /* Switch TTY to raw mode */
-       memset(&ti, 0, sizeof(ti));
-       cfmakeraw(&ti);
-
-       ti.c_cflag |= (B115200 | CLOCAL | CREAD);
-
-       /* Set flow control */
-       ti.c_cflag |= CRTSCTS;
-
-       if (tcsetattr(fd, TCSANOW, &ti) < 0) {
-               perror("Failed to set serial port settings");
-               close(fd);
-               return -1;
-       }
-
-       if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
-               perror("Failed set serial line discipline");
-               close(fd);
-               return -1;
-       }
-
-       printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc);
-
-       return fd;
-}
-
-static int attach_proto(const char *path, unsigned int proto,
+static int attach_proto(int fd, unsigned int proto,
                                        unsigned int mandatory_flags,
                                        unsigned int optional_flags)
 {
        unsigned int flags = mandatory_flags | optional_flags;
-       int fd, dev_id;
-
-       fd = open_serial(path);
-       if (fd < 0)
-               return -1;
+       int dev_id;
 
        if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) {
                if (errno == EINVAL) {
@@ -895,13 +848,222 @@ static int start_audio_server(pid_t pids[2])
        return 0;
 }
 
+static bool find_attach_dev(char path[PATH_MAX])
+{
+       const char *vport_path = "/sys/class/virtio-ports";
+       struct dirent *entry;
+       DIR *dir;
+
+       dir = opendir(vport_path);
+       if (!dir)
+               return false;
+
+       while ((entry = readdir(dir)) != NULL) {
+               FILE *f;
+               char buf[64];
+               size_t size;
+
+               snprintf(path, PATH_MAX, "%s/%s/name", vport_path,
+                                                               entry->d_name);
+               f = fopen(path, "r");
+               if (!f)
+                       continue;
+
+               size = fread(buf, 1, sizeof(buf) - 1, f);
+               buf[size] = 0;
+
+               fclose(f);
+
+               if (strncmp(buf, "bt.", 3) == 0) {
+                       snprintf(path, PATH_MAX, "/dev/%s", entry->d_name);
+                       closedir(dir);
+                       return true;
+               }
+       }
+
+       closedir(dir);
+       return false;
+}
+
+static void copy_fd_bidi(int src, int dst)
+{
+       fd_set rfds, wfds;
+       int fd[2] = { src, dst };
+       uint8_t buf[2][4096];
+       size_t size[2] = { 0, 0 };
+       size_t pos[2] = { 0, 0 };
+       int i, ret;
+
+       /* Simple copying of data src <-> dst to both directions */
+
+       for (i = 0; i < 2; ++i) {
+               int flags = fcntl(fd[i], F_GETFL);
+
+               if (fcntl(fd[i], F_SETFL, flags | O_NONBLOCK) < 0) {
+                       perror("fcntl");
+                       goto error;
+               }
+       }
+
+       while (1) {
+               FD_ZERO(&rfds);
+               FD_ZERO(&wfds);
+
+               for (i = 0; i < 2; ++i) {
+                       if (size[i])
+                               FD_SET(fd[i], &wfds);
+                       else
+                               FD_SET(fd[1 - i], &rfds);
+               }
+
+               ret = select(FD_SETSIZE, &rfds, &wfds, NULL, NULL);
+               if (ret < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       perror("select");
+                       goto error;
+               }
+
+               for (i = 0; i < 2; ++i) {
+                       ssize_t s;
+
+                       if (!size[i] && FD_ISSET(fd[1 - i], &rfds)) {
+                               s = read(fd[1 - i], buf[i], sizeof(buf[i]));
+                               if (s >= 0) {
+                                       size[i] = s;
+                                       pos[i] = 0;
+                               } else if (errno == EINTR) {
+                                       /* ok */
+                               } else {
+                                       perror("read");
+                                       goto error;
+                               }
+
+                       }
+
+                       if (size[i]) {
+                               s = write(fd[i], buf[i] + pos[i], size[i]);
+                               if (s >= 0) {
+                                       size[i] -= s;
+                                       pos[i] += s;
+                               } else if (errno == EINTR || errno == EAGAIN
+                                               || errno == EWOULDBLOCK) {
+                                       /* ok */
+                               } else {
+                                       perror("write");
+                                       goto error;
+                               }
+                       }
+               }
+       }
+       return;
+
+error:
+       fprintf(stderr, "Bluetooth controller forward terminated with error\n");
+       exit(1);
+}
+
+static int start_controller_forward(const char *path, pid_t *controller_pid)
+{
+       struct termios ti;
+       pid_t pid;
+       int src = -1, dst = -1, fd = -1;
+       int ret, saved_ldisc, ldisc = N_HCI;
+
+       /* virtio-serial ports cannot be used for HCI line disciple, so
+        * openpty() serial device and forward data to/from it.
+        */
+
+       src = open(path, O_RDWR);
+       if (src < 0)
+               goto error;
+
+       /* Raw mode TTY */
+       memset(&ti, 0, sizeof(ti));
+       cfmakeraw(&ti);
+       ti.c_cflag |= B115200 | CLOCAL | CREAD;
+
+       /* With flow control */
+       ti.c_cflag |= CRTSCTS;
+
+       ret = openpty(&dst, &fd, NULL, &ti, NULL);
+       if (ret < 0)
+               goto error;
+
+       if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
+               perror("Failed get serial line discipline");
+               goto error;
+       }
+
+       if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
+               perror("Failed set serial line discipline");
+               goto error;
+       }
+
+       printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc);
+
+       pid = fork();
+       if (pid < 0) {
+               perror("Failed to fork new process");
+               goto error;
+       } else if (pid == 0) {
+               close(fd);
+               copy_fd_bidi(src, dst);
+               exit(0);
+       }
+
+       *controller_pid = pid;
+
+       close(src);
+       close(dst);
+       return fd;
+
+error:
+       if (src >= 0)
+               close(src);
+       if (dst >= 0)
+               close(dst);
+       if (fd >= 0)
+               close(fd);
+       return -1;
+}
+
+static int attach_controller(pid_t *controller_pid)
+{
+       unsigned int basic_flags, extra_flags;
+       char path[PATH_MAX];
+       int fd;
+
+       *controller_pid = -1;
+
+       if (!find_attach_dev(path)) {
+               printf("Failed to find Bluetooth controller virtio\n");
+               return -1;
+       }
+
+       printf("Forwarding Bluetooth controller from %s\n", path);
+
+       fd = start_controller_forward(path, controller_pid);
+       if (fd < 0) {
+               printf("Failed to forward Bluetooth controller\n");
+               return -1;
+       }
+
+       basic_flags = (1 << HCI_UART_RESET_ON_INIT);
+       extra_flags = (1 << HCI_UART_VND_DETECT);
+
+       printf("Attaching Bluetooth controller\n");
+
+       return attach_proto(fd, HCI_UART_H4, basic_flags, extra_flags);
+}
+
 static void run_command(char *cmdname, char *home)
 {
        char *argv[9], *envp[3];
        int pos = 0, idx = 0;
        int serial_fd;
        pid_t pid, dbus_pid, daemon_pid, monitor_pid, emulator_pid,
-             dbus_session_pid, audio_pid[2];
+               dbus_session_pid, audio_pid[2], controller_pid;
        int i;
 
        if (!home) {
@@ -910,18 +1072,11 @@ static void run_command(char *cmdname, char *home)
        }
 
        if (num_devs) {
-               const char *node = "/dev/ttyS1";
-               unsigned int basic_flags, extra_flags;
-
-               printf("Attaching BR/EDR controller to %s\n", node);
-
-               basic_flags = (1 << HCI_UART_RESET_ON_INIT);
-               extra_flags = (1 << HCI_UART_VND_DETECT);
-
-               serial_fd = attach_proto(node, HCI_UART_H4, basic_flags,
-                                                               extra_flags);
-       } else
+               serial_fd = attach_controller(&controller_pid);
+       } else {
                serial_fd = -1;
+               controller_pid = -1;
+       }
 
        if (start_dbus) {
                create_dbus_system_conf();
@@ -1063,6 +1218,11 @@ start_next:
                        monitor_pid = -1;
                }
 
+               if (corpse == controller_pid) {
+                       printf("Controller terminated\n");
+                       controller_pid = -1;
+               }
+
                for (i = 0; i < 2; ++i) {
                        if (corpse == audio_pid[i]) {
                                printf("Audio server %d terminated\n", i);
--

2.53.0

Signature · 1 line
Pauli Virtanen
#11 lore raw

PATCH 8/11 doc: add functional/integration testing documentation

In reply to Pauli Virtanen

Add documentation for functional/integration test suite.

---
 doc/test-functional.rst | 374 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 374 insertions(+)
 create mode 100644 doc/test-functional.rst
doc/test-functional.rst new file +374 -0
new file mode 100644
index 000000000..72325ec25
@@ -0,0 +1,374 @@
+===============
+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.
+
+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.
+
+:--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.
+
+Tests that require kernel image or USB controllers are skipped if none
+are available. Normally, tests use `btvirt`.
+
+
+REQUIREMENTS
+============
+
+Python
+------
+
+The following Python packages are required:
+
+.. code-block::
+
+   pytest
+   pexpect
+   dbus-python
+
+To install them via pip::
+
+	python3 -m pip install -r unit/func_test/requirements.txt
+
+On Fedora / RHEL::
+
+	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)**.  Simplest setup is
+
+.. code-block::
+
+	cp ../bluez/doc/tester.config .config
+	make olddefconfig
+	make -j8
+
+USB
+---
+
+Some tests may require a hardware controller instead of the virtual `btvirt` one.
+
+
+EXAMPLES
+========
+
+Run all tests
+-------------
+
+.. code-block::
+
+	$ unit/test-functional --kernel=/pathto/bzImage
+
+	$ export FUNCTIONAL_TESTING_KERNEL=/pathto/bzImage
+	$ unit/test-functional
+
+Show output during run
+----------------------
+
+.. code-block::
+
+	$ unit/test-functional --log-cli-level=0
+
+Show only specific loggers:
+
+.. code-block::
+
+	$ unit/test-functional --log-cli-level=0 --log-filter=rpc,host
+
+	$ unit/test-functional --log-cli-level=0 --log-filter=*.bluetoothctl
+
+Filter out loggers:
+
+.. code-block::
+
+	$ unit/test-functional --log-cli-level=0 --log-filter=-host
+
+	$ unit/test-functional --log-cli-level=0 --log-filter=host,-host.*.1
+
+Run selected tests
+------------------
+
+.. code-block::
+
+	$ unit/test-functional unit/func_test/test_cli_simple.py::test_bluetoothctl_script_show
+
+	$ unit/test-functional -k test_bluetoothctl_script_show
+
+	$ unit/test-functional -k 'test_btmgmt or test_bluetoothctl'
+
+Don't run tests with a given marker:
+
+.. code-block::
+
+	$ unit/test-functional -m "not pipewire"
+
+Don't run known-failing tests:
+
+.. code-block::
+
+	$ unit/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::
+
+	$ unit/test-functional -x --ff
+
+List all tests
+--------------
+
+.. code-block::
+
+	$ unit/test-functional --list
+
+Show errors from know-failing test
+----------------------------------
+
+.. code-block::
+
+	$ unit/test-functional --runxfail -k test_btmgmt_info
+
+Redirect USB devices
+--------------------
+
+.. code-block::
+
+	$ unit/test-functional --usb=hci0,hci1
+
+	$ export FUNCTIONAL_TESTING_CONTROLLERS=hci0,hci1
+	$ unit/test-functional
+
+
+WRITING TESTS
+=============
+
+The functional tests are written in files (test modules) names
+`unit/func_test/test_*.py`.  They are written using standard Pytest
+style.  See https://docs.pytest.org/en/stable/getting-started.html
+
+Example: Virtual machines
+-------------------------
+
+.. code-block:: python
+
+   from .lib import host_config, Bluetoothd, Bluetoothctl
+
+   @host_config(
+       [Bluetoothd(), Bluetoothctl()],
+       [Bluetoothd(), Bluetoothctl()],
+   )
+   def test_bluetoothctl_pair(hosts):
+       host0, host1 = hosts
+
+       host0.bluetoothctl.send("show\n")
+       host0.bluetoothctl.expect("Powered: yes")
+
+       host1.bluetoothctl.send("show\n")
+       host1.bluetoothctl.expect("Powered: yes")
+
+       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")
+
+The test declares a VM setup with two Qemu instances, where both hosts
+run bluetoothd and start a bluetoothctl process.  The Qemu instances
+have `btvirt` virtual BT controllers and can see each other.
+
+The test itself runs on the parent host.
+
+The `host0/1.bluetoothctl.*` commands invoke RPC calls to one of the
+the two VM instances. In this case, they are controlling the
+`bluetoothctl` process using `pexpect` library to deal with its
+command line.
+
+When the test body finishes executing, the test passes. Or, it fails
+if any ``assert`` statement fails or an error is raised. For example,
+above ``RemoteError`` due to bluetoothctl not proceeding as expected
+in pairing is possible.
+
+The host configuration (bluetoothd + bluetoothctl above) is torn down
+between test (SIGTERM/SIGKILL sent etc.).
+
+By default the VM instance itself continues running, and may be used
+for other tests that share the same VM setup.
+
+Generally, the framework automatically orders the tests so that the VM
+setup does not need to be restarted unless needed.
+
+
+Example host plugin
+-------------------
+
+The `host.bluetoothctl` implementation used above is as follows:
+
+.. code-block:: python
+
+   from .lib import HostPlugin
+
+   class Bluetoothctl(HostPlugin):
+       # Declare a unique name:
+
+       name = "bluetoothctl"
+
+       # Declare dependencies on other plugins
+
+       depends = [Bluetoothd()]
+
+       # The following is to be run on parent host outside VMs:
+
+       def __init__(self):
+           self.exe = utils.find_exe("client", "bluetoothctl")
+
+       # These run inside VM on plugin setup / teardown:
+
+       def setup(self, impl):
+           self.logger = utils.LogStream("bluetoothctl")
+           self.ctl = pexpect.spawn(self.exe, logfile=self.logger.stream)
+
+       def teardown(self):
+           self.ctl.terminate()
+           self.ctl.wait()
+
+       # These declare the custom RPC-callable methods of the plugin:
+
+       def expect(self, *a, **kw):
+           ret = self.ctl.expect(*a, **kw)
+           log.debug("pexpect: found")
+           return ret, self.ctl.match.groups()
+
+       def expect_prompt(self):
+           prompt = "\\[[a-zA-Z0-9. -]+\\]>"
+           return self.expect(prompt)
+
+       def send(self, *a, **kw):
+           return self.ctl.send(*a, **kw)
+
+Host plugins are for injecting code to run on the VM side test hosts.
+The host plugins have scope of one test.  The VM side test framework
+sends SIGTERM and SIGKILL to all processes in the test process group
+to reset the state between each test.
+
+The plugins are declared by inheriting from `HostPlugin`. Their
+`__init__()` is supposed to only store declarative configuration on
+`self` and runs on parent side early in the test discovery phase. The
+`setup()` and `teardown()` methods run on VM-side at host environment
+start and end.  All other methods can be invoked via RPC by the parent
+tester, and any values returned by them are passed via RPC back to the
+parent.
+
+To load a plugin to a VM host, pass it to `host_config()` in the
+declaration of a given test.
+
+Reference
+---------
+
+In addition to standard Pytest features, the following items are
+available in the `.lib` submodule.
+
+TODO: not complete
+
+host_config
+~~~~~~~~~~~
+
+.. code-block::
+
+   def host_config(*host_setup, hw=False)
+
+Declare host configuration.
+
+- \*host_setup: each argument is a list of plugins to be loaded on a host.
+  The number of arguments specifies the number of hosts.
+
+- hw (bool): whether to require hardware BT controller
+
+find_exe
+~~~~~~~~
+
+.. code-block::
+
+   from .lib import find_exe
+   bluetoothctl = find_exe("client", "bluetoothctl")
+
+Find absolute path to the given executable, either within BlueZ build
+directory or on host.
+
+RemoteError
+~~~~~~~~~~~
+
+.. code-block::
+
+   from .lib import RemoteError
+
+   try:
+       host.call(foo)
+   except RemoteError as exc:
+       print("    ".join(exc.traceback))
+       original_exception = exc.exc
+
+Exception raised on the VM side, passed through RPC. Properties:
+`traceback` is a list of traceback lines and `exc` is the original
+exception instance raised on the remote side.
Signature · 1 line
2.53.0
#12 lore raw

PATCH 9/11 unit: add functional/integration testing framework

In reply to 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.

This uses tools/test-runner to launch VM instances, but does not use the intra-VM setup code to allow reusing the same VM instances for multiple tests.

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 (more convenient than Python/unittest)

- Grouping tests to minimize VM reboots

- Redirecting USB controllers to use for testing

There is no requirement that the tests spawn VM instances, the test runner can be used for any tests written in Python.

---
 unit/func_test/__init__.py             |   0
 unit/func_test/conftest.py             | 277 ++++++++++++++
 unit/func_test/lib/__init__.py         |   5 +
 unit/func_test/lib/env.py              | 484 +++++++++++++++++++++++++
 unit/func_test/lib/host_plugins.py     | 269 ++++++++++++++
 unit/func_test/lib/rpc.py              | 293 +++++++++++++++
 unit/func_test/lib/runner.py           |  10 +
 unit/func_test/lib/tests/__init__.py   |   0
 unit/func_test/lib/tests/test_rpc.py   |  49 +++
 unit/func_test/lib/utils.py            | 266 ++++++++++++++
 unit/func_test/requirements.txt        |   3 +
 unit/func_test/test_bluetoothctl_vm.py |  76 ++++
 unit/func_test/test_btmgmt_vm.py       |  29 ++
 unit/pytest.ini                        |   6 +
 unit/test-functional                   |   8 +
 15 files changed, 1775 insertions(+)
 create mode 100644 unit/func_test/__init__.py
 create mode 100644 unit/func_test/conftest.py
 create mode 100644 unit/func_test/lib/__init__.py
 create mode 100644 unit/func_test/lib/env.py
 create mode 100644 unit/func_test/lib/host_plugins.py
 create mode 100644 unit/func_test/lib/rpc.py
 create mode 100644 unit/func_test/lib/runner.py
 create mode 100644 unit/func_test/lib/tests/__init__.py
 create mode 100644 unit/func_test/lib/tests/test_rpc.py
 create mode 100644 unit/func_test/lib/utils.py
 create mode 100644 unit/func_test/requirements.txt
 create mode 100644 unit/func_test/test_bluetoothctl_vm.py
 create mode 100644 unit/func_test/test_btmgmt_vm.py
 create mode 100644 unit/pytest.ini
 create mode 100755 unit/test-functional
unit/func_test/__init__.py new file +0 -0
new file mode 100644
index 000000000..e69de29bb
unit/func_test/conftest.py new file +277 -0

277 lines not on this page · raw message

unit/func_test/lib/__init__.py new file +5 -0
new file mode 100644
index 000000000..b2767105e
@@ -0,0 +1,5 @@
+from .rpc import RemoteError
+
+from .env import *
+from .utils import *
+from .host_plugins import *
unit/func_test/lib/env.py new file +484 -0

484 lines not on this page · raw message

unit/func_test/lib/host_plugins.py new file +269 -0

269 lines not on this page · raw message

unit/func_test/lib/rpc.py new file +293 -0

293 lines not on this page · raw message

unit/func_test/lib/runner.py new file +10 -0
new file mode 100644
index 000000000..bbfb00c12
@@ -0,0 +1,10 @@
+#!/usr/bin/python3 -P
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent / ".." / ".."))
+
+import func_test.lib.env
+
+sys.exit(func_test.lib.env._main_runner())
unit/func_test/lib/tests/__init__.py new file +0 -0
new file mode 100644
index 000000000..e69de29bb
unit/func_test/lib/tests/test_rpc.py new file +49 -0

49 lines not on this page · raw message

unit/func_test/lib/utils.py new file +266 -0

266 lines not on this page · raw message

unit/func_test/requirements.txt new file +3 -0
new file mode 100644
index 000000000..2ebe77723
@@ -0,0 +1,3 @@
+pytest
+pexpect
+dbus-python
unit/func_test/test_bluetoothctl_vm.py new file +76 -0

76 lines not on this page · raw message

unit/func_test/test_btmgmt_vm.py new file +29 -0
new file mode 100644
index 000000000..0a8d3ace6
@@ -0,0 +1,29 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+"""
+Tests for btmgmt using VM instances
+"""
+import sys
+import pytest
+import subprocess
+import tempfile
+
+from .lib 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
unit/pytest.ini new file +6 -0
new file mode 100644
index 000000000..17b695861
@@ -0,0 +1,6 @@
+[pytest]
+log_format = %(asctime)s %(levelname)-6s %(name)-20s:  %(message)s
+log_level = 0
+log_file = test-functional.log
+markers =
+    vm: tests requiring VM image
unit/test-functional new file +8 -0
new file mode 100755
index 000000000..9b919f117
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Examples:
+#
+#	./test-functional
+#	./test-functional --log-cli-level=0 -s
+#
+exec python3 -m pytest "$(dirname "$0")"/func_test "$@"
Signature · 1 line
2.53.0
#13 lore raw

PATCH 10/11 unit: func_test: add Pipewire-using smoke tests

In reply to Pauli Virtanen

Add test for Pipewire hosts connecting A2DP/HFP BREDR services to each other

---
 unit/func_test/test_pipewire.py | 137 ++++++++++++++++++++++++++++++++
 unit/pytest.ini                 |   1 +
 2 files changed, 138 insertions(+)
 create mode 100644 unit/func_test/test_pipewire.py
unit/func_test/test_pipewire.py new file +137 -0
new file mode 100644
index 000000000..5cec35b2b
@@ -0,0 +1,137 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+"""
+Tests for Pipewire audio
+"""
+import sys
+import os
+import pytest
+import subprocess
+import tempfile
+import time
+import logging
+import json
+import dbus
+from pathlib import Path
+
+from .lib import (
+    HostPlugin,
+    host_config,
+    find_exe,
+    Bluetoothd,
+    Bluetoothctl,
+    DbusSession,
+    LogStream,
+)
+
+pytestmark = [pytest.mark.vm, pytest.mark.pipewire]
+
+log = logging.getLogger(__name__)
+
+
+class Pipewire(HostPlugin):
+    name = "pipewire"
+    depends = [DbusSession(), Bluetoothd()]
+
+    def __init__(
+        self,
+        uuids=(
+            "0000110a-0000-1000-8000-00805f9b34fb",
+            "0000110b-0000-1000-8000-00805f9b34fb",
+        ),
+    ):
+        self.uuids = tuple(uuids)
+        try:
+            self.exe_pw = find_exe("", "pipewire")
+            self.exe_wp = find_exe("", "wireplumber")
+            self.exe_dump = find_exe("", "pw-dump")
+        except FileNotFoundError:
+            pytest.skip("skip", allow_module_level=True, reason="Pipewire not found")
+
+    def setup(self, impl):
+        self.tmpdir = tempfile.TemporaryDirectory(prefix="pipewire-", dir="/run")
+        conf_dir = Path(self.tmpdir.name) / "config"
+        runtime_dir = Path(self.tmpdir.name) / "runtime"
+
+        conf_dir.mkdir()
+        runtime_dir.mkdir()
+
+        environ = dict(os.environ)
+
+        environ["XDG_CONFIG_HOME"] = str(conf_dir)
+        environ["XDG_STATE_HOME"] = str(runtime_dir)
+        environ["XDG_RUNTIME_HOME"] = str(runtime_dir)
+        environ["PIPEWIRE_RUNTIME_DIR"] = str(runtime_dir)
+        environ["PIPEWIRE_DEBUG"] = "2"
+        environ["WIREPLUMBER_DEBUG"] = "3"
+
+        os.environ["PIPEWIRE_RUNTIME_DIR"] = str(runtime_dir)
+
+        log.info("Start pipewire")
+
+        self.logger = LogStream("pipewire")
+        self.pw = subprocess.Popen(
+            self.exe_pw,
+            env=environ,
+            stdout=self.logger.stream,
+            stderr=subprocess.STDOUT,
+        )
+        self.wp = subprocess.Popen(
+            self.exe_wp,
+            env=environ,
+            stdout=self.logger.stream,
+            stderr=subprocess.STDOUT,
+        )
+
+        # Wait for Pipewire's bluetooth services
+        bus = dbus.SystemBus()
+        adapter = dbus.Interface(
+            bus.get_object("org.bluez", "/org/bluez/hci0"),
+            "org.freedesktop.DBus.Properties",
+        )
+        while True:
+            uuids = [str(uuid) for uuid in adapter.Get("org.bluez.Adapter1", "UUIDs")]
+            if all(uuid in uuids for uuid in self.uuids):
+                break
+            time.sleep(0.1)
+
+        log.info("Pipewire ready")
+
+    def pw_dump(self):
+        ret = subprocess.run(["pw-dump"], stdout=subprocess.PIPE, encoding="utf-8")
+        return ret.stdout
+
+    def teardown(self):
+        log.info("Stop pipewire")
+        self.pw.terminate()
+        self.wp.terminate()
+        self.pw.wait()
+        self.wp.wait()
+        self.tmpdir.cleanup()
+
+
+@host_config(
+    [Bluetoothctl(), Pipewire()],
+    [Bluetoothctl(), Pipewire()],
+)
+def test_pipewire(hosts):
+    from .test_bluetoothctl_vm import test_bluetoothctl_pair
+
+    host0, host1 = hosts
+
+    # Pair first
+    test_bluetoothctl_pair(hosts)
+
+    # Connect
+    host1.bluetoothctl.send(f"trust {host0.bdaddr}\n")
+
+    host0.bluetoothctl.send(f"scan off\n")
+    host0.bluetoothctl.send(f"connect {host1.bdaddr}\n")
+
+    # Wait for pipewire devices to appear
+    for j in range(20):
+        text = host0.pipewire.pw_dump()
+        if "bluez_output." in text:
+            break
+        time.sleep(1)
+    else:
+        assert False, "no pipewire devices seen within timeout"
unit/pytest.ini +1 -0
index 17b695861..af780f897 100644
@@ -3,4 +3,5 @@ log_format = %(asctime)s %(levelname)-6s %(name)-20s:  %(message)s
 log_level = 0
 log_file = test-functional.log
 markers =
+    pipewire: tests requiring Pipewire
     vm: tests requiring VM image
Signature · 1 line
2.53.0
#14 lore raw

PATCH 11/11 build: add functional testing target

In reply to 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  |  7 +++++++
 configure.ac | 17 +++++++++++++++++
 2 files changed, 24 insertions(+)
Makefile.am +7 -0
index dee6aa6d0..906b3ef6b 100644
@@ -786,6 +786,13 @@ endif
 TESTS = $(unit_tests)
 AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69
 
+check-functional: all
+	$(srcdir)/unit/test-functional -v --kernel="$(FUNCTIONAL_TESTING_KERNEL)" --build-dir="$(top_builddir)"
+
+if FUNCTIONAL_TESTING
+check: check-functional
+endif
+
 if DBUS_RUN_SESSION
 AM_TESTS_ENVIRONMENT += dbus-run-session --
 endif
configure.ac +17 -0
index 52de7d665..f4b65f7b5 100644
@@ -405,6 +405,23 @@ 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
+  AC_MSG_CHECKING([pytest and dependencies])
+  python3 -m pip install --dry-run --no-index -r "${srcdir}/unit/func_test/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}])
Signature · 1 line
2.53.0
#15 lore raw
In reply to Pauli Virtanen

Hi Pauli,

Quoted 307 lines · 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.

Implements:

- RPC communication with tester instances running each of the VM hosts. Tests run on parent host, which instructs VM hosts what to do.

- 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 (more convenient than Python/unittest)

- Automatic grouping of tests to minimize VM reboots

- Redirecting USB controllers to use for testing in addition to btvirt

- Fairly straightforward, ~1600 sloc for the framework

There is no requirement that the tests spawn VM instances, the test runner can be used for any tests written in Python.

See doc/test-functional.rst for various examples.

Also unit/func_test/test_bluetoothctl_vm.py has some simple cases, and unit/func_test/test_pipewire.py for a more complicated setup

    host0(qemu): Pipewire <-> BlueZ <-> kernel
    <-> btvirt
    host1(qemu): kernel <-> BlueZ <-> Pipewire

The framework allows easily passing any data and code between the parent and VM hosts, so writing tests is straightforward.

***

Some examples:

$ unit/test-functional --list -q

unit/func_test/lib/tests/test_rpc.py::test_basic unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_script_show[hosts1-vm1] unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] unit/func_test/test_pipewire.py::test_pipewire[hosts3-vm2]

$ unit/test-functional -v --no-header
======================================= test session starts ========================================
collected 5 items
unit/func_test/lib/tests/test_rpc.py::test_basic PASSED                                      [ 20%]
unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_script_show[hosts1-vm1] SKIPPED    [ 40%]
unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] SKIPPED (No kernel image)     [ 60%]

unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] SKIPPED (No k...) [ 80%]
unit/func_test/test_pipewire.py::test_pipewire[hosts3-vm2] SKIPPED (No kernel image) [100%]

=================================== 1 passed, 4 skipped in 0.19s ===================================

$ unit/test-functional --kernel=../linux
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items
unit/func_test/lib/tests/test_rpc.py .                                   [ 20%]
unit/func_test/test_bluetoothctl_vm.py .                                 [ 40%]
unit/func_test/test_btmgmt_vm.py .                                       [ 60%]
unit/func_test/test_bluetoothctl_vm.py .                                 [ 80%]
unit/func_test/test_pipewire.py .                                        [100%]

============================== 5 passed in 41.92s ==============================

$ unit/test-functional --kernel=../linux -k test_btmgmt
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py . [100%]

======================= 1 passed, 4 deselected in 9.15s ========================

$ grep btmgmt test-functional.log
13:15:42 INFO   rpc.host.0.0        :  client: call_plugin ('call', '__call__', <function run at 0x7f27b81ce140>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:15:42 INFO   host.0.0.rpc        :  server: call_plugin ('call', '__call__', <function run at 0x7fd5e35a1010>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:15:42 INFO   host.0.0.run        :      $ /home/pauli/prj/external/bluez/build/tools/btmgmt --index 0 info
$ unit/test-functional --kernel=../linux -k test_btmgmt --log-cli-level=0
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] -------------------------------- live log setup --------------------------------

13:00:31 INFO   func_test.lib.env   :  Starting btvirt: /usr/bin/stdbuf -o L -e L /home/pauli/prj/external/bluez/build/emulator/btvirt --server=/tmp/bluez-func-test-8t6ychy8
13:00:31 OUT    btvirt              :  Bluetooth emulator ver 5.86
13:00:31 INFO   func_test.lib.env   :  Starting host: /home/pauli/prj/external/bluez/build/tools/test-runner --kernel=../linux/arch/x86/boot/bzImage -u/tmp/bluez-func-test-8t6ychy8/bt-server-bredrle -o -chardev -o socket,id=ser0,path=/tmp/bluez-func-test-8t6ychy8/bluez-func-test-rpc-0,server=on,wait=off -o -device -o virtio-serial -o -device -o virtserialport,chardev=ser0,name=bluez-func-test-rpc -H -- /usr/bin/python3 -P /home/pauli/prj/external/bluez/unit/func_test/lib/runner.py /dev/ttyS2
13:00:31 OUT    btvirt              :  Request for /tmp/bluez-func-test-8t6ychy8/bt-server-bredrle
13:00:32 OUT    host.0.0            :  early console in extract_kernel
13:00:32 OUT    host.0.0            :  input_data: 0x000000000425c2c4
...
13:00:39 INFO   rpc.host.0.0        :  client: call_plugin ('call', '__call__', <function run at 0x7f7547472140>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:00:39 DEBUG  host.0.0.rpc        :  server: done
13:00:39 INFO   host.0.0.rpc        :  server: call_plugin ('call', '__call__', <function run at 0x7f77dcc81010>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:00:39 INFO   host.0.0.run        :      $ /home/pauli/prj/external/bluez/build/tools/btmgmt --index 0 info
13:00:40 OUT    host.0.0.run.out    :  hci0:    Primary controller
13:00:40 OUT    host.0.0.run.out    :   addr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000
13:00:40 OUT    host.0.0.run.out    :   supported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver
13:00:40 OUT    host.0.0.run.out    :   current settings: br/edr
13:00:40 OUT    host.0.0.run.out    :   name
13:00:40 OUT    host.0.0.run.out    :   short name
13:00:40 INFO   host.0.0.run        :  (return code 0)
13:00:40 DEBUG  rpc.host.0.0        :  client-reply

PASSED [100%]
13:00:40 OUT host.0.0 : qemu-system-x86_64: terminating on signal 15 from pid 149047 (python3)
======================= 1 passed, 4 deselected in 8.84s ========================

$ unit/test-functional --kernel=../linux -k test_bluetoothctl_pair --log-cli-level=0 --log-filter=*.bluetoothctl,rpc.* --force-usb
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] -------------------------------- live log setup --------------------------------

13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bdaddr object at 0x7f268712d160>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Call object at 0x7f268712d2b0>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.DbusSystem object at 0x7f2687aa30e0>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bluetoothd object at 0x7f2687aa3230>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bluetoothctl object at 0x7f268712d010>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bdaddr object at 0x7f26871542d0>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Call object at 0x7f2687154410>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.DbusSystem object at 0x7f2687aa30e0>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bluetoothd object at 0x7f2687aa3230>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bluetoothctl object at 0x7f2687154190>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: wait_load () {}
13:03:21 DEBUG  rpc.host.0.0        :  client-reply
13:03:21 INFO   rpc.host.0.1        :  client: wait_load () {}
13:03:21 DEBUG  rpc.host.0.1        :  client-reply
-------------------------------- live log call ---------------------------------
13:03:21 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'show\n') {}
13:03:21 DEBUG  rpc.host.0.0        :  client-reply
13:03:21 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Powered: yes') {}
...
13:03:23 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'pair 70:1a:b8:73:99:bb\n') {}
13:03:23 OUT    host.0.0.bluetoothctl:  pair 70:1a:b8:73:99:bb
13:03:23 DEBUG  rpc.host.0.0        :  client-reply
13:03:23 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Confirm passkey (\\d+).*:') {}
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> pair 70:1a:b8:73:99:bb
13:03:23 OUT    host.0.0.bluetoothctl:  Attempting to pair with 70:1A:B8:73:99:BB
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 device_flags_changed: 70:1A:B8:73:99:BB (BR/EDR)
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]>      supp: 0x00000007  curr: 0x00000000
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 type 7 discovering off
13:03:25 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 70:1A:B8:73:99:BB type BR/EDR connected eir_len 12
13:03:25 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB Connected: yes
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> Request confirmation
13:03:25 DEBUG  rpc.host.0.0        :  client-reply
13:03:25 INFO   rpc.host.0.1        :  client: call_plugin ('bluetoothctl', 'expect', 'Confirm passkey 237345') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> hci0 84:5C:F3:77:31:19 type BR/EDR connected eir_len 12
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> [NEW] Device 84:5C:F3:77:31:19 BlueZ 5.86
13:03:25 DEBUG  rpc.host.0.1        :  client-reply
13:03:25 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'yes\n') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> [BlueZ 5.86]> Request confirmation
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [agent] Confirm passkey 237345 (yes/no): yes
13:03:25 DEBUG  rpc.host.0.0        :  client-reply
13:03:25 INFO   rpc.host.0.1        :  client: call_plugin ('bluetoothctl', 'send', 'yes\n') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [BlueZ 5.86]> [agent] Confirm passkey 237345 (yes/no): yes
13:03:25 DEBUG  rpc.host.0.1        :  client-reply
13:03:25 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Pairing successful') {}
13:03:25 OUT    host.0.0.bluetoothctl:  yes
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> hci0 new_link_key 70:1A:B8:73:99:BB type 0x08 pin_len 0 store_hint 1
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB Bonded: yes
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB AddressType: public
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
13:03:26 DEBUG  rpc.host.0.0        :  client-reply

PASSED [100%]
------------------------------ live log teardown -------------------------------
13:03:26 OUT host.0.0.bluetoothctl: [BlueZ 5.86]> [CHG] Device 70:1A:B8:98:FF:qemu-system-x86_64: terminating on signal 15 from pid 149357 (python3)

======================= 1 passed, 4 deselected in 13.22s =======================

$ COLUMNS=80 unit/test-functional -k test_btmgmt --kernel=../linux --trace
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py

Quoted 2 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

PDB runcall (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(19)test_btmgmt_info()

-> (host,) = hosts
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(21)test_btmgmt_info()

-> result = host.call(
(Pdb) p host.bdaddr
'00:aa:01:00:00:42'
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(22)test_btmgmt_info()

-> run,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(23)test_btmgmt_info()

-> [btmgmt, "--index", "0", "info"],
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(24)test_btmgmt_info()

-> stdout=subprocess.PIPE,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(25)test_btmgmt_info()

-> stdin=subprocess.DEVNULL,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(26)test_btmgmt_info()

-> encoding="utf-8",
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(21)test_btmgmt_info()

-> result = host.call(
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(28)test_btmgmt_info()

-> assert result.returncode == 0
(Pdb) p result
CompletedProcess(args=['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info'], returncode=0, stdout='hci0:\tPrimary controller\n\taddr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000\n\tsupported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver \n\tcurrent settings: br/edr \n\tname \n\tshort name \n') (Pdb) print(result.stdout)
hci0: Primary controller
addr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000
supported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver
current settings: br/edr
name
short name
(Pdb) q

!!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Quitting debugger !!!!!!!!!!!!!!!!!!! ======================= 4 deselected in 75.91s (0:01:15) =======================

***

Pauli Virtanen (11):
emulator: btvirt: check pkt lengths, don't get stuck on malformed
emulator: btvirt: allow specifying where server unix sockets are made
test-runner: enable path argument for --unix
test-runner: Add -o/--option option
test-runner: allow source tree root for -k
doc: enable CONFIG_VIRTIO_CONSOLE in tester config
test-runner: use virtio-serial for implementing -u device forwarding
doc: add functional/integration testing documentation
unit: add functional/integration testing framework
unit: func_test: add Pipewire-using smoke tests
build: add functional testing target

 Makefile.am                            |   7 +
 configure.ac                           |  17 +
 doc/ci.config                          |   1 +
 doc/test-functional.rst                | 374 +++++++++++++++++++
 doc/test-runner.rst                    |   1 +
 doc/tester.config                      |   1 +
 emulator/main.c                        |  37 +-
 emulator/server.c                      |   9 +
 tools/test-runner.c                    | 376 ++++++++++++++-----
 unit/func_test/__init__.py             |   0
 unit/func_test/conftest.py             | 277 ++++++++++++++
 unit/func_test/lib/__init__.py         |   5 +
 unit/func_test/lib/env.py              | 484 +++++++++++++++++++++++++
 unit/func_test/lib/host_plugins.py     | 269 ++++++++++++++
 unit/func_test/lib/rpc.py              | 293 +++++++++++++++
 unit/func_test/lib/runner.py           |  10 +
 unit/func_test/lib/tests/__init__.py   |   0
 unit/func_test/lib/tests/test_rpc.py   |  49 +++
 unit/func_test/lib/utils.py            | 266 ++++++++++++++
 unit/func_test/requirements.txt        |   3 +
 unit/func_test/test_bluetoothctl_vm.py |  76 ++++
 unit/func_test/test_btmgmt_vm.py       |  29 ++
 unit/func_test/test_pipewire.py        | 137 +++++++
 unit/pytest.ini                        |   7 +
 unit/test-functional                   |   8 +
 25 files changed, 2631 insertions(+), 105 deletions(-)
 create mode 100644 doc/test-functional.rst
 create mode 100644 unit/func_test/__init__.py
 create mode 100644 unit/func_test/conftest.py
 create mode 100644 unit/func_test/lib/__init__.py
 create mode 100644 unit/func_test/lib/env.py
 create mode 100644 unit/func_test/lib/host_plugins.py
 create mode 100644 unit/func_test/lib/rpc.py
 create mode 100644 unit/func_test/lib/runner.py
 create mode 100644 unit/func_test/lib/tests/__init__.py
 create mode 100644 unit/func_test/lib/tests/test_rpc.py
 create mode 100644 unit/func_test/lib/utils.py
 create mode 100644 unit/func_test/requirements.txt
 create mode 100644 unit/func_test/test_bluetoothctl_vm.py
 create mode 100644 unit/func_test/test_btmgmt_vm.py
 create mode 100644 unit/func_test/test_pipewire.py
 create mode 100644 unit/pytest.ini
 create mode 100755 unit/test-functional
Quoted 2 lines

--
2.53.0

Looks like a fairly solid start, that said I wouldn't mix these with unit test, that is more of a unit/whitebox style testing not really meant for end-to-end testing, Id probably have this under test/functional or test/pytest, anyway we could possibly remove the existing python tests and just convert them, or perhaps move them to examples since they are just demostrating how to use our D-Bus APIs using python rather than testing a specific use-case, etc.

Regarding pytest, Im not really an expert in the python testing frameworks so I will probably need to do some digging to see what are the options and there might be companies that already emply similar testing environment so I guess it is a good chance to make an attempt to convince more people to contribute upstream so we can consolidate in a single framework for end-to-end testing.

Signature · 1 line
Luiz Augusto von Dentz
#16 lore raw

Hi,

ma, 2026-03-02 kello 16:18 -0500, Luiz Augusto von Dentz kirjoitti:

Quoted 310 lines

Hi Pauli,

Quoted 307 lines · 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.

Implements:

- RPC communication with tester instances running each of the VM hosts. Tests run on parent host, which instructs VM hosts what to do.

- 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 (more convenient than Python/unittest)

- Automatic grouping of tests to minimize VM reboots

- Redirecting USB controllers to use for testing in addition to btvirt

- Fairly straightforward, ~1600 sloc for the framework

There is no requirement that the tests spawn VM instances, the test runner can be used for any tests written in Python.

See doc/test-functional.rst for various examples.

Also unit/func_test/test_bluetoothctl_vm.py has some simple cases, and unit/func_test/test_pipewire.py for a more complicated setup

    host0(qemu): Pipewire <-> BlueZ <-> kernel
    <-> btvirt
    host1(qemu): kernel <-> BlueZ <-> Pipewire

The framework allows easily passing any data and code between the parent and VM hosts, so writing tests is straightforward.

***

Some examples:

$ unit/test-functional --list -q

unit/func_test/lib/tests/test_rpc.py::test_basic unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_script_show[hosts1-vm1] unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] unit/func_test/test_pipewire.py::test_pipewire[hosts3-vm2]

$ unit/test-functional -v --no-header
======================================= test session starts ========================================
collected 5 items
unit/func_test/lib/tests/test_rpc.py::test_basic PASSED                                      [ 20%]
unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_script_show[hosts1-vm1] SKIPPED    [ 40%]
unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] SKIPPED (No kernel image)     [ 60%]

unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] SKIPPED (No k...) [ 80%]
unit/func_test/test_pipewire.py::test_pipewire[hosts3-vm2] SKIPPED (No kernel image) [100%]

=================================== 1 passed, 4 skipped in 0.19s ===================================

$ unit/test-functional --kernel=../linux
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items
unit/func_test/lib/tests/test_rpc.py .                                   [ 20%]
unit/func_test/test_bluetoothctl_vm.py .                                 [ 40%]
unit/func_test/test_btmgmt_vm.py .                                       [ 60%]
unit/func_test/test_bluetoothctl_vm.py .                                 [ 80%]
unit/func_test/test_pipewire.py .                                        [100%]

============================== 5 passed in 41.92s ==============================

$ unit/test-functional --kernel=../linux -k test_btmgmt
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py . [100%]

======================= 1 passed, 4 deselected in 9.15s ========================

$ grep btmgmt test-functional.log
13:15:42 INFO   rpc.host.0.0        :  client: call_plugin ('call', '__call__', <function run at 0x7f27b81ce140>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:15:42 INFO   host.0.0.rpc        :  server: call_plugin ('call', '__call__', <function run at 0x7fd5e35a1010>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:15:42 INFO   host.0.0.run        :      $ /home/pauli/prj/external/bluez/build/tools/btmgmt --index 0 info
$ unit/test-functional --kernel=../linux -k test_btmgmt --log-cli-level=0
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py::test_btmgmt_info[hosts2-vm1] -------------------------------- live log setup --------------------------------

13:00:31 INFO   func_test.lib.env   :  Starting btvirt: /usr/bin/stdbuf -o L -e L /home/pauli/prj/external/bluez/build/emulator/btvirt --server=/tmp/bluez-func-test-8t6ychy8
13:00:31 OUT    btvirt              :  Bluetooth emulator ver 5.86
13:00:31 INFO   func_test.lib.env   :  Starting host: /home/pauli/prj/external/bluez/build/tools/test-runner --kernel=../linux/arch/x86/boot/bzImage -u/tmp/bluez-func-test-8t6ychy8/bt-server-bredrle -o -chardev -o socket,id=ser0,path=/tmp/bluez-func-test-8t6ychy8/bluez-func-test-rpc-0,server=on,wait=off -o -device -o virtio-serial -o -device -o virtserialport,chardev=ser0,name=bluez-func-test-rpc -H -- /usr/bin/python3 -P /home/pauli/prj/external/bluez/unit/func_test/lib/runner.py /dev/ttyS2
13:00:31 OUT    btvirt              :  Request for /tmp/bluez-func-test-8t6ychy8/bt-server-bredrle
13:00:32 OUT    host.0.0            :  early console in extract_kernel
13:00:32 OUT    host.0.0            :  input_data: 0x000000000425c2c4
...
13:00:39 INFO   rpc.host.0.0        :  client: call_plugin ('call', '__call__', <function run at 0x7f7547472140>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:00:39 DEBUG  host.0.0.rpc        :  server: done
13:00:39 INFO   host.0.0.rpc        :  server: call_plugin ('call', '__call__', <function run at 0x7f77dcc81010>, ['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info']) {'stdout': -1, 'stdin': -3, 'encoding': 'utf-8'}
13:00:39 INFO   host.0.0.run        :      $ /home/pauli/prj/external/bluez/build/tools/btmgmt --index 0 info
13:00:40 OUT    host.0.0.run.out    :  hci0:    Primary controller
13:00:40 OUT    host.0.0.run.out    :   addr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000
13:00:40 OUT    host.0.0.run.out    :   supported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver
13:00:40 OUT    host.0.0.run.out    :   current settings: br/edr
13:00:40 OUT    host.0.0.run.out    :   name
13:00:40 OUT    host.0.0.run.out    :   short name
13:00:40 INFO   host.0.0.run        :  (return code 0)
13:00:40 DEBUG  rpc.host.0.0        :  client-reply

PASSED [100%]
13:00:40 OUT host.0.0 : qemu-system-x86_64: terminating on signal 15 from pid 149047 (python3)
======================= 1 passed, 4 deselected in 8.84s ========================

$ unit/test-functional --kernel=../linux -k test_bluetoothctl_pair --log-cli-level=0 --log-filter=*.bluetoothctl,rpc.* --force-usb
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_bluetoothctl_vm.py::test_bluetoothctl_pair[hosts0-vm2] -------------------------------- live log setup --------------------------------

13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bdaddr object at 0x7f268712d160>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Call object at 0x7f268712d2b0>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.DbusSystem object at 0x7f2687aa30e0>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bluetoothd object at 0x7f2687aa3230>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: start_load (<func_test.lib.host_plugins.Bluetoothctl object at 0x7f268712d010>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bdaddr object at 0x7f26871542d0>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Call object at 0x7f2687154410>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.DbusSystem object at 0x7f2687aa30e0>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bluetoothd object at 0x7f2687aa3230>,) {}
13:03:20 INFO   rpc.host.0.1        :  client: start_load (<func_test.lib.host_plugins.Bluetoothctl object at 0x7f2687154190>,) {}
13:03:20 INFO   rpc.host.0.0        :  client: wait_load () {}
13:03:21 DEBUG  rpc.host.0.0        :  client-reply
13:03:21 INFO   rpc.host.0.1        :  client: wait_load () {}
13:03:21 DEBUG  rpc.host.0.1        :  client-reply
-------------------------------- live log call ---------------------------------
13:03:21 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'show\n') {}
13:03:21 DEBUG  rpc.host.0.0        :  client-reply
13:03:21 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Powered: yes') {}
...
13:03:23 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'pair 70:1a:b8:73:99:bb\n') {}
13:03:23 OUT    host.0.0.bluetoothctl:  pair 70:1a:b8:73:99:bb
13:03:23 DEBUG  rpc.host.0.0        :  client-reply
13:03:23 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Confirm passkey (\\d+).*:') {}
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> pair 70:1a:b8:73:99:bb
13:03:23 OUT    host.0.0.bluetoothctl:  Attempting to pair with 70:1A:B8:73:99:BB
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 device_flags_changed: 70:1A:B8:73:99:BB (BR/EDR)
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]>      supp: 0x00000007  curr: 0x00000000
13:03:23 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 type 7 discovering off
13:03:25 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> hci0 70:1A:B8:73:99:BB type BR/EDR connected eir_len 12
13:03:25 OUT    host.0.0.bluetoothctl:  [bluetoothctl]> [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB Connected: yes
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> Request confirmation
13:03:25 DEBUG  rpc.host.0.0        :  client-reply
13:03:25 INFO   rpc.host.0.1        :  client: call_plugin ('bluetoothctl', 'expect', 'Confirm passkey 237345') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> hci0 84:5C:F3:77:31:19 type BR/EDR connected eir_len 12
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> [NEW] Device 84:5C:F3:77:31:19 BlueZ 5.86
13:03:25 DEBUG  rpc.host.0.1        :  client-reply
13:03:25 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'send', 'yes\n') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [bluetoothctl]> [BlueZ 5.86]> Request confirmation
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [agent] Confirm passkey 237345 (yes/no): yes
13:03:25 DEBUG  rpc.host.0.0        :  client-reply
13:03:25 INFO   rpc.host.0.1        :  client: call_plugin ('bluetoothctl', 'send', 'yes\n') {}
13:03:25 OUT    host.0.1.bluetoothctl:  [BlueZ 5.86]> [agent] Confirm passkey 237345 (yes/no): yes
13:03:25 DEBUG  rpc.host.0.1        :  client-reply
13:03:25 INFO   rpc.host.0.0        :  client: call_plugin ('bluetoothctl', 'expect', 'Pairing successful') {}
13:03:25 OUT    host.0.0.bluetoothctl:  yes
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> hci0 new_link_key 70:1A:B8:73:99:BB type 0x08 pin_len 0 store_hint 1
13:03:25 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB Bonded: yes
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB AddressType: public
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
13:03:26 OUT    host.0.0.bluetoothctl:  [BlueZ 5.86]> [CHG] Device 70:1A:B8:73:99:BB UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
13:03:26 DEBUG  rpc.host.0.0        :  client-reply

PASSED [100%]
------------------------------ live log teardown -------------------------------
13:03:26 OUT host.0.0.bluetoothctl: [BlueZ 5.86]> [CHG] Device 70:1A:B8:98:FF:qemu-system-x86_64: terminating on signal 15 from pid 149357 (python3)

======================= 1 passed, 4 deselected in 13.22s =======================

$ COLUMNS=80 unit/test-functional -k test_btmgmt --kernel=../linux --trace
============================= test session starts ==============================
platform linux -- Python 3.14.3, pytest-8.3.5, pluggy-1.6.0
rootdir: /home/pauli/prj/external/bluez/unit
configfile: pytest.ini
plugins: cov-5.0.0, forked-1.6.0, rerunfailures-15.0, timeout-2.4.0, xdist-3.7.0, hypothesis-6.123.0, flaky-3.8.1, anyio-4.12.1
collected 5 items / 4 deselected / 1 selected

unit/func_test/test_btmgmt_vm.py

Quoted 2 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

PDB runcall (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(19)test_btmgmt_info()

-> (host,) = hosts
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(21)test_btmgmt_info()

-> result = host.call(
(Pdb) p host.bdaddr
'00:aa:01:00:00:42'
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(22)test_btmgmt_info()

-> run,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(23)test_btmgmt_info()

-> [btmgmt, "--index", "0", "info"],
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(24)test_btmgmt_info()

-> stdout=subprocess.PIPE,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(25)test_btmgmt_info()

-> stdin=subprocess.DEVNULL,
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(26)test_btmgmt_info()

-> encoding="utf-8",
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(21)test_btmgmt_info()

-> result = host.call(
(Pdb) n

Quoted 1 line

/home/pauli/prj/external/bluez/unit/func_test/test_btmgmt_vm.py(28)test_btmgmt_info()

-> assert result.returncode == 0
(Pdb) p result
CompletedProcess(args=['/home/pauli/prj/external/bluez/build/tools/btmgmt', '--index', '0', 'info'], returncode=0, stdout='hci0:\tPrimary controller\n\taddr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000\n\tsupported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver \n\tcurrent settings: br/edr \n\tname \n\tshort name \n') (Pdb) print(result.stdout)
hci0: Primary controller
addr 00:AA:01:00:00:42 version 11 manufacturer 1521 class 0x000000
supported settings: powered connectable fast-connectable discoverable bondable link-security ssp br/edr le advertising secure-conn debug-keys privacy static-addr phy-configuration cis-central cis-peripheral iso-broadcaster sync-receiver ll-privacy past-sender past-receiver
current settings: br/edr
name
short name
(Pdb) q

!!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Quitting debugger !!!!!!!!!!!!!!!!!!! ======================= 4 deselected in 75.91s (0:01:15) =======================

***

Pauli Virtanen (11):
emulator: btvirt: check pkt lengths, don't get stuck on malformed
emulator: btvirt: allow specifying where server unix sockets are made
test-runner: enable path argument for --unix
test-runner: Add -o/--option option
test-runner: allow source tree root for -k
doc: enable CONFIG_VIRTIO_CONSOLE in tester config
test-runner: use virtio-serial for implementing -u device forwarding
doc: add functional/integration testing documentation
unit: add functional/integration testing framework
unit: func_test: add Pipewire-using smoke tests
build: add functional testing target

 Makefile.am                            |   7 +
 configure.ac                           |  17 +
 doc/ci.config                          |   1 +
 doc/test-functional.rst                | 374 +++++++++++++++++++
 doc/test-runner.rst                    |   1 +
 doc/tester.config                      |   1 +
 emulator/main.c                        |  37 +-
 emulator/server.c                      |   9 +
 tools/test-runner.c                    | 376 ++++++++++++++-----
 unit/func_test/__init__.py             |   0
 unit/func_test/conftest.py             | 277 ++++++++++++++
 unit/func_test/lib/__init__.py         |   5 +
 unit/func_test/lib/env.py              | 484 +++++++++++++++++++++++++
 unit/func_test/lib/host_plugins.py     | 269 ++++++++++++++
 unit/func_test/lib/rpc.py              | 293 +++++++++++++++
 unit/func_test/lib/runner.py           |  10 +
 unit/func_test/lib/tests/__init__.py   |   0
 unit/func_test/lib/tests/test_rpc.py   |  49 +++
 unit/func_test/lib/utils.py            | 266 ++++++++++++++
 unit/func_test/requirements.txt        |   3 +
 unit/func_test/test_bluetoothctl_vm.py |  76 ++++
 unit/func_test/test_btmgmt_vm.py       |  29 ++
 unit/func_test/test_pipewire.py        | 137 +++++++
 unit/pytest.ini                        |   7 +
 unit/test-functional                   |   8 +
 25 files changed, 2631 insertions(+), 105 deletions(-)
 create mode 100644 doc/test-functional.rst
 create mode 100644 unit/func_test/__init__.py
 create mode 100644 unit/func_test/conftest.py
 create mode 100644 unit/func_test/lib/__init__.py
 create mode 100644 unit/func_test/lib/env.py
 create mode 100644 unit/func_test/lib/host_plugins.py
 create mode 100644 unit/func_test/lib/rpc.py
 create mode 100644 unit/func_test/lib/runner.py
 create mode 100644 unit/func_test/lib/tests/__init__.py
 create mode 100644 unit/func_test/lib/tests/test_rpc.py
 create mode 100644 unit/func_test/lib/utils.py
 create mode 100644 unit/func_test/requirements.txt
 create mode 100644 unit/func_test/test_bluetoothctl_vm.py
 create mode 100644 unit/func_test/test_btmgmt_vm.py
 create mode 100644 unit/func_test/test_pipewire.py
 create mode 100644 unit/pytest.ini
 create mode 100755 unit/test-functional
Quoted 7 lines
Quoted 2 lines

--
2.53.0

Looks like a fairly solid start, that said I wouldn't mix these with unit test, that is more of a unit/whitebox style testing not really meant for end-to-end testing, Id probably have this under test/functional or test/pytest,

Ok.

Quoted 4 lines

anyway we could possibly remove the
existing python tests and just convert them, or perhaps move them to examples since they are just demostrating how to use our D-Bus APIs using python rather than testing a specific use-case, etc.

Most of these appear example test clients, and don't have the server side. I guess there's some value in keeping them, and just put the automated test suite to subfolder there.

Quoted 6 lines

Regarding pytest, Im not really an expert in the python testing frameworks so I will probably need to do some digging to see what are the options and there might be companies that already emply similar testing environment so I guess it is a good chance to make an attempt to convince more people to contribute upstream so we can consolidate in a single framework for end-to-end testing.

Pytest is a generic test framework; I'd say there are only two relevant choices for these in Python. It is widely used eg. by big parts of the Python machine learning / scientific stack (Pytorch, numpy, etc) and other projects, should be safe choice.

For specifically this kind of end-to-end or integration testing with multiple VM images + controller redirection, which are the custom parts here, it seemed not so easy to find something open source that's immediately relevant.

There are things for embedded / hardware testing (openhtf, pytest- embedded) but not clear those are immediately helpful here. There are of course applications for controlling fleets of VM/Docker images (eg. Ansible), but these are typically via network and probably need less spartan VM environment.

Signature · 1 line
Pauli Virtanen