From a5f63a9ab09922957cf06d3271f84a42acb426f8 Mon Sep 17 00:00:00 2001 From: Massimiliano Date: Tue, 19 Mar 2024 12:45:17 +0100 Subject: [PATCH] Added 10m timeout to eddystone bluetooth test (bugfix) (#1086) * Added 10m timeout to eddystone bluetooth test * Add tests for eddystone_scanner Minor: black eddystone scanner --- .../scripts/eddystone_scanner.py | 62 +++++++++------- .../tests/test_eddystone_scanner.py | 70 +++++++++++++++++++ 2 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 checkbox-support/checkbox_support/tests/test_eddystone_scanner.py diff --git a/checkbox-support/checkbox_support/scripts/eddystone_scanner.py b/checkbox-support/checkbox_support/scripts/eddystone_scanner.py index be9cf9c83..079848cc0 100644 --- a/checkbox-support/checkbox_support/scripts/eddystone_scanner.py +++ b/checkbox-support/checkbox_support/scripts/eddystone_scanner.py @@ -19,38 +19,40 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import argparse +import sys import time +import argparse from checkbox_support.vendor.beacontools import ( - BeaconScanner, EddystoneURLFrame) + BeaconScanner, + EddystoneURLFrame, +) +from checkbox_support.helpers.timeout import timeout from checkbox_support.interactive_cmd import InteractiveCommand def init_bluetooth(): # Power on the bluetooth controller - with InteractiveCommand('bluetoothctl') as btctl: - btctl.writeline('power on') + with InteractiveCommand("bluetoothctl") as btctl: + btctl.writeline("power on") time.sleep(3) - btctl.writeline('scan on') + btctl.writeline("scan on") time.sleep(3) - btctl.writeline('exit') + btctl.writeline("exit") btctl.kill() def beacon_scan(hci_device): TIMEOUT = 10 - beacon_mac = beacon_rssi = beacon_packet = '' + beacon_mac = beacon_rssi = beacon_packet = "" def callback(bt_addr, rssi, packet, additional_info): nonlocal beacon_mac, beacon_rssi, beacon_packet beacon_mac, beacon_rssi, beacon_packet = bt_addr, rssi, packet scanner = BeaconScanner( - callback, - bt_device_id=hci_device, - packet_filter=EddystoneURLFrame + callback, bt_device_id=hci_device, packet_filter=EddystoneURLFrame ) scanner.start() @@ -59,27 +61,34 @@ def callback(bt_addr, rssi, packet, additional_info): time.sleep(1) scanner.stop() if beacon_packet: - print('Eddystone beacon detected: URL: {} ' - ''.format(beacon_packet.url, beacon_mac, beacon_rssi)) + print( + "Eddystone beacon detected: URL: {} " + "".format(beacon_packet.url, beacon_mac, beacon_rssi) + ) return 0 - print('No EddyStone URL advertisement detected!') + print("No EddyStone URL advertisement detected!") return 1 -def main(): +@timeout(60 * 10) # 10 minutes timeout +def main(argv): init_bluetooth() parser = argparse.ArgumentParser( - description="Track BLE advertised packets") - parser.add_argument("-D", "--device", default='hci0', - help="Select the hciX device to use " - "(default hci0).") - args = parser.parse_args() + description="Track BLE advertised packets" + ) + parser.add_argument( + "-D", + "--device", + default="hci0", + help="Select the hciX device to use " "(default hci0).", + ) + args = parser.parse_args(argv) try: - hci_device = int(args.device.replace('hci', '')) + hci_device = int(args.device.replace("hci", "")) except ValueError: - print('Bad device argument, defaulting to hci0') + print("Bad device argument, defaulting to hci0") hci_device = 0 # Newer bluetooth controllers and bluez versions allow extended commands @@ -90,13 +99,16 @@ def main(): # Try the newest one first, then the older one if that doesn't work rc = beacon_scan(hci_device) if rc: - print('Trying again with older beacontools version...') + print("Trying again with older beacontools version...") global BeaconScanner, EddystoneURLFrame from checkbox_support.vendor.beacontools_2_0_2 import ( - BeaconScanner, EddystoneURLFrame) + BeaconScanner, + EddystoneURLFrame, + ) + rc = beacon_scan(hci_device) return rc -if __name__ == '__main__': - raise SystemExit(main()) +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/checkbox-support/checkbox_support/tests/test_eddystone_scanner.py b/checkbox-support/checkbox_support/tests/test_eddystone_scanner.py new file mode 100644 index 000000000..79e9f2258 --- /dev/null +++ b/checkbox-support/checkbox_support/tests/test_eddystone_scanner.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# encoding: UTF-8 +# Copyright (c) 2024 Canonical Ltd. +# +# Authors: +# Massimiliano Girardi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import unittest +from unittest.mock import patch, MagicMock + +from checkbox_support.scripts import eddystone_scanner + + +class TestEddystoneScanner(unittest.TestCase): + @patch("checkbox_support.scripts.eddystone_scanner.BeaconScanner") + def test_beacon_scan_ok(self, mock_beacon_scanner): + class BeaconScanner: + def __init__(self, callback, *args, **kwargs): + self.callback = callback + + def start(self): + packet = MagicMock(url="packet_url") + self.callback("address", "rssi", packet, None) + + def stop(self): + pass + + mock_beacon_scanner.side_effect = BeaconScanner + self.assertEqual(eddystone_scanner.beacon_scan("1"), 0) + + @patch("checkbox_support.scripts.eddystone_scanner.BeaconScanner") + @patch("time.time") + @patch("time.sleep") + def test_beacon_scan_fail( + self, mock_sleep, mock_time, mock_beacon_scanner + ): + mock_time.side_effect = [0, 60 * 60 * 60] # 60h, trigger timeout + self.assertEqual(eddystone_scanner.beacon_scan("1"), 1) + + @patch("checkbox_support.scripts.eddystone_scanner.BeaconScanner") + @patch("checkbox_support.scripts.eddystone_scanner.InteractiveCommand") + @patch("time.sleep") + def test_main_ok( + self, mock_sleep, mock_interactive_command, mock_beacon_scanner + ): + class BeaconScanner: + def __init__(self, callback, *args, **kwargs): + self.callback = callback + + def start(self): + packet = MagicMock(url="packet_url") + self.callback("address", "rssi", packet, None) + + def stop(self): + pass + + mock_beacon_scanner.side_effect = BeaconScanner + self.assertEqual(eddystone_scanner.main(["--device", "hc1"]), 0)