From 550898988749c3c896297eb12bd7c5e028a11407 Mon Sep 17 00:00:00 2001 From: stanley31huang Date: Tue, 23 Jul 2024 21:25:20 +0800 Subject: [PATCH] Automate WWAN 3gpp scan test (New) (#1323) - Add a `3gpp-scan` subcommand to the wwan_tests.py script - Create a new `wwan/3gpp-scan-{manufacturer}-{model}-{hw_id}-auto` template job to automatically scan available GSM connections using the new subcommand --------- Co-authored-by: Pierre Equoy --- providers/base/bin/wwan_tests.py | 73 +++++++++++ providers/base/tests/test_wwan_tests.py | 157 +++++++++++++++++++++++- providers/base/units/wwan/jobs.pxu | 43 ++++--- providers/base/units/wwan/test-plan.pxu | 5 +- 4 files changed, 255 insertions(+), 23 deletions(-) diff --git a/providers/base/bin/wwan_tests.py b/providers/base/bin/wwan_tests.py index a1690d5378..4e33f09e00 100755 --- a/providers/base/bin/wwan_tests.py +++ b/providers/base/bin/wwan_tests.py @@ -297,6 +297,16 @@ def _wwan_radio_off(): print() +def _wwan_radio_status(): + print_head("Get radio status") + cmd = ["nmcli", "r", "wwan"] + ret_sp = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True + ) + print(ret_sp.stdout.decode("utf-8")) + return ret_sp.stdout.decode("utf-8").strip() + + def _destroy_3gpp_connection(): print_head("Destroy 3GPP Connection") cmd = ["nmcli", "c", "delete", GSM_CON_ID] @@ -362,6 +372,68 @@ def invoked(self): sys.exit(ret_code) +class WWANTestCtx: + + def __init__(self, hardware_id, use_mmcli=True, need_enable=False): + self.mm_obj = MMCLI() if use_mmcli else MMDbus() + self.need_enable = need_enable + self.original_status = "" + self.modem_idx = self.mm_obj.equipment_id_to_mm_id(hardware_id) + + def __enter__(self): + self.original_status = _wwan_radio_status() + if self.need_enable and self.original_status == "disabled": + _wwan_radio_on() + return self + + def __exit__(self, type, value, traceback): + if self.need_enable and self.original_status == "disabled": + _wwan_radio_off() + + +class ThreeGppScanTest: + + def register_argument(self): + + parser = argparse.ArgumentParser() + parser.add_argument( + "hw_id", type=str, help="The hardware ID of the modem" + ) + parser.add_argument( + "--timeout", + type=int, + default=300, + help="timeout for 3gpp-scan", + ) + return parser.parse_args(sys.argv[2:]) + + def invoked(self): + + args = self.register_argument() + ret_code = 1 + try: + with WWANTestCtx(args.hw_id, True, True) as ctx: + cmd = [ + "mmcli", + "-m", + str(ctx.modem_idx), + "--3gpp-scan", + "--timeout", + str(args.timeout), + ] + print_head("Scanning 3GPP available network") + print("+ {}".format(" ".join(cmd))) + ret_sp = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + print(ret_sp.stdout.decode("utf-8")) + ret_code = ret_sp.returncode + except subprocess.CalledProcessError: + pass + + sys.exit(ret_code) + + class CountModems: def invoked(self): @@ -462,6 +534,7 @@ def main(self): "count": CountModems, "resources": Resources, "3gpp-connection": ThreeGppConnection, + "3gpp-scan": ThreeGppScanTest, "sim-present": SimPresent, "sim-info": SimInfo, } diff --git a/providers/base/tests/test_wwan_tests.py b/providers/base/tests/test_wwan_tests.py index e48aea074d..06ae054c2c 100644 --- a/providers/base/tests/test_wwan_tests.py +++ b/providers/base/tests/test_wwan_tests.py @@ -1,8 +1,10 @@ import unittest import sys -from unittest.mock import patch, call, Mock, MagicMock +from unittest.mock import patch, Mock, MagicMock from io import StringIO from contextlib import redirect_stdout +import argparse +import subprocess # Mock the dbus module due to is is not available on CI testing environment sys.modules["dbus"] = MagicMock() @@ -109,3 +111,156 @@ def test_invoked_with_mmdbus(self, mock_mmdbus): self.assertTrue(mmdbus_instance.get_model_name.called) self.assertTrue(mmdbus_instance.get_firmware_revision.called) self.assertTrue(mmdbus_instance.get_hardware_revision.called) + + +class TestCommonFunctions(unittest.TestCase): + + @patch("subprocess.run") + def test_wwan_radio_status(self, mock_run): + mock_run.return_value = subprocess.CompletedProcess( + args=[], returncode=0, stdout=b"disabled", stderr=b"" + ) + with redirect_stdout(StringIO()): + status = wwan_tests._wwan_radio_status() + self.assertEqual(status, "disabled") + + @patch("subprocess.run") + def test_wwan_radio_status_exception(self, mock_run): + mock_run.side_effect = subprocess.CalledProcessError(1, "cmd") + with redirect_stdout(StringIO()): + with self.assertRaises(subprocess.CalledProcessError): + wwan_tests._wwan_radio_status() + + +class TestWWANTestCtx(unittest.TestCase): + + @patch("wwan_tests._wwan_radio_off") + @patch("wwan_tests._wwan_radio_on") + @patch("wwan_tests._wwan_radio_status") + @patch("wwan_tests.MMDbus") + def test_wwan_ctx_init_with_dbus( + self, mock_mmdbus, mock_r_status, mock_r_on, mock_r_off + ): + mmdbus_instance = Mock() + mmdbus_instance.equipment_id_to_mm_id.return_value = "0" + mock_mmdbus.return_value = mmdbus_instance + mock_r_status.return_value = "enabled" + + with wwan_tests.WWANTestCtx("test_id", False): + pass + + mmdbus_instance.equipment_id_to_mm_id.assert_called_with("test_id") + self.assertTrue(mock_mmdbus.called) + self.assertFalse(mock_r_on.called) + self.assertFalse(mock_r_off.called) + self.assertTrue(mock_r_status.called) + + @patch("wwan_tests._wwan_radio_off") + @patch("wwan_tests._wwan_radio_on") + @patch("wwan_tests._wwan_radio_status") + @patch("wwan_tests.MMCLI") + def test_wwan_ctx_init_with_mmcli( + self, mock_mmcli, mock_r_status, mock_r_on, mock_r_off + ): + mmcli_instance = Mock() + mmcli_instance.equipment_id_to_mm_id.return_value = "0" + mock_mmcli.return_value = mmcli_instance + mock_r_status.return_value = "disabled" + + with wwan_tests.WWANTestCtx("test_id", True, True): + pass + + mmcli_instance.equipment_id_to_mm_id.assert_called_with("test_id") + self.assertTrue(mock_mmcli.called) + self.assertTrue(mock_r_on.called) + self.assertTrue(mock_r_off.called) + self.assertTrue(mock_r_status.called) + + +class TestThreeGppScanTest(unittest.TestCase): + + def test_register_argument(self): + + sys.argv = ["wwan_tests.py", "3gpp-scan", "2", "--timeout", "600"] + obj_3gppscan = wwan_tests.ThreeGppScanTest() + ret_args = obj_3gppscan.register_argument() + self.assertEqual(ret_args.hw_id, "2") + self.assertEqual(ret_args.timeout, 600) + + @patch("subprocess.run") + @patch("wwan_tests.WWANTestCtx") + @patch("wwan_tests.ThreeGppScanTest.register_argument") + def test_invoked_successfully(self, mock_arg, mock_mmctx, mock_run): + mock_arg.return_value = argparse.Namespace(hw_id="2", timeout=200) + mmcli_instance = Mock() + mmcli_instance.modem_idx = "0" + mock_mmctx.return_value.__enter__.return_value = mmcli_instance + mock_run.return_value = subprocess.CompletedProcess( + args=[], returncode=0, stdout=b"output", stderr=b"" + ) + + with redirect_stdout(StringIO()): + with self.assertRaises(SystemExit) as context: + obj_3gppscan = wwan_tests.ThreeGppScanTest() + obj_3gppscan.invoked() + + self.assertEqual(context.exception.code, 0) + mock_mmctx.assert_called_with("2", True, True) + mock_run.assert_called_with( + ["mmcli", "-m", "0", "--3gpp-scan", "--timeout", "200"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + @patch("subprocess.run") + @patch("wwan_tests.WWANTestCtx") + @patch("wwan_tests.ThreeGppScanTest.register_argument") + def test_invoked_failed_exit_code(self, mock_arg, mock_mmctx, mock_run): + mock_arg.return_value = argparse.Namespace(hw_id="2", timeout=200) + mmcli_instance = Mock() + mmcli_instance.modem_idx = "0" + mock_mmctx.return_value.__enter__.return_value = mmcli_instance + mock_run.return_value = subprocess.CompletedProcess( + args=[], returncode=1, stdout=b"output", stderr=b"" + ) + + with redirect_stdout(StringIO()): + with self.assertRaises(SystemExit) as context: + obj_3gppscan = wwan_tests.ThreeGppScanTest() + obj_3gppscan.invoked() + + self.assertEqual(context.exception.code, 1) + mock_mmctx.assert_called_with("2", True, True) + mock_run.assert_called_with( + ["mmcli", "-m", "0", "--3gpp-scan", "--timeout", "200"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + @patch("subprocess.run") + @patch("wwan_tests.WWANTestCtx") + @patch("wwan_tests.ThreeGppScanTest.register_argument") + def test_invoked_call_error(self, mock_arg, mock_mmctx, mock_run): + mock_arg.return_value = argparse.Namespace(hw_id="2", timeout=200) + mmcli_instance = Mock() + mmcli_instance.modem_idx = "0" + mock_mmctx.return_value.__enter__.return_value = mmcli_instance + mock_run.return_value = subprocess.CompletedProcess( + args=[], + returncode=1, + stdout=b"output", + stderr=b"", + ) + + with redirect_stdout(StringIO()): + with self.assertRaises(SystemExit) as context: + obj_3gppscan = wwan_tests.ThreeGppScanTest() + obj_3gppscan.invoked() + + self.assertEqual(context.exception.code, 1) + mock_mmctx.assert_called_with("2", True, True) + mock_run.assert_called_with( + ["mmcli", "-m", "0", "--3gpp-scan", "--timeout", "200"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) diff --git a/providers/base/units/wwan/jobs.pxu b/providers/base/units/wwan/jobs.pxu index 9ed8e76c89..e169edaf26 100644 --- a/providers/base/units/wwan/jobs.pxu +++ b/providers/base/units/wwan/jobs.pxu @@ -51,6 +51,7 @@ estimated_duration: 10.0 category_id: wwan flags: preserve-locale also-after-suspend preserve-cwd imports: from com.canonical.plainbox import manifest +depends: wwan/check-sim-present-{manufacturer}-{model}-{hw_id}-auto requires: manifest.has_wwan_module == 'True' snap.name == 'modem-manager' or package.name == 'modemmanager' @@ -101,6 +102,28 @@ requires: manifest.has_wwan_module == 'True' snap.name == 'modem-manager' or package.name == 'modemmanager' +unit: template +template-resource: wwan_resource +template-unit: job +id: wwan/3gpp-scan-{manufacturer}-{model}-{hw_id}-auto +template-id: wwan/3gpp-scan-manufacturer-model-hw_id-auto +_summary: Scan for available 3GPP networks with the modem +_template-summary: Scan for available 3GPP networks with the {model} modem +_description: + Scan for available 3GPP networks with the target modem +plugin: shell +command: wwan_tests.py 3gpp-scan {hw_id} +environ: LD_LIBRARY_PATH +user: root +estimated_duration: 10.0 +category_id: wwan +flags: preserve-locale also-after-suspend +depends: wwan/check-sim-present-{manufacturer}-{model}-{hw_id}-auto +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' + snap.name == 'modem-manager' or package.name == 'modemmanager' + id: wwan/detect-manual plugin: manual _summary: Check if WWAN module is available @@ -171,26 +194,6 @@ requires: depends: wwan/check-sim-present-manual -id: wwan/scan-networks-manual -plugin: manual -_summary: Verify that modem can scan for available networks -_purpose: - Ensure that the modem can scan/find available networks -_steps: - 1. Open another terminal on SUT (or press ctrl+z to suspend Checkbox) - 2. Run `sudo mmcli -m 0 --3gpp-scan --timeout=300` - 3. Run `fg` to jump back to checkbox (if you're running in the same terminal) -_verification: - Were available networks listed? -estimated_duration: 120s -flags: also-after-suspend -category_id: wwan -imports: from com.canonical.plainbox import manifest -requires: - manifest.has_wwan_module == 'True' -depends: - wwan/check-sim-present-manual - id: wwan/gsm-connection-interrupted-manual plugin: manual template-engine: jinja2 diff --git a/providers/base/units/wwan/test-plan.pxu b/providers/base/units/wwan/test-plan.pxu index 3312766f94..1a456d6ba9 100644 --- a/providers/base/units/wwan/test-plan.pxu +++ b/providers/base/units/wwan/test-plan.pxu @@ -16,6 +16,7 @@ _description: Automated wwan tests for Snappy Ubuntu Core devices include: # Note these tests require snap calling snap support wwan/detect + wwan/3gpp-scan-manufacturer-model-hw_id-auto wwan/gsm-connection-.*-auto wwan/check-sim-present-.*-auto bootstrap_include: @@ -36,6 +37,8 @@ _name: Automated wwan tests (after suspend) _description: Automated wwan tests for Snappy Ubuntu Core devices include: after-suspend-wwan/detect + after-suspend-wwan/check-sim-present-manufacturer-model-hw_id-auto + after-suspend-wwan/3gpp-scan-manufacturer-model-hw_id-auto after-suspend-wwan/gsm-connection-.*-auto bootstrap_include: wwan_resource @@ -46,7 +49,6 @@ _name: Manual wwan tests _description: Manual wwan tests for Snappy Ubuntu Core devices include: wwan/detect-manual - wwan/scan-networks-manual wwan/check-sim-present-manual wwan/gsm-connection-interrupted-manual @@ -56,6 +58,5 @@ _name: Manual wwan tests (after suspend) _description: Manual wwan tests for Snappy Ubuntu Core devices include: after-suspend-wwan/detect-manual - after-suspend-wwan/scan-networks-manual after-suspend-wwan/check-sim-present-manual after-suspend-wwan/gsm-connection-interrupted-manual \ No newline at end of file