From ea6f0a80d51de68d5a7619d6d9cd4352327eb45c Mon Sep 17 00:00:00 2001 From: hanhsuan <32028620+hanhsuan@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:49:36 +0800 Subject: [PATCH 01/25] add depends for suspend/{index}_suspend_after_switch_to_card_{product_slug}_auto (BugFix) (#1484) * add depends for suspend/{index}_suspend_after_switch_to_card_{product_slug}_auto to avoid running this test unexpectedly * Remove after option (as depends -> after) --- providers/base/units/suspend/suspend-graphics.pxu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/base/units/suspend/suspend-graphics.pxu b/providers/base/units/suspend/suspend-graphics.pxu index 3640cbcb32..7495dab446 100644 --- a/providers/base/units/suspend/suspend-graphics.pxu +++ b/providers/base/units/suspend/suspend-graphics.pxu @@ -24,7 +24,7 @@ template-id: suspend/index_suspend_after_switch_to_card_product_slug_auto requires: sleep.mem == 'supported' rtc.state == 'supported' -after: graphics/{index}_auto_switch_card_{product_slug} +depends: graphics/{index}_auto_switch_card_{product_slug} user: root environ: PLAINBOX_SESSION_SHARE command: From 90ed9b01c00aaa8b798b81c0a936b672f2f6e6c7 Mon Sep 17 00:00:00 2001 From: rickwu666666 <98441647+rickwu666666@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:38:19 +0800 Subject: [PATCH 02/25] Add parser to parse wifi interface name which is not start with phy (BugFix) (#1390) * Modify command in resource job * Add script to replace wifi_interface_mode parser * Add unittest for wifi_interface_mode * Fix format to fit black * Add install libsystemd-dev section in git workflow for resource providers * Modify logic and add more unittest to increase code coverage * Apply suggestions from code review Co-authored-by: Massimiliano * Remove redundant function run_command * Modify requires section for the job using wifi_interface_mode resource job * Modify unittest --------- Co-authored-by: Massimiliano --- .github/workflows/tox-provider-resource.yaml | 4 + providers/base/units/wireless/nm-hotspot.pxu | 4 +- providers/base/units/wireless/wifi-ap.pxu | 58 ++++----- providers/resource/bin/wifi_interface_mode.py | 68 +++++++++++ providers/resource/jobs/resource.pxu | 7 +- .../tests/test_wifi_interface_mode.py | 113 ++++++++++++++++++ 6 files changed, 217 insertions(+), 37 deletions(-) create mode 100755 providers/resource/bin/wifi_interface_mode.py create mode 100755 providers/resource/tests/test_wifi_interface_mode.py diff --git a/.github/workflows/tox-provider-resource.yaml b/.github/workflows/tox-provider-resource.yaml index ac43198c2d..b6538f6237 100644 --- a/.github/workflows/tox-provider-resource.yaml +++ b/.github/workflows/tox-provider-resource.yaml @@ -47,6 +47,10 @@ jobs: python-version: ${{ matrix.python }} env: PIP_TRUSTED_HOST: pypi.python.org pypi.org files.pythonhosted.org + - name: Install libsystemd-dev + run: | + sudo apt-get update + sudo apt-get install -y libsystemd-dev - name: Install tox run: pip install tox - name: Run tox diff --git a/providers/base/units/wireless/nm-hotspot.pxu b/providers/base/units/wireless/nm-hotspot.pxu index 282d7316c8..984e7baf4a 100644 --- a/providers/base/units/wireless/nm-hotspot.pxu +++ b/providers/base/units/wireless/nm-hotspot.pxu @@ -15,7 +15,7 @@ category_id: wifi_ap estimated_duration: 10 flags: preserve-locale also-after-suspend requires: - wifi_interface_mode.{{ interface }}_AP == 'supported' + ( wifi_interface_mode.interface == '{{ interface }}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{{ interface }}' and net_if_management.master_mode_managed_by == 'NetworkManager' {%- if __on_ubuntucore__ %} connections.slot == 'network-manager:service' and connections.plug == '{{ __system_env__["SNAP_NAME"] }}:network-manager' @@ -39,7 +39,7 @@ category_id: wifi_ap estimated_duration: 10 flags: preserve-locale also-after-suspend requires: - wifi_interface_mode.{{ interface }}_AP == 'supported' + ( wifi_interface_mode.interface == '{{ interface }}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{{ interface }}' and net_if_management.master_mode_managed_by == 'NetworkManager' {%- if __on_ubuntucore__ %} connections.slot == 'network-manager:service' and connections.plug == '{{ __system_env__["SNAP_NAME"] }}:network-manager' diff --git a/providers/base/units/wireless/wifi-ap.pxu b/providers/base/units/wireless/wifi-ap.pxu index 4ca6ff5024..bc79f2881f 100644 --- a/providers/base/units/wireless/wifi-ap.pxu +++ b/providers/base/units/wireless/wifi-ap.pxu @@ -8,7 +8,7 @@ category_id: wifi_ap _summary: Create open 802.11a Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -65,7 +65,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -81,7 +81,7 @@ category_id: wifi_ap _summary: Create open 802.11b Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -138,7 +138,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -154,7 +154,7 @@ category_id: wifi_ap _summary: Create open 802.11g Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -211,7 +211,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -227,7 +227,7 @@ category_id: wifi_ap _summary: Create open 802.11ad Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -284,7 +284,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -300,7 +300,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11a Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -359,7 +359,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -375,7 +375,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11b Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{{ interface }}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -434,7 +434,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -450,7 +450,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11g Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -507,7 +507,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -523,7 +523,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11ad Wi-Fi AP on {interface} with no STA (Manual) plugin: manual requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 _purpose: @@ -582,7 +582,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -598,7 +598,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11a Wi-Fi AP on {interface} with active STA (Manual) plugin: user-interact-verify requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID @@ -637,7 +637,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11a Wi-Fi Access Point on {interface} with active STA plugin: shell requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME @@ -686,7 +686,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11b Wi-Fi AP on {interface} with active STA (Manual) plugin: user-interact-verify requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID @@ -725,7 +725,7 @@ category_id: wifi_ap _summary: Create a WPA2 802.11b Wi-Fi Access Point on {interface} with active STA plugin: shell requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME @@ -774,7 +774,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11g Wi-Fi AP on {interface} with active STA (Manual) plugin: user-interact-verify requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID @@ -813,7 +813,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11g Wi-Fi Access Point on {interface} with active STA plugin: shell requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME @@ -862,7 +862,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11ad Wi-Fi AP on {interface} with active STA (Manual) plugin: user-interact-verify requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID @@ -901,7 +901,7 @@ category_id: wifi_ap _summary: Create WPA2 802.11ad Wi-Fi Access Point on {interface} with active STA plugin: shell requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME @@ -950,7 +950,7 @@ category_id: wifi_ap _summary: Create Access Point on {interface} using wifi-ap.setup-wizard plugin: shell requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 20.0 user: root @@ -998,7 +998,7 @@ command: echo "Rebooting" reboot requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -1035,7 +1035,7 @@ command: sleep "${{WIFI_AP_SETUPTIME:-10}}" reboot requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' estimated_duration: 120.0 environ: WIFI_AP_SETUPTIME @@ -1067,7 +1067,7 @@ command: exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' depends: wireless/wifi_ap_across_reboot_{interface}_setup estimated_duration: 120.0 @@ -1093,7 +1093,7 @@ command: wifi-ap.config set disabled=true if [ "$RES" -eq 2 ]; then exit 0; else exit 1; fi requires: - wifi_interface_mode.{interface}_AP == 'supported' + ( wifi_interface_mode.interface == '{interface}' and wifi_interface_mode.AP == 'supported' ) net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'wifi-ap' depends: wireless/wifi_ap_across_reboot_{interface}_setup_manual estimated_duration: 120.0 diff --git a/providers/resource/bin/wifi_interface_mode.py b/providers/resource/bin/wifi_interface_mode.py new file mode 100755 index 0000000000..c55e9b0b06 --- /dev/null +++ b/providers/resource/bin/wifi_interface_mode.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +from subprocess import check_output +import re +from typing import List, Tuple, Dict + + +def get_interfaces() -> List[Tuple[str, str]]: + output = check_output(["iw", "dev"], universal_newlines=True) + device_pattern = r"phy#(\d+).*?Interface (\S+)" + if output: + matches = re.findall(device_pattern, output, re.DOTALL) + return [ + (device_id, interface_name) + for device_id, interface_name in matches + ] + raise SystemExit(0) + + +def get_wiphy_info() -> Dict[int, List[str]]: + # We can not use command "iw phy0 info" to get the infomation of sepcific + # interface. + # Because of "phy0" in command could be other pattern like "mwiphy0". + # And such pattern as "mwiphy0" will only show in the output of command + # "iw phy". + output = check_output(["iw", "phy"], universal_newlines=True) + if not output: + return [] + + interfaces_info = (info.strip() for info in output.split("Wiphy")) + # drop empty sections + interfaces_info = filter(bool, interfaces_info) + index_re = re.compile(r"phy(\d+)") + wiphy_info = {} + supported_modes_re = re.compile( + r"Supported interface modes:\s*((?:\s*\*\s*[\w/-]+\s*)+)" + ) + for interface_info in interfaces_info: + interface_id = index_re.search(interface_info).group(1) + match_modes = supported_modes_re.search(interface_info).group(1) + + supported_modes = map(str.strip, match_modes.split("*")) + # remove first element because it is spaces before the first * + _ = next(supported_modes) + wiphy_info[interface_id] = list(supported_modes) + return wiphy_info + + +def print_supported_modes() -> str: + interfaces = get_interfaces() + wiphy_info = get_wiphy_info() + for wiphy_index, modes in wiphy_info.items(): + interface = interfaces[int(wiphy_index)][1] + print("interface: {}".format(interface)) + for mode in modes: + print( + "{}: supported".format( + mode.replace("-", "_").replace("/", "_") + ) + ) + print() + + +def main(): + print_supported_modes() + + +if __name__ == "__main__": + main() diff --git a/providers/resource/jobs/resource.pxu b/providers/resource/jobs/resource.pxu index 2b74b1a380..2bae08bd4f 100644 --- a/providers/resource/jobs/resource.pxu +++ b/providers/resource/jobs/resource.pxu @@ -372,12 +372,7 @@ estimated_duration: 0.1 plugin: resource category_id: information_gathering command: - # shellcheck disable=SC2046 - for i in $(iw dev | grep -oP 'Interface\s+\K\w+'); - do iw phy phy$(iw dev "$i" info | grep -oP 'wiphy\s+\K\d+') info | - grep -Pzo '(?s)Supported interface modes:\n\K(\s+\*\s.*?\n)+' | - sed "s/.*\* \(.*\)/""$i""_\1: supported/" | tr -d '\000'; - done + wifi_interface_mode.py _summary: Create resource info for supported wifi interface modes id: snap diff --git a/providers/resource/tests/test_wifi_interface_mode.py b/providers/resource/tests/test_wifi_interface_mode.py new file mode 100755 index 0000000000..499bf6dced --- /dev/null +++ b/providers/resource/tests/test_wifi_interface_mode.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import unittest +from unittest.mock import patch, call +from wifi_interface_mode import ( + get_interfaces, + get_wiphy_info, + print_supported_modes, + main, +) + + +class TestWiFiFunctions(unittest.TestCase): + @patch("wifi_interface_mode.check_output") + def test_get_interfaces(self, mock_check_output): + mock_check_output.return_value = """ + phy#0 + Interface wlan0 + phy#1 + Interface wlan1 + """ + expected = [("0", "wlan0"), ("1", "wlan1")] + result = get_interfaces() + self.assertEqual(result, expected) + + @patch("wifi_interface_mode.check_output") + def test_get_interfaces_no_wifi_interface(self, mock_check_output): + mock_check_output.return_value = "" + + with self.assertRaises(SystemExit) as cm: + get_interfaces() + + self.assertEqual(cm.exception.code, 0) + + @patch("wifi_interface_mode.check_output") + def test_get_wiphy_info_with_one_phy(self, mock_check_output): + mock_check_output.return_value = """Wiphy phy0 + Supported interface modes: + * managed + * AP/VLAN + * monitor + """ + expected = { + "0": ["managed", "AP/VLAN", "monitor"], + } + result = get_wiphy_info() + self.assertEqual(result, expected) + + @patch("wifi_interface_mode.check_output") + def test_get_wiphy_info_with_phy_start_with_mwi(self, mock_check_output): + mock_check_output.return_value = """Wiphy mwiphy0 + Supported interface modes: + * managed + * AP/VLAN + * monitor + """ + expected = { + "0": ["managed", "AP/VLAN", "monitor"], + } + result = get_wiphy_info() + self.assertEqual(result, expected) + + @patch("wifi_interface_mode.check_output") + def test_get_wiphy_info_with_two_phy(self, mock_check_output): + mock_check_output.return_value = """Wiphy phy0 + Supported interface modes: + * managed + * AP/VLAN + * monitor + Wiphy phy1 + Supported interface modes: + * managed + * AP/VLAN + * P2P-client + """ + expected = { + "0": ["managed", "AP/VLAN", "monitor"], + "1": ["managed", "AP/VLAN", "P2P-client"], + } + result = get_wiphy_info() + self.assertEqual(result, expected) + + @patch("wifi_interface_mode.get_interfaces") + @patch("wifi_interface_mode.get_wiphy_info") + def test_print_supported_modes( + self, mock_get_wiphy_info, mock_get_interfaces + ): + # Mock the return values + mock_get_interfaces.return_value = [("0", "wlan0"), ("1", "wlan1")] + mock_get_wiphy_info.return_value = { + "0": ["IBSS", "AP/VLAN"], + "1": ["AP", "P2P-client"], + } + # Mock print to capture output + with patch("builtins.print") as mock_print: + print_supported_modes() + + # Expected sequence of print calls + expected_calls = [ + call("interface: wlan0"), + call("IBSS: supported"), + call("AP_VLAN: supported"), + call(), + call("interface: wlan1"), + call("AP: supported"), + call("P2P_client: supported"), + call(), + ] + mock_print.assert_has_calls(expected_calls, any_order=True) + + +if __name__ == "__main__": + unittest.main() From 104c9315eb018a7737e5530ec1ca70852f20d1d7 Mon Sep 17 00:00:00 2001 From: hanhsuan <32028620+hanhsuan@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:40:36 +0800 Subject: [PATCH 03/25] Remove hibernate from iot test plan (BugFix) (#1483) There is no more hibernate test requirement in the iot project. Therefore, remove this test from stress-full test plan --- providers/base/units/stress/test-plan.pxu | 1 - 1 file changed, 1 deletion(-) diff --git a/providers/base/units/stress/test-plan.pxu b/providers/base/units/stress/test-plan.pxu index ebe0e8550d..bb2ca39ca9 100644 --- a/providers/base/units/stress/test-plan.pxu +++ b/providers/base/units/stress/test-plan.pxu @@ -130,7 +130,6 @@ nested_part: warm-boot-stress-test cold-boot-stress-test suspend-cycles-stress-test - hibernate-stress-test stress-ng-automated stress-iperf3-automated From 319d21f5d6c181906940c5af422b91ab535ba14c Mon Sep 17 00:00:00 2001 From: Massimiliano Date: Fri, 20 Sep 2024 11:23:46 +0200 Subject: [PATCH 04/25] Workflow to dispatch jobs to the lab (infra) (#1466) * Quote variable * Fix quotes * remove buggy constraint * Workflow dispatch workflow * Generic template to run job from source * All resources and update with second envsubst * Try to actually submit the job * Update launcher name everywhere * Use the testflinger action to launch the job * Rename all artifacts to make pathing simpler * Don't submit the template but the actual launcher * Better documentation and location for files * Remove outdated file * Explicit source instead of . * Use pipx instead of pip Minor: omit .service from service name * Better naming * Rename in a sensible way the checkbox conf during the workflow --- .github/workflows/dispatch_lab_job.yaml | 68 ++++++++++++--- tools/lab_dispatch/build_install_deb.py | 84 +++++++++++++++++++ tools/lab_dispatch/generic_source.yaml | 74 ++++++++++++++++ .../checkbox.no-manifest.template.conf | 40 +++++++++ tools/lab_dispatch/resources/manifest.conf | 12 +++ 5 files changed, 268 insertions(+), 10 deletions(-) create mode 100644 tools/lab_dispatch/build_install_deb.py create mode 100644 tools/lab_dispatch/generic_source.yaml create mode 100644 tools/lab_dispatch/resources/checkbox.no-manifest.template.conf create mode 100644 tools/lab_dispatch/resources/manifest.conf diff --git a/.github/workflows/dispatch_lab_job.yaml b/.github/workflows/dispatch_lab_job.yaml index 7b122b4754..1832bcecd7 100644 --- a/.github/workflows/dispatch_lab_job.yaml +++ b/.github/workflows/dispatch_lab_job.yaml @@ -1,14 +1,62 @@ -name: Run Workflow on PR Comment +name: Dispatch Checkbox jobs in the lab on: - issue_comment: - types: [created] + workflow_dispatch: + inputs: + # matrix to create is an array where each item is a job configuration + # to be dispatched in the lab. + # A job configuration is a dict with + # - data_source: distribution to provision (ex. distro: desktop-22-04-2-uefi) + # - queue: machine that will run the job (ex. 202012-28526) + # - test_plan: Checkbox test plan to run (ex. com.canonical.certification::sru) + # - match: subset of jobs to run (ex. .*wireless.*) + # + # One possible matrix_to_create would therefore look like this: + # matrix_to_create=[{ data_source: "distro: desktop-22-04-2-uefi", queue: "202012-28526", match: ".*wireless.*", test_plan: "com.canonical.certification::sru" }]' + # + # To run this workflow manually you can use the `gh` cli utility as follows: + # gh workflow run dispatch_lab_job.yaml -f 'matrix_to_create=[...]' + matrix_to_create: + description: 'Json formatted description of the jobs to dispatch' + required: true + type: string jobs: - trigger: - runs-on: ubuntu-latest + run-matrix: + runs-on: [self-hosted, testflinger] + strategy: + matrix: + spec: ${{ fromJson(inputs.matrix_to_create) }} + defaults: + run: + working-directory: tools/lab_dispatch steps: - - name: Dispatch test in the lab and monitor it - if: ${{ contains(github.event.comment.body, '/lab') && github.event.issue.pull_request && github.event.issue.author_association == "MEMBER" }} - run: - COMMENT_BODY="${{ github.event.comment.body }}" - echo $COMMENT_BODY + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install dependencies + run: | + sudo apt install gettext + - name: Build test resource + env: + INPUT_DATA_SOURCE: ${{ matrix.spec.data_source }} + INPUT_QUEUE: ${{ matrix.spec.queue }} + INPUT_MATCH: ${{ matrix.spec.match }} + INPUT_TEST_PLAN: ${{ matrix.spec.test_plan }} + INPUT_PASSWORD_SECRET: ${{ secrets.INPUT_PASSWORD_SECRET }} + run: | + echo "::group::Building the testflinger job" + export INPUT_CHECKBOX_REVISION="$(git rev-parse HEAD)" + envsubst '$INPUT_CHECKBOX_REVISION $INPUT_DATA_SOURCE $INPUT_QUEUE' < generic_source.yaml | tee job.yaml + echo "::endgroup::" + + echo "::group::Building the Checkbox launcher" + + # this goes from .template. (missing secret, testplan, match etc. to .partial.) + # this is partial as some values are filled in on the agent (like wireless access points names) + envsubst '$INPUT_TEST_PLAN $INPUT_MATCH $INPUT_PASSWORD_SECRET' < resources/checkbox.no-manifest.template.conf | tee resources/checkbox.no-manifest.partial.conf + echo "::endgroup::" + - name: Submit and monitor job + uses: canonical/testflinger/.github/actions/submit@main + with: + poll: true + job-path: tools/lab_dispatch/job.yaml diff --git a/tools/lab_dispatch/build_install_deb.py b/tools/lab_dispatch/build_install_deb.py new file mode 100644 index 0000000000..285f193e7a --- /dev/null +++ b/tools/lab_dispatch/build_install_deb.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +import shutil +import argparse +import subprocess + +from pathlib import Path +from contextlib import suppress + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("paths", type=Path, nargs="+") + parser.add_argument("--clean", action="store_true") + + return parser.parse_args() + + +def prepare_repo(repo_root, package_path): + shutil.move(package_path / "debian", repo_root) + + +def install_build_depends(repo_root): + subprocess.check_call( + ["sudo", "apt-get", "-y", "build-dep", "."], cwd=repo_root + ) + + +def build_package(repo_root): + # -Pnocheck: skip tests as we have a pipeline that builds/tests debian + # packages and doing them on slow machines is a big waste of + # time/resources + subprocess.check_call(["dpkg-buildpackage", "-Pnocheck"], cwd=repo_root) + + +def install_local_package(repo_root, deb_name_glob): + # we build in path.parent, dpkg will put the result on .. + package_list = list(repo_root.parent.glob(deb_name_glob)) + print(f"==== Installing {package_list} ====") + package_list = [str(x.resolve()) for x in package_list] + subprocess.check_call( + [ + "sudo", + "apt-get", + "--fix-broken", + "-y", + "install", + ] + + package_list, + cwd=repo_root.parent, + ) + + +def install_package(repo_root, package_path): + deb_name_glob = f"*{package_path.name}*.deb" + install_local_package(repo_root, deb_name_glob) + + +def clean_repo(repo_root): + subprocess.check_call(["git", "clean", "-xfd", "."], cwd=repo_root) + + +def build_install_deb(path, clean): + package_path = path.resolve() + + repo_root = package_path.parent + if "providers" in str(package_path): + repo_root = repo_root.parent + + prepare_repo(repo_root, package_path) + install_build_depends(repo_root) + build_package(repo_root) + install_package(repo_root, package_path) + if clean: + clean_repo(repo_root) + + +def main(): + args = parse_args() + for path in args.paths: + build_install_deb(path, args.clean) + + +if __name__ == "__main__": + main() diff --git a/tools/lab_dispatch/generic_source.yaml b/tools/lab_dispatch/generic_source.yaml new file mode 100644 index 0000000000..394031bd28 --- /dev/null +++ b/tools/lab_dispatch/generic_source.yaml @@ -0,0 +1,74 @@ +job_queue: $INPUT_QUEUE +global_timeout: 3600 +output_timeout: 1800 +provision_data: + $INPUT_DATA_SOURCE +test_data: + attachments: + - local: "tools/lab_dispatch/resources/manifest.conf" + agent: "manifest.conf" + - local: "tools/lab_dispatch/resources/checkbox.no-manifest.partial.conf" + agent: "checkbox.no-manifest.partial.conf" + - local: "tools/lab_dispatch/build_install_deb.py" + agent: "build_install_deb.py" + test_cmds: | + #!/usr/bin/env bash + + set -x + + # input arguments + CHECKBOX_REVISION=$INPUT_CHECKBOX_REVISION + RESOURCES_PATH="attachments/test" + + TOOLS_PATH=tools + + # retrieve all scripts/tools necessary from a repo + curl -Ls -o install_tools.sh https://raw.githubusercontent.com/canonical/hwcert-jenkins-tools/main/install_tools.sh + source install_tools.sh $TOOLS_PATH + + # ensure device is available before continuing + wait_for_ssh + + _run sudo add-apt-repository ppa:checkbox-dev/edge + _run install_packages git python3 python3-pip dpkg-dev + + wait_for_ssh + + _put $RESOURCES_PATH/build_install_deb.py : + _run git clone https://github.com/canonical/checkbox.git + _run git -C checkbox checkout $CHECKBOX_REVISION + _run python3 build_install_deb.py --clean checkbox/checkbox-ng \ + checkbox/checkbox-support checkbox/providers/resource \ + checkbox/providers/base checkbox/providers/sru + _run sudo systemctl restart checkbox-ng + + git clone https://github.com/canonical/checkbox.git + git -C checkbox checkout $CHECKBOX_REVISION + pipx install --spec checkbox/checkbox-ng checkbox-ng + + # retrieve manifest + MANIFEST_FILE=manifest.conf + fetch_manifest --manifest_file manifest.conf $CID $HEXR_DEVICE_SECURE_ID + if [ $? -ne 0 ]; then + echo "Using default manifest" + MANIFEST_FILE=$RESOURCES_PATH/manifest.conf + fi + + ### create checkbox launcher + # first dump the location specific infos in the launcher + which envsubst || install_packages gettext + envsubst < $RESOURCES_PATH/checkbox.no-manifest.partial.conf > checkbox.no-manifest.conf + # then insert the manifest entries via the stacker + stacker --output checkbox.conf checkbox.no-manifest.conf $MANIFEST_FILE + + wait_for_ssh + + check_for_checkbox_service + + # run the canary test plan + PYTHONUNBUFFERED=1 checkbox-cli control $DEVICE_IP checkbox.conf + EXITCODE=$? + + # placeholder for gathering possible artifacts + + exit $EXITCODE diff --git a/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf b/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf new file mode 100644 index 0000000000..f5c95c536b --- /dev/null +++ b/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf @@ -0,0 +1,40 @@ +[launcher] +launcher_version = 1 +stock_reports = certification, text + +[test plan] +unit = $INPUT_TEST_PLAN +forced = yes + +[test selection] +forced = true +match=$INPUT_MATCH + +[ui] +type = silent + +[environment] +ROUTERS = multiple +WPA_BG_SSID = $WPA_BG_SSID +WPA_BG_PSK = $INPUT_PASSWORD_SECRET +WPA_N_SSID = $WPA_N_SSID +WPA_N_PSK = $INPUT_PASSWORD_SECRET +WPA_AC_SSID = $WPA_AC_SSID +WPA_AC_PSK = $INPUT_PASSWORD_SECRET +WPA_AX_SSID = $WPA_AX_SSID +WPA_AX_PSK = $INPUT_PASSWORD_SECRET +WPA3_AX_SSID = $WPA3_AX_SSID +WPA3_AX_PSK = $INPUT_PASSWORD_SECRET +OPEN_BG_SSID = $OPEN_BG_SSID +OPEN_N_SSID = $OPEN_N_SSID +OPEN_AC_SSID = $OPEN_AC_SSID +OPEN_AX_SSID = $OPEN_AX_SSID +BTDEVADDR = 54:35:30:15:BC:DA +TRANSFER_SERVER = cdimage.ubuntu.com +DISPLAY= :0 +SUDO_USER = ubuntu +STRESS_NG_DISK_TIME = 15 +STRESS_NG_CPU_TIME = 180 +PTS_CACHE_URL = http://10.102.196.9/sru/phoronix_cache/ +SNAPD_TASK_TIMEOUT = 120 +ZAPPER_HOST = $ZAPPER_IP diff --git a/tools/lab_dispatch/resources/manifest.conf b/tools/lab_dispatch/resources/manifest.conf new file mode 100644 index 0000000000..901d303ec7 --- /dev/null +++ b/tools/lab_dispatch/resources/manifest.conf @@ -0,0 +1,12 @@ +[manifest] +com.canonical.certification::has_bt_smart = false +com.canonical.certification::has_camera = true +com.canonical.certification::has_card_reader = true +com.canonical.certification::has_ethernet_adapter = true +com.canonical.certification::has_thunderbolt = false +com.canonical.certification::has_thunderbolt3 = false +com.canonical.certification::has_touchscreen = false +com.canonical.certification::has_tpm2_chip = false +com.canonical.certification::has_usb_storage = true +com.canonical.certification::has_usb_type_c = false +com.canonical.certification::has_wlan_adapter = true From c9f8b0387651c0375c19b0dc97ee261ea65a0917 Mon Sep 17 00:00:00 2001 From: stanley31huang Date: Mon, 23 Sep 2024 15:28:22 +0800 Subject: [PATCH 05/25] Update gadget interface validation test plan (BugFix) (#1494) Update gadget interface validation test plan update ce-oem-automated test plan and gadget interface test cases --- .../checkbox-provider-ce-oem/units/gadget/jobs.pxu | 2 -- .../checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/gadget/jobs.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/gadget/jobs.pxu index 2cf5b332a3..b914d6b43c 100644 --- a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/gadget/jobs.pxu +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/gadget/jobs.pxu @@ -52,7 +52,6 @@ requires: lsb.distributor_id == 'Ubuntu Core' category_id: com.canonical.certification::gadget estimated_duration: 5s -flags: also-after-suspend command: check_gadget_interface.py --type {type} --name {name} --interface {interface} # --attrs "{attrs}" @@ -73,5 +72,4 @@ requires: lsb.distributor_id == 'Ubuntu Core' category_id: com.canonical.certification::gadget estimated_duration: 5s -flags: also-after-suspend command: check_gadget_interface.py --type {type} --name {name} --interface {interface} diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu index 08d06fd22f..f408c33d78 100644 --- a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu @@ -83,6 +83,7 @@ nested_part: com.canonical.certification::rtc-automated com.canonical.certification::led-indicator-auto before-suspend-ce-oem-spi-automated + ce-oem-gadget-automated id: after-suspend-ce-oem-manual unit: test plan From e721427fc7be802ce805557c752fc108e6a4db81 Mon Sep 17 00:00:00 2001 From: Zongmin Liu Date: Mon, 23 Sep 2024 15:49:38 +0800 Subject: [PATCH 06/25] Apply blocker certification-status to all the test jobs in ce-oem test plans (BugFix) (#1473) * Tidy up ce-oem test plans a bit * Apply blocker certification-status to all the test jobs in ce-oem test plans --- .../units/test-plan-ce-oem-full-classic.pxu | 100 +++++++++++------ .../units/test-plan-ce-oem-full-core.pxu | 102 ++++++++++-------- .../units/test-plan-ce-oem.pxu | 49 +++++---- 3 files changed, 155 insertions(+), 96 deletions(-) diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-classic.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-classic.pxu index 48e99d79db..8b50879cb7 100644 --- a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-classic.pxu +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-classic.pxu @@ -6,40 +6,45 @@ id: ce-oem-iot-server-24-04 unit: test plan -_name: CE-OEM-IOT - Full manual + automated tests for Ubuntu Server 24.04 +_name: CE-OEM-IOT - Full manual + automated OEMQA tests for Ubuntu Server 24.04 _description: - Combined manual and automated test plans for the IoT devices running Ubuntu Server. + Combined manual and automated test plans for IoT devices running Ubuntu Server. include: nested_part: ce-oem-iot-server-24-04-manual ce-oem-iot-server-24-04-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-22-04 unit: test plan -_name: CE-OEM-IOT - Full manual + automated tests for Ubuntu Server 22.04 +_name: CE-OEM-IOT - Full manual + automated OEMQA tests for Ubuntu Server 22.04 _description: - Combined manual and automated test plans for the IoT devices running Ubuntu Server. + Combined manual and automated test plans for IoT devices running Ubuntu Server. include: nested_part: ce-oem-iot-server-22-04-manual ce-oem-iot-server-22-04-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-20-04 unit: test plan -_name: CE-OEM-IOT - Full manual + automated tests for Ubuntu Server 20.04 +_name: CE-OEM-IOT - Full manual + automated OEMQA tests for Ubuntu Server 20.04 _description: - Combined manual and automated test plans for Ubuntu Server IoT devices. + Combined manual and automated test plans for IoT devices running Ubuntu Server. include: nested_part: ce-oem-iot-server-20-04-manual ce-oem-iot-server-20-04-automated - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-24-04-manual unit: test plan -_name: CE-OEM-IOT - Manual only QA tests for Ubuntu Server 24.04 +_name: CE-OEM-IOT - Manual only OEMQA tests for Ubuntu Server 24.04 _description: - Ubuntu Server QA test plan for the ce-oem-iot hardware. This test plan contains + Ubuntu Server OEMQA test plan for the IoT devices. This test plan contains all of the tests that require manual control of device hardware or some other user input to complete. estimated_duration: 3600 @@ -50,12 +55,14 @@ nested_part: after-suspend-ce-oem-manual exclude: com.canonical.certification::ethernet/wol_S4_.* +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-22-04-manual unit: test plan -_name: CE-OEM-IOT - Manual only QA tests for Ubuntu Server 22.04 +_name: CE-OEM-IOT - Manual only OEMQA tests for Ubuntu Server 22.04 _description: - Ubuntu Server QA test plan for the ce-oem-iot hardware. This test plan contains + Ubuntu Server OEMQA test plan for the IoT devices. This test plan contains all of the tests that require manual control of device hardware or some other user input to complete. estimated_duration: 3600 @@ -66,12 +73,14 @@ nested_part: after-suspend-ce-oem-manual exclude: com.canonical.certification::ethernet/wol_S4_.* +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-20-04-manual unit: test plan -_name: CE-OEM-IOT - Manual only QA tests for Ubuntu Server 20.04 +_name: CE-OEM-IOT - Manual only OEMQA tests for Ubuntu Server 20.04 _description: - Ubuntu Server QA test plan for the ce-oem-iot hardware. This test plan contains + Ubuntu Server OEMQA test plan for the IoT devices. This test plan contains all of the tests that require manual control of device hardware or some other user input to complete. estimated_duration: 3600 @@ -82,70 +91,93 @@ nested_part: after-suspend-ce-oem-manual exclude: com.canonical.certification::ethernet/wol_S4_.* +certification_status_overrides: + apply blocker to .* - -id: ce-oem-iot-server-22-04-automated +id: ce-oem-iot-server-24-04-automated unit: test plan -_name: CE-OEM-IOT - Automated only QA tests for Ubuntu Server 22.04 +_name: CE-OEM-IOT - Automated only OEMQA tests for Ubuntu Server 24.04 _description: - Ubuntu Server QA test plan for the IoT hardware. This test plan contains - all of the automated tests used to validate the IoT device. + Ubuntu Server OEMQA test plan for the IoT devices. This test plan contains + all of the automated tests used to validate the IoT devices. include: nested_part: ce-oem-automated - com.canonical.certification::client-cert-iot-server-22-04-automated + com.canonical.certification::client-cert-iot-server-24-04-automated after-suspend-ce-oem-automated exclude: +certification_status_overrides: + apply blocker to .* -id: ce-oem-iot-server-24-04-automated +id: ce-oem-iot-server-22-04-automated unit: test plan -_name: CE-OEM-IOT - Automated only QA tests for Ubuntu Server 24.04 +_name: CE-OEM-IOT - Automated only OEMQA tests for Ubuntu Server 22.04 _description: - Ubuntu Server QA test plan for the IoT hardware. This test plan contains - all of the automated tests used to validate the IoT device. + Ubuntu Server OEMQA test plan for the IoT devices. This test plan contains + all of the automated tests used to validate the IoT devices. include: nested_part: ce-oem-automated - com.canonical.certification::client-cert-iot-server-24-04-automated + com.canonical.certification::client-cert-iot-server-22-04-automated after-suspend-ce-oem-automated exclude: +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-20-04-automated unit: test plan -_name: CE-OEM-IOT - Automated only QA tests for Ubuntu Server 20.04 +_name: CE-OEM-IOT - Automated only OEMQA tests for Ubuntu Server 20.04 _description: - Ubuntu Server QA test plan for the ce-oem-iot hardware. This test plan contains - all of the automated tests used to validate the ce-oem-iot device. + Ubuntu Server OEMQA test plan for the IoT devices. This test plan contains + all of the automated tests used to validate the IoT devices. include: nested_part: ce-oem-automated com.canonical.certification::client-cert-iot-server-20-04-automated after-suspend-ce-oem-automated exclude: +certification_status_overrides: + apply blocker to .* + +id: ce-oem-iot-server-24-04-stress +unit: test plan +_name: CE-OEM-IOT - Stress only OEMQA tests for Ubuntu Server 24.04 +_description: + Ubuntu Server OEMQA test plan that includes all stress tests required for IoT devices +include: +nested_part: + com.canonical.certification::client-cert-iot-server-24-04-stress + ce-oem-stress +exclude: + com.canonical.certification::stress-tests/hibernate.* +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-22-04-stress unit: test plan -_name: CE-OEM-IOT - Stress tests for Ubuntu Server 22.04 +_name: CE-OEM-IOT - Stress only OEMQA tests for Ubuntu Server 22.04 _description: - Ubuntu Server QA test plan that includes all stress tests required for IoT devices + Ubuntu Server OEMQA test plan that includes all stress tests required for IoT devices include: nested_part: - com.canonical.certification::stress-iperf3-automated # keep if ethernet is supported com.canonical.certification::client-cert-iot-server-22-04-stress ce-oem-stress exclude: com.canonical.certification::stress-tests/hibernate.* - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-server-20-04-stress unit: test plan -_name: CE-OEM-IOT - Stress tests for Ubuntu Server 20.04 +_name: CE-OEM-IOT - Stress only OEMQA tests for Ubuntu Server 20.04 _description: - Ubuntu Server QA test plan that includes all stress tests required for IoT devices + Ubuntu Server OEMQA test plan that includes all stress tests required for IoT devices include: nested_part: - com.canonical.certification::stress-iperf3-automated # keep if ethernet is supported com.canonical.certification::client-cert-iot-server-20-04-stress ce-oem-stress exclude: com.canonical.certification::stress-tests/hibernate.* +certification_status_overrides: + apply blocker to .* + diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-core.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-core.pxu index 5fb41f5b1a..65b01742a6 100644 --- a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-core.pxu +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem-full-core.pxu @@ -6,41 +6,47 @@ id: ce-oem-iot-ubuntucore-24 unit: test plan -_name: CE-OEM-IOT - Full manual + automated tests for Ubuntu Core 24 +_name: CE-OEM-IOT - Full manual + automated OEMQA tests for Ubuntu Core 24 _description: - Combined manual and automated test plans for IoT device running Ubuntu Core. + Combined manual and automated test plans for IoT devices running Ubuntu Core. include: nested_part: ce-oem-iot-ubuntucore-24-manual ce-oem-iot-ubuntucore-24-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-22 unit: test plan -_name: CE-OEM-IOT - Full manual + automated tests for Ubuntu Core 22 +_name: CE-OEM-IOT - Full manual + automated OEMQA tests for Ubuntu Core 22 _description: - Combined manual and automated test plans for IoT device running Ubuntu Core. + Combined manual and automated test plans for IoT devices running Ubuntu Core. include: nested_part: ce-oem-iot-ubuntucore-22-manual ce-oem-iot-ubuntucore-22-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-20 unit: test plan -_name: CE-OEM-IOT - Full manual + automated tests for Ubuntu Core 20 +_name: CE-OEM-IOT - Full manual + automated OEMQA tests for Ubuntu Core 20 _description: - Combined manual and automated test plans for IoT device running Ubuntu Core. + Combined manual and automated test plans for IoT devices running Ubuntu Core. include: nested_part: ce-oem-iot-ubuntucore-20-manual ce-oem-iot-ubuntucore-20-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-24-manual unit: test plan -_name: CE-OEM-IOT - Manual only QA tests for Ubuntu Core 24 +_name: CE-OEM-IOT - Manual only OEMQA tests for Ubuntu Core 24 _description: - Ubuntu Core QA test plan for the IoT hardware. This test plan contains - all of the tests that require manual control of device hardware - or some other user input to complete. + Ubuntu Core OEMQA test plan for the IoT devices. This test plan contains + all of the tests that require manual control of device hardware + or some other user input to complete. estimated_duration: 3600 include: com.canonical.certification::disk/encryption/check-fde-tpm # keep if FDE with TPM is enabled @@ -51,15 +57,16 @@ nested_part: after-suspend-ce-oem-manual exclude: com.canonical.certification::ethernet/wol_S4_.* - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-22-manual unit: test plan -_name: CE-OEM-IOT - Manual only QA tests for Ubuntu Core 22 +_name: CE-OEM-IOT - Manual only OEMQA tests for Ubuntu Core 22 _description: - Ubuntu Core QA test plan for the IoT hardware. This test plan contains - all of the tests that require manual control of device hardware - or some other user input to complete. + Ubuntu Core OEMQA test plan for the IoT devices. This test plan contains + all of the tests that require manual control of device hardware + or some other user input to complete. estimated_duration: 3600 include: com.canonical.certification::disk/encryption/check-fde-tpm # keep if FDE with TPM is enabled @@ -70,15 +77,16 @@ nested_part: after-suspend-ce-oem-manual exclude: com.canonical.certification::ethernet/wol_S4_.* - +certification_status_overrides: + apply blocker to .* id: ce-oem-ubuntucore-20-manual unit: test plan -_name: CE-OEM-IOT - Manual only QA tests for Ubuntu Core 20 +_name: CE-OEM-IOT - Manual only OEMQA tests for Ubuntu Core 20 _description: - Ubuntu Core QA test plan for the IoT hardware. This test plan contains - all of the tests that require manual control of device hardware - or some other user input to complete. + Ubuntu Core OEMQA test plan for the IoT devices. This test plan contains + all of the tests that require manual control of device hardware + or some other user input to complete. estimated_duration: 3600 include: com.canonical.certification::disk/encryption/check-fde-tpm # keep if FDE with TPM is enabled @@ -89,14 +97,15 @@ nested_part: after-suspend-ce-oem-manual exclude: com.canonical.certification::ethernet/wol_S4_.* - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-24-automated unit: test plan -_name: CE-OEM-IOT - Automated only QA tests for Ubuntu Core 24 +_name: CE-OEM-IOT - Automated only OEMQA tests for Ubuntu Core 24 _description: - Ubuntu Core QA test plan for the IoT hardware. This test plan contains - all of the automated tests used to validate the IoT device. + Ubuntu Core OEMQA test plan for the IoT devices. This test plan contains + all of the automated tests used to validate the IoT devices. include: com.canonical.certification::image/model-grade com.canonical.certification::miscellanea/secure_boot_mode_.* # keep if secure boot is enabled @@ -106,14 +115,15 @@ nested_part: com.canonical.certification::client-cert-iot-ubuntucore-24-automated after-suspend-ce-oem-automated exclude: - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-22-automated unit: test plan -_name: CE-OEM-IOT - Automated only QA tests for Ubuntu Core 22 +_name: CE-OEM-IOT - Automated only OEMQA tests for Ubuntu Core 22 _description: - Ubuntu Core QA test plan for the IoT hardware. This test plan contains - all of the automated tests used to validate the IoT device. + Ubuntu Core OEMQA test plan for the IoT devices. This test plan contains + all of the automated tests used to validate the IoT devices. include: com.canonical.certification::image/model-grade com.canonical.certification::miscellanea/secure_boot_mode_.* # keep if secure boot is enabled @@ -123,14 +133,15 @@ nested_part: com.canonical.certification::client-cert-iot-ubuntucore-22-automated after-suspend-ce-oem-automated exclude: - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-20-automated unit: test plan -_name: CE-OEM-IOT - Automated only QA tests for Ubuntu Core 20 +_name: CE-OEM-IOT - Automated only OEMQA tests for Ubuntu Core 20 _description: - Ubuntu Core QA test plan for the IoT hardware. This test plan contains - all of the automated tests used to validate the IoT device. + Ubuntu Core OEMQA test plan for the IoT devices. This test plan contains + all of the automated tests used to validate the IoT devices. include: com.canonical.certification::image/model-grade com.canonical.certification::miscellanea/secure_boot_mode_.* # keep if secure boot is enabled @@ -140,45 +151,48 @@ nested_part: com.canonical.certification::client-cert-iot-ubuntucore-20-automated after-suspend-ce-oem-automated exclude: - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-24-stress unit: test plan -_name: CE-OEM-IOT - Stress tests for Ubuntu Core 24 +_name: CE-OEM-IOT - Stress only OEMQA tests for Ubuntu Core 24 _description: - Ubuntu Core QA test plan that includes all stress tests required for IoT devices + Ubuntu Core OEMQA test plan that includes all stress tests required for IoT devices include: nested_part: - com.canonical.certification::stress-iperf3-automated # keep if ethernet is supported com.canonical.certification::client-cert-iot-ubuntucore-24-stress ce-oem-stress exclude: com.canonical.certification::stress-tests/hibernate.* - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-22-stress unit: test plan -_name: CE-OEM-IOT - Stress tests for Ubuntu Core 22 +_name: CE-OEM-IOT - Stress only OEMQA tests for Ubuntu Core 22 _description: - Ubuntu Core QA test plan that includes all stress tests required for IoT devices + Ubuntu Core OEMQA test plan that includes all stress tests required for IoT devices include: nested_part: - com.canonical.certification::stress-iperf3-automated # keep if ethernet is supported com.canonical.certification::client-cert-iot-ubuntucore-22-stress ce-oem-stress exclude: com.canonical.certification::stress-tests/hibernate.* - +certification_status_overrides: + apply blocker to .* id: ce-oem-iot-ubuntucore-20-stress unit: test plan -_name: CE-OEM-IOT - Stress tests for Ubuntu Core 20 +_name: CE-OEM-IOT - Stress only OEMQA tests for Ubuntu Core 20 _description: - Ubuntu Core QA test plan that includes all stress tests required for IoT devices + Ubuntu Core OEMQA test plan that includes all stress tests required for IoT devices include: nested_part: - com.canonical.certification::stress-iperf3-automated # keep if ethernet is supported com.canonical.certification::client-cert-iot-ubuntucore-20-stress ce-oem-stress exclude: com.canonical.certification::stress-tests/hibernate.* +certification_status_overrides: + apply blocker to .* + diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu index f408c33d78..5128b12c79 100644 --- a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu @@ -6,23 +6,25 @@ id: ce-oem-full unit: test plan -_name: CE-OEM - Full manual + automated tests for Ubuntu Core and Classic +_name: CE-OEM - Full manual + automated OEMQA tests for Ubuntu Core and Classic _description: - Combined manual and automated test plans for Ubuntu Core and Classic devices. + Combined manual and automated test plans for Ubuntu Core and Classic devices. include: nested_part: ce-oem-manual ce-oem-automated after-suspend-ce-oem-manual after-suspend-ce-oem-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-manual unit: test plan -_name: CE-OEM - Manual only QA tests for Ubuntu Core and Classic +_name: CE-OEM - Manual only OEMQA tests for Ubuntu Core and Classic _description: - Ubuntu Core and Classic QA test plan for the hardware. This test plan contains - all of the tests that require manual control of device hardware - or some other user input to complete. + Ubuntu Core and Classic OEMQA test plan for the hardware. This test plan contains + all of the tests that require manual control of device hardware + or some other user input to complete. estimated_duration: 3600 include: nested_part: @@ -46,13 +48,15 @@ nested_part: ce-oem-iio-sensors-manual ce-oem-digital-io-manual ce-oem-secure-boot-manual +certification_status_overrides: + apply blocker to .* id: ce-oem-automated unit: test plan -_name: CE-OEM - Automated only QA tests for Ubuntu Core and Classic +_name: CE-OEM - Automated only OEMQA tests for Ubuntu Core and Classic _description: - Ubuntu Core and Classic QA test plan for the hardware. This test plan contains - all of the automated tests used to validate the device. + Ubuntu Core and Classic OEMQA test plan for the hardware. This test plan contains + all of the automated tests used to validate the device. estimated_duration: 120 include: nested_part: @@ -84,14 +88,16 @@ nested_part: com.canonical.certification::led-indicator-auto before-suspend-ce-oem-spi-automated ce-oem-gadget-automated +certification_status_overrides: + apply blocker to .* id: after-suspend-ce-oem-manual unit: test plan -_name: CE-OEM - Manual only QA tests for Ubuntu Core and Classic +_name: CE-OEM - Manual only OEMQA after suspend tests for Ubuntu Core and Classic _description: - Ubuntu Core and Classic QA test plan for the hardware. This test plan contains - all of the tests that require manual control of device hardware - or some other user input to complete. + Ubuntu Core and Classic OEMQA test plan for the hardware. This test plan contains + all of the after suspend tests that require manual control of device hardware + or some other user input to complete. estimated_duration: 3600 include: nested_part: @@ -113,13 +119,15 @@ nested_part: com.canonical.certification::after-suspend-led-indicator-manual after-suspend-ce-oem-iio-sensors-manual after-suspend-ce-oem-digital-io-manual +certification_status_overrides: + apply blocker to .* id: after-suspend-ce-oem-automated unit: test plan -_name: CE-OEM - Automated only QA tests for Ubuntu Core and Classic +_name: CE-OEM - Automated only OEMQA after suspend tests for Ubuntu Core and Classic _description: - Ubuntu Core and Classic QA test plan for the hardware. This test plan contains - all of the automated tests used to validate the device. + Ubuntu Core and Classic OEMQA test plan for the hardware. This test plan contains + all of the after suspend automated tests used to validate the device. estimated_duration: 120 include: nested_part: @@ -146,14 +154,19 @@ nested_part: after-suspend-ce-oem-iio-sensors-automated after-suspend-ce-oem-digital-io-automated after-suspend-ce-oem-spi-automated +certification_status_overrides: + apply blocker to .* id: ce-oem-stress unit: test plan -_name: CE-OEM - stress only QA tests for Ubuntu Core and Classic +_name: CE-OEM - Stress only OEMQA tests for Ubuntu Core and Classic _description: - Ubuntu Core and Classic QA test plan that includes all stress tests required for devices + Ubuntu Core and Classic OEMQA test plan that includes all stress tests required for devices estimated_duration: 3600 include: nested_part: ce-oem-cold-boot-stress-test-by-pdu ce-oem-ethernet-tcp-stress +certification_status_overrides: + apply blocker to .* + From 0952eb2ee2ac70a4d13d8595e057dafe85c27d8d Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Tue, 24 Sep 2024 10:50:12 +0200 Subject: [PATCH 07/25] Use the retry decorator in WiFi tests (Bugfix) (#1488) * Add the function name when running a retry This helps investigating issues when more than one functions are using the retry decorator. * Rename retry mock function * Refactor open_connection() and secured_connection() Both function are doing exactly the same thing, the only difference being the command used. Create a connection() function, and call it from open_connection() and secured_connection(). Compared to the previous implementation, the connection() function is more linear and does not rely on boolean return codes to decide what to do. Instead, it's trying to do things and expect exceptions to be raised in case of problem (this is why it's using subprocess.run(..., check=True) for instance) * Let exceptions bubble up instead of catching them * Use the retry decorator Parts of the wifi_nmcli_test.py script was implementing a basic retry mechanism. Remove this to use the retry decorator instead. In addition, raise exceptions rather than trying to catching them, or using boolean return codes. * Adjust unit tests * Add mock_retry to the unit tests for the main function This is to avoid waiting for the actual retry decorator implementation. * Fix unit test to work with Python 3.5 * Fix long line that failed the flake8 tests * Fix perform_ping_test to raise an exception * Improve unit test coverage * Update providers/base/bin/wifi_nmcli_test.py Co-authored-by: Massimiliano * Update providers/base/bin/wifi_nmcli_test.py Co-authored-by: Massimiliano * Cleanup tests and unused variables to make automated checks happy * Replace run() with check_call() * Add contributors in script header * Fix more unit tests --------- Co-authored-by: Massimiliano --- .../checkbox_support/helpers/retry.py | 6 +- providers/base/bin/wifi_nmcli_test.py | 198 ++++++-------- providers/base/tests/test_wifi_nmcli_test.py | 245 ++++++++++-------- 3 files changed, 210 insertions(+), 239 deletions(-) diff --git a/checkbox-support/checkbox_support/helpers/retry.py b/checkbox-support/checkbox_support/helpers/retry.py index 7c667f0bd4..0108f3ff48 100644 --- a/checkbox-support/checkbox_support/helpers/retry.py +++ b/checkbox-support/checkbox_support/helpers/retry.py @@ -47,7 +47,9 @@ def run_with_retry(f, max_attempts, delay, *args, **kwargs): "delay should be at least 1 ({} was used)".format(delay) ) for attempt in range(1, max_attempts + 1): - attempt_string = "Attempt {}/{}".format(attempt, max_attempts) + attempt_string = "Attempt {}/{} (function '{}')".format( + attempt, max_attempts, f.__name__ + ) print() print("=" * len(attempt_string)) print(attempt_string) @@ -97,7 +99,7 @@ def fake_run_with_retry(f, max_attempts, delay, *args, **kwargs): return f(*args, **kwargs) -mock_timeout = functools.partial( +mock_retry = functools.partial( patch, "checkbox_support.helpers.retry.run_with_retry", new=fake_run_with_retry, diff --git a/providers/base/bin/wifi_nmcli_test.py b/providers/base/bin/wifi_nmcli_test.py index d4b2981fc9..185cf0cfe7 100755 --- a/providers/base/bin/wifi_nmcli_test.py +++ b/providers/base/bin/wifi_nmcli_test.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -# Copyright 2017-2019 Canonical Ltd. +# Copyright 2017-2024 Canonical Ltd. # All rights reserved. # # Written by: # Jonathan Cave # Taihsiang Ho +# Isaac Yang +# Pierre Equoy # # wireless connection tests using nmcli @@ -15,11 +17,11 @@ import os import subprocess as sp import sys -import time import shlex from packaging import version as version_parser +from checkbox_support.helpers.retry import retry from gateway_ping_test import ping @@ -89,13 +91,10 @@ def turn_down_nm_connections(): for name, value in connections.items(): uuid = value["uuid"] print("Turn down connection", name) - try: - cmd = "nmcli c down {}".format(uuid) - print_cmd(cmd) - sp.call(shlex.split(cmd)) - print("{} {} is down now".format(name, uuid)) - except sp.CalledProcessError as e: - print("Can't down {}: {}".format(uuid, str(e))) + cmd = "nmcli c down {}".format(uuid) + print_cmd(cmd) + sp.check_call(shlex.split(cmd)) + print("{} {} is down now".format(name, uuid)) print() @@ -105,26 +104,18 @@ def delete_test_ap_ssid_connection(): if "TEST_CON" not in connections: print("No TEST_CON connection found, nothing to delete") return - try: - cmd = "nmcli c delete TEST_CON" - print_cmd(cmd) - sp.call(shlex.split(cmd)) - print("TEST_CON is deleted") - except Exception as e: - print("Can't delete TEST_CON : {}".format(str(e))) + cmd = "nmcli c delete TEST_CON" + print_cmd(cmd) + sp.check_call(shlex.split(cmd)) + print("TEST_CON is deleted") +@retry(max_attempts=5, delay=60) def device_rescan(): print_head("Calling a rescan") cmd = "nmcli d wifi rescan" print_cmd(cmd) - retcode = sp.call(shlex.split(cmd)) - if retcode != 0: - # Most often the rescan request fails because NM has itself started - # a scan in recent past, we should let these operations complete before - # attempting a connection - print("Scan request failed, allow other operations to complete (15s)") - time.sleep(15) + sp.check_call(shlex.split(cmd)) print() @@ -183,48 +174,59 @@ def perform_ping_test(interface): if target: count = 5 result = ping(target, interface, count, 10) - if result["received"] == count: - return True + if result["received"] != count: + raise ValueError( + "{} packets expected but only {} received".format( + count, result["received"] + ) + ) - return False +@retry(max_attempts=5, delay=1) +def wait_for_connected(interface, essid): + cmd = ( + "nmcli -m tabular -t -f GENERAL.STATE,GENERAL.CONNECTION " + "d show {}".format(interface) + ) + print_cmd(cmd) + output = sp.check_output(shlex.split(cmd), universal_newlines=True) + print(output) + state, ssid = output.strip().splitlines() -def wait_for_connected(interface, essid, max_wait=5): - connected = False - attempts = 0 - while not connected and attempts < max_wait: - cmd = ( - "nmcli -m tabular -t -f GENERAL.STATE,GENERAL.CONNECTION " - "d show {}".format(interface) - ) - print_cmd(cmd) - output = sp.check_output(shlex.split(cmd)) - state, ssid = output.decode(sys.stdout.encoding).strip().splitlines() + if state.startswith("100") and ssid == essid: + print("Reached connected state with ESSID: {}".format(essid)) + elif ssid != essid: + error_msg = ( + "ERROR: did not reach connected state with ESSID: {}\n" + "ESSID mismatch:\n Excepted:{}\n Actually:{}" + ).format(essid, ssid, essid) + raise SystemExit(error_msg) + elif not state.startswith("100"): + error_msg = "State is not connected: {}".format(state) + raise SystemExit(error_msg) + print() - if state.startswith("100") and ssid == essid: - connected = True - break - time.sleep(1) - attempts += 1 +def connection(cmd, device): + print_head("Connection attempt") + print_cmd(cmd) + sp.check_call(shlex.split(cmd)) - if connected: - print("Reached connected state with ESSID: {}".format(essid)) - else: - print( - "ERROR: did not reach connected state with ESSID: {}".format(essid) - ) - if ssid != essid: - print( - "ESSID mismatch:\n Excepted:{}\n Actually:{}".format( - ssid, essid - ) - ) - if not state.startswith("100"): - print("State is not connected: {}".format(state)) + # Make sure the connection is brought up + turn_up_connection("TEST_CON") - print() - return connected + print_head("Ensure interface is connected") + wait_for_connected(device, "TEST_CON") + + print_head("Display address") + print_address_info(device) + + print_head("Display route table") + print_route_info() + + print_head("Perform a ping test") + perform_ping_test(device) + print("Connection test passed\n") def open_connection(args): @@ -233,7 +235,6 @@ def open_connection(args): # ipv6.method ignore : I believe that NM can report the device as Connected # if an IPv6 address is setup. This should ensure in # this test we are using IPv4 - print_head("Connection attempt") cmd = ( "nmcli c add con-name TEST_CON " "ifname {} " @@ -244,31 +245,7 @@ def open_connection(args): "ipv4.dhcp-timeout 30 " "ipv6.method ignore".format(args.device, args.essid) ) - print_cmd(cmd) - sp.call(shlex.split(cmd)) - - # Make sure the connection is brought up - turn_up_connection("TEST_CON") - - print_head("Ensure interface is connected") - reached_connected = wait_for_connected(args.device, "TEST_CON") - - rc = 1 - if reached_connected: - print_head("Display address") - print_address_info(args.device) - - print_head("Display route table") - print_route_info() - - print_head("Perform a ping test") - test_result = perform_ping_test(args.device) - if test_result: - rc = 0 - print("Connection test passed\n") - else: - print("Connection test failed\n") - return rc + connection(cmd, args.device) def secured_connection(args): @@ -277,7 +254,6 @@ def secured_connection(args): # ipv6.method ignore : I believe that NM can report the device as Connected # if an IPv6 address is setup. This should ensure in # this test we are using IPv4 - print_head("Connection attempt") cmd = ( "nmcli c add con-name TEST_CON " "ifname {} " @@ -292,31 +268,7 @@ def secured_connection(args): args.device, args.essid, args.exchange, args.psk ) ) - print_cmd(cmd) - sp.call(shlex.split(cmd)) - - # Make sure the connection is brought up - turn_up_connection("TEST_CON") - - print_head("Ensure interface is connected") - reached_connected = wait_for_connected(args.device, "TEST_CON") - - rc = 1 - if reached_connected: - print_head("Display address") - print_address_info(args.device) - - print_head("Display route table") - print_route_info() - - print_head("Perform a ping test") - test_result = perform_ping_test(args.device) - if test_result: - rc = 0 - print("Connection test passed\n") - else: - print("Connection test failed\n") - return rc + connection(cmd, args.device) def hotspot(args): @@ -414,6 +366,7 @@ def parser_args(): return args +@retry(max_attempts=5, delay=60) def main(): args = parser_args() start_time = datetime.datetime.now() @@ -424,32 +377,33 @@ def main(): if args.test_type == "scan": if not aps_dict: - print("Failed to find any APs") - return 1 + raise SystemExit("Failed to find any access point.") else: print("Found {} access points".format(len(aps_dict))) - return 0 + return if not aps_dict: - print("Targed access points: {} not found".format(args.essid)) - return 1 + raise SystemExit( + "Targed access point: {} not found".format(args.essid) + ) if args.func: delete_test_ap_ssid_connection() activated_uuid = get_nm_activate_connection() turn_down_nm_connections() try: - result = args.func(args) + args.func(args) + except Exception: + # The test is not required to run as root, but root access is + # required for journal access so only attempt to print when e.g. + # running under Remote + if os.geteuid() == 0: + print_journal_entries(start_time) + raise finally: turn_up_connection(activated_uuid) delete_test_ap_ssid_connection() - # The test is not required to run as root, but root access is required for - # journal access so only attempt to print when e.g. running under Remote - if result != 0 and os.geteuid() == 0: - print_journal_entries(start_time) - return result - if __name__ == "__main__": sys.exit(main()) diff --git a/providers/base/tests/test_wifi_nmcli_test.py b/providers/base/tests/test_wifi_nmcli_test.py index 6d7d9e3dba..b84730023e 100644 --- a/providers/base/tests/test_wifi_nmcli_test.py +++ b/providers/base/tests/test_wifi_nmcli_test.py @@ -16,8 +16,12 @@ # along with this program. If not, see . +import subprocess import unittest from unittest.mock import patch, call, MagicMock + +from checkbox_support.helpers.retry import mock_retry + from wifi_nmcli_test import ( legacy_nmcli, _get_nm_wireless_connections, @@ -29,8 +33,12 @@ list_aps, show_aps, wait_for_connected, + connection, open_connection, secured_connection, + print_address_info, + print_route_info, + perform_ping_test, hotspot, parser_args, main, @@ -136,34 +144,37 @@ def test_no_connections_to_turn_down( self.assertEqual(get_connections_mock.call_count, 1) sp_call_mock.assert_not_called() - @patch("wifi_nmcli_test.sp.call") + @patch("wifi_nmcli_test.sp.check_call") @patch( "wifi_nmcli_test._get_nm_wireless_connections", return_value={"Wireless1": {"uuid": "uuid1", "state": "activated"}}, ) def test_turn_down_single_connection( - self, get_connections_mock, sp_call_mock + self, get_connections_mock, sp_check_call_mock ): turn_down_nm_connections() self.assertEqual(get_connections_mock.call_count, 1) - sp_call_mock.assert_called_once_with("nmcli c down uuid1".split()) + sp_check_call_mock.assert_called_once_with( + "nmcli c down uuid1".split() + ) - @patch( - "wifi_nmcli_test.sp.call", side_effect=Exception("Error turning down") - ) + @patch("wifi_nmcli_test.sp.check_call") @patch( "wifi_nmcli_test._get_nm_wireless_connections", return_value={"Wireless1": {"uuid": "uuid1", "state": "activated"}}, ) def test_turn_down_single_connection_with_exception( - self, get_connections_mock, sp_call_mock + self, get_connections_mock, sp_check_call_mock ): - with self.assertRaises(Exception): + sp_check_call_mock.side_effect = subprocess.CalledProcessError("", 1) + with self.assertRaises(subprocess.CalledProcessError): turn_down_nm_connections() self.assertEqual(get_connections_mock.call_count, 1) - sp_call_mock.assert_called_once_with("nmcli c down uuid1".split()) + sp_check_call_mock.assert_called_once_with( + "nmcli c down uuid1".split() + ) - @patch("wifi_nmcli_test.sp.call") + @patch("wifi_nmcli_test.sp.check_call") @patch( "wifi_nmcli_test._get_nm_wireless_connections", return_value={ @@ -172,7 +183,7 @@ def test_turn_down_single_connection_with_exception( }, ) def test_turn_down_multiple_connections( - self, get_connections_mock, sp_call_mock + self, get_connections_mock, sp_check_call_mock ): turn_down_nm_connections() self.assertEqual(get_connections_mock.call_count, 1) @@ -180,11 +191,11 @@ def test_turn_down_multiple_connections( call("nmcli c down uuid1".split()), call("nmcli c down uuid2".split()), ] - sp_call_mock.assert_has_calls(calls, any_order=True) + sp_check_call_mock.assert_has_calls(calls, any_order=True) class TestDeleteTestApSsidConnection(unittest.TestCase): - @patch("wifi_nmcli_test.sp.call", return_value=0) + @patch("wifi_nmcli_test.sp.check_call") @patch( "wifi_nmcli_test._get_nm_wireless_connections", return_value={ @@ -193,27 +204,11 @@ class TestDeleteTestApSsidConnection(unittest.TestCase): ) @patch("wifi_nmcli_test.print") def test_delete_existing_test_con( - self, print_mock, get_nm_wireless_connections_mock, sp_call_mock + self, print_mock, get_nm_wireless_connections_mock, sp_check_call_mock ): delete_test_ap_ssid_connection() print_mock.assert_called_with("TEST_CON is deleted") - @patch("wifi_nmcli_test.sp.call", side_effect=Exception("Deletion failed")) - @patch( - "wifi_nmcli_test._get_nm_wireless_connections", - return_value={ - "TEST_CON": {"uuid": "uuid-test", "state": "deactivated"} - }, - ) - @patch("wifi_nmcli_test.print") - def test_delete_test_con_exception( - self, print_mock, get_nm_wireless_connections_mock, sp_call_mock - ): - delete_test_ap_ssid_connection() - print_mock.assert_called_with( - "Can't delete TEST_CON : Deletion failed" - ) - @patch("wifi_nmcli_test._get_nm_wireless_connections", return_value={}) @patch("wifi_nmcli_test.print") def test_no_test_con_to_delete( @@ -276,158 +271,175 @@ def test_show_aps_multiple_aps(self, mock_print): mock_print.assert_has_calls(expected_calls, any_order=True) +@mock_retry() class TestWaitForConnected(unittest.TestCase): @patch("wifi_nmcli_test.print_cmd", new=MagicMock()) - @patch("wifi_nmcli_test.time.sleep", MagicMock(return_value=None)) @patch( "wifi_nmcli_test.sp.check_output", MagicMock( side_effect=[ - b"30:disconnected\nTestESSID", - b"100:connected\nTestESSID", + "100:connected\nTestESSID\n", ] ), ) def test_wait_for_connected_success(self): interface = "wlan0" essid = "TestESSID" - self.assertTrue(wait_for_connected(interface, essid)) + wait_for_connected(interface, essid) @patch( "wifi_nmcli_test.sp.check_output", - MagicMock(return_value=b"30:disconnected\nTestESSID"), + MagicMock(return_value="30:disconnected\nTestESSID\n"), ) @patch("wifi_nmcli_test.print_cmd", new=MagicMock()) - @patch("wifi_nmcli_test.time.sleep", MagicMock(return_value=None)) def test_wait_for_connected_failure_due_to_timeout(self): interface = "wlan0" essid = "TestESSID" - self.assertFalse(wait_for_connected(interface, essid, max_wait=3)) + with self.assertRaises(SystemExit): + wait_for_connected(interface, essid) @patch( "wifi_nmcli_test.sp.check_output", - MagicMock(return_value=b"100:connected\nWrongESSID"), + MagicMock(return_value="100:connected\nWrongESSID\n"), ) @patch("wifi_nmcli_test.print_cmd", new=MagicMock()) - @patch("wifi_nmcli_test.time.sleep", MagicMock(return_value=None)) def test_wait_for_connected_failure_due_to_essid_mismatch(self): interface = "wlan0" essid = "TestESSID" - self.assertFalse(wait_for_connected(interface, essid)) + with self.assertRaises(SystemExit): + wait_for_connected(interface, essid) -class TestOpenConnection(unittest.TestCase): - @patch("wifi_nmcli_test.sp.call", new=MagicMock()) - @patch("wifi_nmcli_test.print_address_info", new=MagicMock()) - @patch("wifi_nmcli_test.print_route_info", new=MagicMock()) - @patch("wifi_nmcli_test.turn_up_connection", new=MagicMock()) - @patch("wifi_nmcli_test.print_head", new=MagicMock()) - @patch("wifi_nmcli_test.print_cmd", new=MagicMock()) - @patch("wifi_nmcli_test.perform_ping_test", return_value=True) - @patch("wifi_nmcli_test.wait_for_connected", return_value=True) - def test_open_connection_success( - self, perform_ping_test_mock, wait_for_connected_mock - ): - args = type("", (), {})() - args.device = "wlan0" - args.essid = "TestESSID" - rc = open_connection(args) - self.assertEqual(rc, 0) - - @patch("wifi_nmcli_test.sp.call", new=MagicMock()) - @patch("wifi_nmcli_test.print_address_info", new=MagicMock()) - @patch("wifi_nmcli_test.print_route_info", new=MagicMock()) - @patch("wifi_nmcli_test.turn_up_connection", new=MagicMock()) - @patch("wifi_nmcli_test.print_head", new=MagicMock()) - @patch("wifi_nmcli_test.print_cmd", new=MagicMock()) - @patch("wifi_nmcli_test.perform_ping_test", MagicMock(return_value=False)) - @patch("wifi_nmcli_test.wait_for_connected", MagicMock(return_value=True)) - def test_open_connection_failed_ping(self): - args = type("", (), {})() - args.device = "wlan0" - args.essid = "TestESSID" - rc = open_connection(args) - self.assertEqual(rc, 1) - - @patch("wifi_nmcli_test.sp.call", new=MagicMock()) - @patch("wifi_nmcli_test.print_head", new=MagicMock()) - @patch("wifi_nmcli_test.print_cmd", new=MagicMock()) - @patch("wifi_nmcli_test.turn_up_connection", new=MagicMock()) - @patch("wifi_nmcli_test.wait_for_connected", MagicMock(return_value=False)) - def test_open_connection_failed_to_connect(self): - args = type("", (), {})() - args.device = "wlan0" - args.essid = "TestESSID" - rc = open_connection(args) - self.assertEqual(rc, 1) - - -class TestSecuredConnection(unittest.TestCase): - @patch("wifi_nmcli_test.sp.call", new=MagicMock()) +class TestConnection(unittest.TestCase): + @patch("wifi_nmcli_test.sp.check_call", new=MagicMock()) @patch("wifi_nmcli_test.print_route_info", new=MagicMock()) @patch("wifi_nmcli_test.print_address_info", new=MagicMock()) @patch("wifi_nmcli_test.turn_up_connection", new=MagicMock()) @patch("wifi_nmcli_test.sp.check_output", new=MagicMock()) @patch("wifi_nmcli_test.wait_for_connected", return_value=True) @patch("wifi_nmcli_test.perform_ping_test", return_value=True) - def test_secured_connection_success( + def test_connection_success( self, perform_ping_test_mock, wait_for_connected_mock, ): - args = type("", (), {})() - args.device = "wlan0" - args.essid = "TestSSID" - args.exchange = "wpa-psk" - args.psk = "password123" - rc = secured_connection(args) - self.assertEqual(rc, 0) + cmd = "test" + device = "wlan0" + connection(cmd, device) wait_for_connected_mock.assert_called_with("wlan0", "TEST_CON") perform_ping_test_mock.assert_called_with("wlan0") - @patch("wifi_nmcli_test.sp.call", new=MagicMock()) + @patch("wifi_nmcli_test.sp.check_call", new=MagicMock()) @patch("wifi_nmcli_test.print_route_info", new=MagicMock()) @patch("wifi_nmcli_test.print_address_info", new=MagicMock()) @patch("wifi_nmcli_test.turn_up_connection", new=MagicMock()) @patch("wifi_nmcli_test.sp.check_output", new=MagicMock()) - @patch("wifi_nmcli_test.wait_for_connected", return_value=False) + @patch("wifi_nmcli_test.wait_for_connected") @patch("wifi_nmcli_test.perform_ping_test", return_value=False) - def test_secured_connection_fail_to_connect( + def test_connection_fail_to_connect( self, perform_ping_test_mock, wait_for_connected_mock, ): - args = type("", (), {})() - args.device = "wlan0" - args.essid = "TestSSID" - args.exchange = "wpa-psk" - args.psk = "password123" - rc = secured_connection(args) - self.assertEqual(rc, 1) + wait_for_connected_mock.side_effect = SystemExit() + cmd = "test" + device = "wlan0" + with self.assertRaises(SystemExit): + connection(cmd, device) wait_for_connected_mock.assert_called_with("wlan0", "TEST_CON") perform_ping_test_mock.assert_not_called() - @patch("wifi_nmcli_test.sp.call", new=MagicMock()) + @patch("wifi_nmcli_test.sp.run") @patch("wifi_nmcli_test.print_route_info", new=MagicMock()) @patch("wifi_nmcli_test.print_address_info", new=MagicMock()) @patch("wifi_nmcli_test.turn_up_connection", new=MagicMock()) @patch("wifi_nmcli_test.sp.check_output", new=MagicMock()) @patch("wifi_nmcli_test.wait_for_connected", return_value=False) @patch("wifi_nmcli_test.perform_ping_test", return_value=True) - def test_secured_connection_command_failure( + def test_connection_command_failure( self, perform_ping_test_mock, wait_for_connected_mock, + sp_run_mock, ): - args = type("", (), {})() + sp_run_mock.side_effect = subprocess.CalledProcessError("", 1) + cmd = "test" + device = "wlan0" + with self.assertRaises(subprocess.CalledProcessError): + connection(cmd, device) + wait_for_connected_mock.assert_not_called() + perform_ping_test_mock.assert_not_called() + + +class TestOpenConnection(unittest.TestCase): + @patch("wifi_nmcli_test.connection") + def test_open_connection(self, mock_connection): + """ + Check that security-related parameters are absent in the command + sent to connection(). + """ + args = MagicMock() args.device = "wlan0" args.essid = "TestSSID" args.exchange = "wpa-psk" args.psk = "password123" - rc = secured_connection(args) - self.assertEqual(rc, 1) - wait_for_connected_mock.assert_called_with("wlan0", "TEST_CON") - perform_ping_test_mock.assert_not_called() + open_connection(args) + self.assertNotIn("wifi-sec", mock_connection.call_args[0][0]) + + +class TestSecuredConnection(unittest.TestCase): + @patch("wifi_nmcli_test.connection") + def test_secured_connection(self, mock_connection): + """ + Check that security-related parameters are present in the command + sent to connection(). + """ + args = MagicMock() + args.device = "wlan0" + args.essid = "TestSSID" + args.exchange = "wpa-psk" + args.psk = "password123" + secured_connection(args) + self.assertIn("wifi-sec", mock_connection.call_args[0][0]) + + +class TestDeviceRescan(unittest.TestCase): + @patch("wifi_nmcli_test.sp.check_call") + def test_device_rescan_success(self, mock_sp_check_call): + device_rescan() + + +class TestPrintAddressInfo(unittest.TestCase): + @patch("wifi_nmcli_test.sp.call") + def test_print_address_info_success(self, mock_sp_call): + print_address_info("wlan0") + + +class TestPrintRouteInfo(unittest.TestCase): + @patch("wifi_nmcli_test.sp.call") + def test_print_route_info_success(self, mock_sp_call): + print_route_info() + + +@patch("wifi_nmcli_test.ping") +@patch("wifi_nmcli_test.sp.check_output") +class TestPerformPingTest(unittest.TestCase): + def test_perform_ping_test_success(self, mock_check_output, mock_ping): + mock_ping.return_value = { + "transmitted": 5, + "received": 5, + "pct_loss": 0, + } + perform_ping_test("wlan0") + + def test_perform_ping_test_failure(self, mock_check_output, mock_ping): + mock_ping.return_value = { + "transmitted": 5, + "received": 0, + "pct_loss": 0, + } + with self.assertRaises(ValueError): + perform_ping_test("wlan0") class TestParserArgs(unittest.TestCase): @@ -480,6 +492,7 @@ def test_parser_args_ap(self): self.assertEqual(args.band, "5GHz") +@mock_retry() class TestMainFunction(unittest.TestCase): @patch("wifi_nmcli_test.delete_test_ap_ssid_connection", new=MagicMock()) @@ -496,7 +509,8 @@ def test_main_scan_no_aps_found( list_aps_mock, get_nm_activate_connection_mock, ): - main() + with self.assertRaises(SystemExit): + main() @patch("wifi_nmcli_test.delete_test_ap_ssid_connection", new=MagicMock()) @patch("wifi_nmcli_test.turn_down_nm_connections", new=MagicMock()) @@ -540,7 +554,8 @@ def test_main_open_no_aps_found( list_aps_mock, get_nm_activate_connection_mock, ): - main() + with self.assertRaises(SystemExit): + main() @patch("wifi_nmcli_test.delete_test_ap_ssid_connection", new=MagicMock()) @patch( From 23593091de828904b1cf58b5ebcf26870fa7cd8e Mon Sep 17 00:00:00 2001 From: Isaac Yang <47034756+seankingyang@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:43:17 +0700 Subject: [PATCH 08/25] Fix the combination of backup_loc path (Bugfix) (#1498) Fix the combinatio of back_loc path --- providers/base/bin/wifi_nmcli_backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/base/bin/wifi_nmcli_backup.py b/providers/base/bin/wifi_nmcli_backup.py index 22d8e64427..88a9422f3f 100755 --- a/providers/base/bin/wifi_nmcli_backup.py +++ b/providers/base/bin/wifi_nmcli_backup.py @@ -78,7 +78,7 @@ def save_connections(keyfile_list): continue print(" Found file {}".format(f)) - basedir = Path(f).parent + basedir = Path(f).parent.relative_to("/") backup_loc = SAVE_DIR / basedir os.makedirs(backup_loc, exist_ok=True) From 7182b163a4231d5e32e1bbbe495e21f7147cfcdb Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Tue, 24 Sep 2024 15:24:31 +0200 Subject: [PATCH 09/25] Switch from mobilebroadband to wwan nested parts for cert test plans (bugfix) (#1496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Switch from mobilebroadband to wwan nested parts for cert test plans Following QA changes in #821¹ regarding the replacement of mobilebroadband-cert-.* nested part for their wwan-.* counterparts, the same is applied for: - client-cert-desktop-24-04 (it was missed in #821 due to bad timing) - client-cert-odm-desktop-2?-04 (to match what's being tested by the QA team) client-cert-odm-desktop-18-04 was not included due to the fact that Ubuntu 18.04 has seen EOL and therefore its related test plan is not actively maintained anymore. ¹ https://github.com/canonical/checkbox/pull/821 * Add after-suspend-wwan-manual for homogeneity * Remove superseded jobs from [after-suspend-]wwan-manual nested parts wwan/detect-manual and wwan/check-sim-present-manual have an automated version available. wwan/gsm-connection-interrupted-manual depends on the manual versions of the detection and SIM verification jobs, and is therefore removed as well, as we have an automated connection job. --- providers/base/units/wwan/jobs.pxu | 2 +- providers/base/units/wwan/test-plan.pxu | 6 ------ .../units/client-cert-desktop-20-04.pxu | 1 + .../units/client-cert-desktop-22-04.pxu | 1 + .../units/client-cert-desktop-24-04.pxu | 7 ++++--- .../units/client-cert-odm-desktop-20-04.pxu | 5 +++-- .../units/client-cert-odm-desktop-22-04.pxu | 5 +++-- .../units/client-cert-odm-desktop-24-04.pxu | 5 +++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/providers/base/units/wwan/jobs.pxu b/providers/base/units/wwan/jobs.pxu index e169edaf26..ca269e82e1 100644 --- a/providers/base/units/wwan/jobs.pxu +++ b/providers/base/units/wwan/jobs.pxu @@ -226,7 +226,7 @@ _steps: _verification: Was the connection revived after removing the Faraday bag? estimate_duration: 120s -flags: also-after-suspend +flags: also-after-suspend category_id: wwan imports: from com.canonical.plainbox import manifest requires: diff --git a/providers/base/units/wwan/test-plan.pxu b/providers/base/units/wwan/test-plan.pxu index 6b419e1426..0e2959c076 100644 --- a/providers/base/units/wwan/test-plan.pxu +++ b/providers/base/units/wwan/test-plan.pxu @@ -48,15 +48,9 @@ unit: test plan _name: Manual wwan tests _description: Manual wwan tests for Snappy Ubuntu Core devices include: - wwan/detect-manual - wwan/check-sim-present-manual - wwan/gsm-connection-interrupted-manual id: after-suspend-wwan-manual unit: test plan _name: Manual wwan tests (after suspend) _description: Manual wwan tests for Snappy Ubuntu Core devices include: - after-suspend-wwan/detect-manual - after-suspend-wwan/check-sim-present-manual - after-suspend-wwan/gsm-connection-interrupted-manual diff --git a/providers/certification-client/units/client-cert-desktop-20-04.pxu b/providers/certification-client/units/client-cert-desktop-20-04.pxu index 6540283c67..7e62e289a8 100644 --- a/providers/certification-client/units/client-cert-desktop-20-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-20-04.pxu @@ -65,6 +65,7 @@ nested_part: after-suspend-keys-cert-full after-suspend-led-cert-full after-suspend-mediacard-cert-full + after-suspend-wwan-manual after-suspend-optical-cert-full after-suspend-touchpad-cert-full after-suspend-touchscreen-cert-manual diff --git a/providers/certification-client/units/client-cert-desktop-22-04.pxu b/providers/certification-client/units/client-cert-desktop-22-04.pxu index 69b489120e..e9435a14f6 100644 --- a/providers/certification-client/units/client-cert-desktop-22-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-22-04.pxu @@ -68,6 +68,7 @@ nested_part: after-suspend-keys-cert-full after-suspend-led-cert-full after-suspend-mediacard-cert-full + after-suspend-wwan-manual after-suspend-ethernet-manual after-suspend-networking-manual after-suspend-optical-cert-full diff --git a/providers/certification-client/units/client-cert-desktop-24-04.pxu b/providers/certification-client/units/client-cert-desktop-24-04.pxu index 3b30f7b98e..b0770701ec 100644 --- a/providers/certification-client/units/client-cert-desktop-24-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-24-04.pxu @@ -35,7 +35,7 @@ nested_part: led-cert-manual mediacard-cert-manual memory-manual - mobilebroadband-cert-manual + wwan-manual ethernet-cert-manual networking-cert-manual optical-cert-manual @@ -60,6 +60,7 @@ nested_part: after-suspend-keys-cert-full after-suspend-led-cert-full after-suspend-mediacard-cert-full + after-suspend-wwan-manual after-suspend-ethernet-manual after-suspend-networking-manual after-suspend-optical-cert-full @@ -102,7 +103,7 @@ nested_part: mediacard-cert-automated mediacard-automated memory-automated - mobilebroadband-cert-automated + wwan-automated ethernet-cert-automated networking-cert-automated optical-cert-automated @@ -123,7 +124,7 @@ nested_part: after-suspend-cpu-cert-automated after-suspend-input-cert-automated after-suspend-disk-cert-automated - after-suspend-mobilebroadband-cert-automated + after-suspend-wwan-automated after-suspend-ethernet-cert-automated after-suspend-networking-cert-automated after-suspend-optical-cert-automated diff --git a/providers/certification-client/units/client-cert-odm-desktop-20-04.pxu b/providers/certification-client/units/client-cert-odm-desktop-20-04.pxu index ddbde29bc9..1f296b52fc 100644 --- a/providers/certification-client/units/client-cert-odm-desktop-20-04.pxu +++ b/providers/certification-client/units/client-cert-odm-desktop-20-04.pxu @@ -22,7 +22,7 @@ nested_part: keys-cert-manual led-cert-manual mediacard-cert-manual - mobilebroadband-cert-manual + wwan-manual ethernet-cert-manual networking-cert-manual optical-cert-manual @@ -121,7 +121,7 @@ nested_part: keys-cert-automated led-cert-automated mediacard-cert-automated - mobilebroadband-cert-automated + wwan-automated ethernet-cert-automated networking-cert-automated optical-cert-automated @@ -157,6 +157,7 @@ nested_part: # after-suspend-usb-cert-automated # after-suspend-usb3-cert-automated # after-suspend-usb-c-cert-automated # only usb-c-cert-full + after-suspend-wwan-automated after-suspend-wireless-cert-automated # Automated Tests # The following tests are purely automated and/or lenghty stress tests. diff --git a/providers/certification-client/units/client-cert-odm-desktop-22-04.pxu b/providers/certification-client/units/client-cert-odm-desktop-22-04.pxu index 5ae0d40a91..a52095177e 100644 --- a/providers/certification-client/units/client-cert-odm-desktop-22-04.pxu +++ b/providers/certification-client/units/client-cert-odm-desktop-22-04.pxu @@ -22,7 +22,7 @@ nested_part: keys-cert-manual led-cert-manual mediacard-cert-manual - mobilebroadband-cert-manual + wwan-manual ethernet-cert-manual networking-cert-manual optical-cert-manual @@ -122,7 +122,7 @@ nested_part: keys-cert-automated led-cert-automated mediacard-cert-automated - mobilebroadband-cert-automated + wwan-automated ethernet-cert-automated networking-cert-automated optical-cert-automated @@ -158,6 +158,7 @@ nested_part: # after-suspend-usb-cert-automated # after-suspend-usb3-cert-automated # after-suspend-usb-c-cert-automated # only usb-c-cert-full + after-suspend-wwan-automated after-suspend-wireless-cert-automated # Automated Tests # The following tests are purely automated and/or lenghty stress tests. diff --git a/providers/certification-client/units/client-cert-odm-desktop-24-04.pxu b/providers/certification-client/units/client-cert-odm-desktop-24-04.pxu index 0545a1fa64..40748a925d 100644 --- a/providers/certification-client/units/client-cert-odm-desktop-24-04.pxu +++ b/providers/certification-client/units/client-cert-odm-desktop-24-04.pxu @@ -22,7 +22,7 @@ nested_part: led-cert-manual mediacard-cert-manual mei-manual - mobilebroadband-cert-manual + wwan-manual ethernet-cert-manual networking-cert-manual optical-cert-manual @@ -118,7 +118,7 @@ nested_part: led-cert-automated mediacard-cert-automated mei-automated - mobilebroadband-cert-automated + wwan-automated ethernet-cert-automated networking-cert-automated optical-cert-automated @@ -150,6 +150,7 @@ nested_part: # after-suspend-usb-cert-automated # after-suspend-usb3-cert-automated # after-suspend-usb-c-cert-automated # only usb-c-cert-full + after-suspend-wwan-automated after-suspend-wireless-cert-automated # Automated Tests # The following tests are purely automated and/or lenghty stress tests. From aacb28b7080cf3fc731018ce08e4a5d7a6fc4ce4 Mon Sep 17 00:00:00 2001 From: Massimiliano Date: Wed, 25 Sep 2024 11:31:01 +0200 Subject: [PATCH 10/25] Fix envvars + propagation in lab pipeline (infra) (#1495) * Set debian frontend and str shutil params * Make the build even less interactive Minor: group steps together in the GH output * Remove wrongly escaped arg * Inherit the whole env + non interactive * Missing -E to sudo * Enable exit on error * Use DEBIAN_FRONTEND inline --- tools/lab_dispatch/build_install_deb.py | 43 ++++++++++++++++++++++--- tools/lab_dispatch/generic_source.yaml | 1 + 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/tools/lab_dispatch/build_install_deb.py b/tools/lab_dispatch/build_install_deb.py index 285f193e7a..d580ac3ba2 100644 --- a/tools/lab_dispatch/build_install_deb.py +++ b/tools/lab_dispatch/build_install_deb.py @@ -1,10 +1,28 @@ #!/usr/bin/env python3 +import os import shutil import argparse +import functools import subprocess +from copy import copy from pathlib import Path -from contextlib import suppress + + +def github_group(group_name): + def _group_printing(f): + @functools.wraps(f) + def _f(*args, **kwargs): + # flush is necessary to make this appear above subprocess output + print("::group::" + group_name, flush=True) + try: + return f(*args, **kwargs) + finally: + print("::endgroup::", flush=True) + + return _f + + return _group_printing def parse_args(): @@ -16,22 +34,38 @@ def parse_args(): def prepare_repo(repo_root, package_path): - shutil.move(package_path / "debian", repo_root) + shutil.move(str(package_path / "debian"), str(repo_root)) +@github_group("Installing build depends") def install_build_depends(repo_root): subprocess.check_call( - ["sudo", "apt-get", "-y", "build-dep", "."], cwd=repo_root + [ + "sudo", + "DEBIAN_FRONTEND=noninteractive", + "apt-get", + "-y", + "build-dep", + ".", + ], + cwd=repo_root, ) +@github_group("Building the packages") def build_package(repo_root): + environ = copy(os.environ) + environ["DEBIAN_FRONTEND"] = "noninteractive" + # -Pnocheck: skip tests as we have a pipeline that builds/tests debian # packages and doing them on slow machines is a big waste of # time/resources - subprocess.check_call(["dpkg-buildpackage", "-Pnocheck"], cwd=repo_root) + subprocess.check_call( + ["dpkg-buildpackage", "-Pnocheck"], cwd=repo_root, env=environ + ) +@github_group("Installing the packages") def install_local_package(repo_root, deb_name_glob): # we build in path.parent, dpkg will put the result on .. package_list = list(repo_root.parent.glob(deb_name_glob)) @@ -40,6 +74,7 @@ def install_local_package(repo_root, deb_name_glob): subprocess.check_call( [ "sudo", + "DEBIAN_FRONTEND=noninteractive", "apt-get", "--fix-broken", "-y", diff --git a/tools/lab_dispatch/generic_source.yaml b/tools/lab_dispatch/generic_source.yaml index 394031bd28..e2350cf80a 100644 --- a/tools/lab_dispatch/generic_source.yaml +++ b/tools/lab_dispatch/generic_source.yaml @@ -15,6 +15,7 @@ test_data: #!/usr/bin/env bash set -x + set -e # input arguments CHECKBOX_REVISION=$INPUT_CHECKBOX_REVISION From da70aef83574bce4c58cda524d2a40654482cfce Mon Sep 17 00:00:00 2001 From: Fernando Bravo <39527354+fernando79513@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:29:10 +0200 Subject: [PATCH 11/25] Sd card not tested (BugFix) (#1485) * Fixed storage test for mediacard+usb combo * Updated mediacard jobs to use the mediacard_combo storage test --- .../checkbox_support/scripts/run_watcher.py | 56 +++++++++- .../tests/test_run_watcher.py | 104 ++++++++++++++++++ providers/base/units/mediacard/jobs.pxu | 14 +-- 3 files changed, 165 insertions(+), 9 deletions(-) diff --git a/checkbox-support/checkbox_support/scripts/run_watcher.py b/checkbox-support/checkbox_support/scripts/run_watcher.py index ede02a8ee7..ef34a3df9f 100644 --- a/checkbox-support/checkbox_support/scripts/run_watcher.py +++ b/checkbox-support/checkbox_support/scripts/run_watcher.py @@ -310,6 +310,48 @@ def _parse_journal_line(self, line_str): self.action = "removal" +class MediacardComboStorage(StorageWatcher): + """ + MediacardComboStorage handles the insertion and removal of sd, sdhc, mmc + etc., for devices that combine mediacard and usb storage. + """ + + def __init__(self, *args): + super().__init__(*args) + self.mounted_partition = None + self.action = None + self.device = None + self.address = None + self.number = None + self.driver = None + + def _validate_insertion(self): + if self.mounted_partition and self.action == "insertion": + logger.info("usable partition: {}".format(self.mounted_partition)) + logger.info("Device: {}".format(self.device)) + if self.address: + logger.info("Address: {}".format(self.address)) + if self.driver: + logger.info("Controller: {}".format(self.driver)) + if self.number: + logger.info("Number: {}".format(self.number)) + logger.info("Mediacard insertion test passed.") + self.test_passed = True + + def _validate_removal(self): + if self.action == "removal": + logger.info("Mediacard removal test passed.") + self.test_passed = True + + def _parse_journal_line(self, line_str): + """ + Gets one of the lines from the journal and updates values by calling + the parsers of MediacardStorage and USBStorage. + """ + MediacardStorage._parse_journal_line(self, line_str) + USBStorage._parse_journal_line(self, line_str) + + class ThunderboltStorage(StorageWatcher): """ ThunderboltStorage handles the insertion and removal of thunderbolt @@ -368,8 +410,14 @@ def parse_args(): ) parser.add_argument( "storage_type", - choices=["usb2", "usb3", "mediacard", "thunderbolt"], - help=("usb2, usb3, mediacard or thunderbolt"), + choices=[ + "usb2", + "usb3", + "mediacard", + "mediacard_combo", + "thunderbolt", + ], + help=("usb2, usb3, mediacard, mediacard_combo or thunderbolt"), ) parser.add_argument( "--zapper-usb-address", @@ -389,6 +437,10 @@ def main(): ) elif args.storage_type == "mediacard": watcher = MediacardStorage(args.storage_type, args.zapper_usb_address) + elif args.storage_type == "mediacard_combo": + watcher = MediacardComboStorage( + args.storage_type, args.zapper_usb_address + ) else: watcher = USBStorage(args.storage_type, args.zapper_usb_address) diff --git a/checkbox-support/checkbox_support/tests/test_run_watcher.py b/checkbox-support/checkbox_support/tests/test_run_watcher.py index 52803efe18..3f71e0ec99 100644 --- a/checkbox-support/checkbox_support/tests/test_run_watcher.py +++ b/checkbox-support/checkbox_support/tests/test_run_watcher.py @@ -27,6 +27,7 @@ StorageWatcher, USBStorage, MediacardStorage, + MediacardComboStorage, ThunderboltStorage, parse_args, main, @@ -428,6 +429,91 @@ def test_mediacard_storage_parse_journal_line(self): MediacardStorage._parse_journal_line(mock_mediacard_storage, line_str) self.assertEqual(mock_mediacard_storage.action, None) + def test_mediacard_combo_storage_init(self): + mediacard_combo_storage = MediacardComboStorage( + "mediacard", "zapper_addr" + ) + self.assertEqual(mediacard_combo_storage.storage_type, "mediacard") + self.assertEqual( + mediacard_combo_storage.zapper_usb_address, "zapper_addr" + ) + self.assertIsNone(mediacard_combo_storage.mounted_partition) + + def test_mediacard_combo_storage_validate_insertion(self): + mock_mediacard_combo_storage = MagicMock() + mock_mediacard_combo_storage.mounted_partition = "mmcblk0p1" + mock_mediacard_combo_storage.action = "insertion" + mock_mediacard_combo_storage.device = "SD" + mock_mediacard_combo_storage.address = "123456" + mock_mediacard_combo_storage.driver = None + mock_mediacard_combo_storage.number = None + + MediacardComboStorage._validate_insertion(mock_mediacard_combo_storage) + self.assertEqual(mock_mediacard_combo_storage.test_passed, True) + + mock_mediacard_combo_storage = MagicMock() + mock_mediacard_combo_storage.mounted_partition = "sda1" + mock_mediacard_combo_storage.action = "insertion" + mock_mediacard_combo_storage.device = "SD" + mock_mediacard_combo_storage.driver = "xhci_hcd" + mock_mediacard_combo_storage.number = 1 + mock_mediacard_combo_storage.address = None + + MediacardComboStorage._validate_insertion(mock_mediacard_combo_storage) + self.assertEqual(mock_mediacard_combo_storage.test_passed, True) + + def test_mediacard_combo_storage_validate_removal(self): + mock_mediacard_combo_storage = MagicMock() + mock_mediacard_combo_storage.action = "removal" + + MediacardComboStorage._validate_removal(mock_mediacard_combo_storage) + self.assertEqual(mock_mediacard_combo_storage.test_passed, True) + + def test_mediacard_combo_storage_no_insertion(self): + mock_mediacard_combo_storage = MagicMock() + mock_mediacard_combo_storage.mounted_partition = None + mock_mediacard_combo_storage.action = "" + MediacardComboStorage._validate_insertion(mock_mediacard_combo_storage) + + def test_mediacard_combo_storage_no_removal(self): + mock_mediacard_combo_storage = MagicMock() + mock_mediacard_combo_storage.action = "" + MediacardComboStorage._validate_removal(mock_mediacard_combo_storage) + + def test_mediacard_combo_storage_parse_journal_line(self): + line_str = "mmcblk0: p1" + mock_mediacard_combo_storage = MagicMock() + MediacardComboStorage._parse_journal_line( + mock_mediacard_combo_storage, line_str + ) + self.assertEqual( + mock_mediacard_combo_storage.mounted_partition, "mmcblk0p1" + ) + + line_str = "new SD card at address 123456" + mock_mediacard_combo_storage = MagicMock() + MediacardComboStorage._parse_journal_line( + mock_mediacard_combo_storage, line_str + ) + self.assertEqual(mock_mediacard_combo_storage.action, "insertion") + self.assertEqual(mock_mediacard_combo_storage.device, "SD") + self.assertEqual(mock_mediacard_combo_storage.address, "123456") + + line_str = "card 123456 removed" + mock_mediacard_combo_storage = MagicMock() + MediacardComboStorage._parse_journal_line( + mock_mediacard_combo_storage, line_str + ) + self.assertEqual(mock_mediacard_combo_storage.action, "removal") + + line_str = "Invalid line" + mock_mediacard_combo_storage = MagicMock() + mock_mediacard_combo_storage.action = None + MediacardComboStorage._parse_journal_line( + mock_mediacard_combo_storage, line_str + ) + self.assertEqual(mock_mediacard_combo_storage.action, None) + def test_thunderbolt_storage_init(self): thunderbolt_storage = ThunderboltStorage("thunderbolt", "zapper_addr") self.assertEqual(thunderbolt_storage.storage_type, "thunderbolt") @@ -559,6 +645,24 @@ def test_main_mediacard(self, mock_parse_args, mock_mediacard): # check that the watcher is an MediacardStorage object self.assertIsInstance(watcher, MediacardStorage) + @patch( + "checkbox_support.scripts.run_watcher.MediacardComboStorage", + spec=MediacardComboStorage, + ) + @patch("checkbox_support.scripts.run_watcher.parse_args") + def test_main_mediacard_combo(self, mock_parse_args, mock_mediacard): + mock_parse_args.return_value = argparse.Namespace( + testcase="insertion", + storage_type="mediacard_combo", + zapper_usb_address=None, + ) + main() + self.assertEqual(mock_mediacard.call_count, 1) + # get the watcher object from main + watcher = mock_mediacard.return_value + # check that the watcher is an MediacardComboStorage object + self.assertIsInstance(watcher, MediacardComboStorage) + @patch( "checkbox_support.scripts.run_watcher.ThunderboltStorage", spec=ThunderboltStorage, diff --git a/providers/base/units/mediacard/jobs.pxu b/providers/base/units/mediacard/jobs.pxu index 244f9173fb..9dcb3f13f7 100644 --- a/providers/base/units/mediacard/jobs.pxu +++ b/providers/base/units/mediacard/jobs.pxu @@ -5,7 +5,7 @@ category_id: com.canonical.plainbox::mediacard id: mediacard/mmc-storage-manual estimated_duration: 120 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo imports: from com.canonical.plainbox import manifest requires: manifest.has_card_reader == 'True' @@ -36,7 +36,7 @@ id: mediacard/sdhc-storage-manual flags: also-after-suspend estimated_duration: 30.0 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo imports: from com.canonical.plainbox import manifest requires: manifest.has_card_reader == 'True' @@ -67,7 +67,7 @@ category_id: com.canonical.plainbox::mediacard id: mediacard/cf-storage-manual estimated_duration: 120 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo imports: from com.canonical.plainbox import manifest requires: manifest.has_card_reader == 'True' @@ -97,7 +97,7 @@ category_id: com.canonical.plainbox::mediacard id: mediacard/sdxc-storage-manual estimated_duration: 120 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo imports: from com.canonical.plainbox import manifest requires: manifest.has_card_reader == 'True' @@ -127,7 +127,7 @@ category_id: com.canonical.plainbox::mediacard id: mediacard/ms-storage-manual estimated_duration: 120 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo imports: from com.canonical.plainbox import manifest requires: manifest.has_card_reader == 'True' @@ -158,7 +158,7 @@ category_id: com.canonical.plainbox::mediacard id: mediacard/msp-storage-manual estimated_duration: 120 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo user: root imports: from com.canonical.plainbox import manifest requires: @@ -190,7 +190,7 @@ category_id: com.canonical.plainbox::mediacard id: mediacard/xd-storage-manual estimated_duration: 120 command: - checkbox-support-run_watcher storage mediacard + checkbox-support-run_watcher storage mediacard_combo imports: from com.canonical.plainbox import manifest requires: manifest.has_card_reader == 'True' From 09cf5e3c2bdd34c5c3f82224cdcdeefb4470f5a9 Mon Sep 17 00:00:00 2001 From: Paolo Gentili Date: Wed, 25 Sep 2024 13:22:30 +0200 Subject: [PATCH 12/25] Robot/Keyboard test doesn't work at first attempt (Bugfix) (#1459) Fix: zapper keyboard test doesn't work at first attempt --- providers/base/bin/zapper_keyboard_test.py | 21 ++++++++++++-- providers/base/tests/test_zapper_keyboard.py | 29 ++++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/providers/base/bin/zapper_keyboard_test.py b/providers/base/bin/zapper_keyboard_test.py index 0aec015c2f..a9290762c9 100755 --- a/providers/base/bin/zapper_keyboard_test.py +++ b/providers/base/bin/zapper_keyboard_test.py @@ -9,11 +9,20 @@ import struct import sys import threading +import time from enum import Enum from pathlib import Path from checkbox_support.scripts.zapper_proxy import zapper_run # noqa: E402 +ROBOT_INIT = """ +*** Settings *** +Library libraries/ZapperHid.py + +*** Test Cases *** +Do nothing + Log Re-configure HID device +""" ROBOT_TESTCASE_COMBO = """ *** Settings *** @@ -124,8 +133,12 @@ def get_zapper_kbd_device(): """ zapper_kbd = "usb-Canonical_Zapper_main_board_123456*-event-kbd" - for file_path in Path("/dev/input/by-id/").glob(zapper_kbd): - return str(file_path) + start = time.time() + for _ in range(5): + for file_path in Path("/dev/input/by-id/").glob(zapper_kbd): + print(time.time() - start) + return str(file_path) + time.sleep(1) raise FileNotFoundError("Cannot find Zapper Keyboard.") @@ -138,6 +151,9 @@ def main(argv): if len(argv) != 2: raise SystemExit("Usage: {} ".format(argv[0])) + # A simple robot-run to initialize the Zapper HID device + zapper_run(argv[1], "robot_run", ROBOT_INIT.encode(), {}, {}) + try: zapper_kbd = get_zapper_kbd_device() except FileNotFoundError as exc: @@ -150,7 +166,6 @@ def main(argv): listener = KeyboardListener(zapper_kbd, events.append) listener.start() - zapper_run(argv[1], "reset_hid_state") try: assert_key_combo(argv[1], events) assert_type_string(argv[1], events) diff --git a/providers/base/tests/test_zapper_keyboard.py b/providers/base/tests/test_zapper_keyboard.py index 35ff03196c..65a2c1bdba 100644 --- a/providers/base/tests/test_zapper_keyboard.py +++ b/providers/base/tests/test_zapper_keyboard.py @@ -60,6 +60,7 @@ def test_main_no_args(self): with self.assertRaises(SystemExit): zapper_keyboard_test.main([1]) + @patch("time.sleep", Mock()) @patch("zapper_keyboard_test.Path") def test_get_zapper_kbd_device(self, mock_path): """ @@ -67,11 +68,17 @@ def test_get_zapper_kbd_device(self, mock_path): keyboard device when it's the only Zapper HID device. """ - mock_path.return_value.glob.return_value = [ - Path( - "/dev/input/by-id/" - "usb-Canonical_Zapper_main_board_123456-event-kbd", - ) + mock_path.return_value.glob.side_effect = [ + [], + [], + [], + [], + [ + Path( + "/dev/input/by-id/" + "usb-Canonical_Zapper_main_board_123456-event-kbd", + ) + ], ] device = zapper_keyboard_test.get_zapper_kbd_device() self.assertEqual( @@ -80,6 +87,7 @@ def test_get_zapper_kbd_device(self, mock_path): "usb-Canonical_Zapper_main_board_123456-event-kbd", ) + @patch("time.sleep", Mock()) @patch("zapper_keyboard_test.Path") def test_get_zapper_kbd_device_if01(self, mock_path): """ @@ -100,6 +108,7 @@ def test_get_zapper_kbd_device_if01(self, mock_path): "usb-Canonical_Zapper_main_board_123456-if01-event-kbd", ) + @patch("time.sleep", Mock()) @patch("zapper_keyboard_test.Path") def test_get_zapper_kbd_device_not_found(self, mock_path): """ @@ -111,6 +120,7 @@ def test_get_zapper_kbd_device_not_found(self, mock_path): with self.assertRaises(FileNotFoundError): zapper_keyboard_test.get_zapper_kbd_device() + @patch("zapper_keyboard_test.zapper_run", Mock()) @patch("zapper_keyboard_test.get_zapper_kbd_device") def test_main_no_keyboard(self, mock_get_dev): """Check main exits with failure if Zapper keyboard is missing.""" @@ -118,6 +128,7 @@ def test_main_no_keyboard(self, mock_get_dev): with self.assertRaises(SystemExit): zapper_keyboard_test.main([1, 2]) + @patch("zapper_keyboard_test.zapper_run", Mock()) @patch("zapper_keyboard_test.get_zapper_kbd_device") @patch("os.access") def test_main_no_file_or_permission(self, mock_access, mock_get_dev): @@ -154,7 +165,13 @@ def test_main( mock_key.return_value.start.assert_called_once_with() mock_key.return_value.stop.assert_called_once_with() mock_key.return_value.join.assert_called_once_with() - mock_run.assert_called_once_with("127.0.0.1", "reset_hid_state") + mock_run.assert_called_once_with( + "127.0.0.1", + "robot_run", + zapper_keyboard_test.ROBOT_INIT.encode(), + {}, + {}, + ) mock_combo.side_effect = None mock_type.side_effect = AssertionError From 0d0f7211d2762b9e718207c4d3f7a1398fefbf90 Mon Sep 17 00:00:00 2001 From: Andrej Velichkovski <33193463+andrejvelichkovski@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:23:51 +0200 Subject: [PATCH 13/25] Add Lunar Lake CPUID (New) (#1504) Add Lunar Lake CPUID --- providers/base/bin/cpuid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/providers/base/bin/cpuid.py b/providers/base/bin/cpuid.py index fdbc0a3c93..6fa39842b5 100755 --- a/providers/base/bin/cpuid.py +++ b/providers/base/bin/cpuid.py @@ -209,7 +209,8 @@ def cpuid_to_human_friendly(cpuid: str) -> str: "Sierra Forest": ['0xa06f3'], "Granite Rapids": ['0xa06e0', '0xa06d0'], "Meteor Lake": ['0xa06a4'], - "Arrow Lake": ['0xc0660'] + "Arrow Lake": ['0xc0660'], + "Lunar Lake": ["0xb06d1"] } for key in CPUIDS.keys(): for value in CPUIDS[key]: From 572db70545f5d541973c4ea3b44f13ed54effc20 Mon Sep 17 00:00:00 2001 From: eugene-yujinwu <117058060+eugene-yujinwu@users.noreply.github.com> Date: Thu, 26 Sep 2024 22:04:47 +0800 Subject: [PATCH 14/25] Add wake-on-LAN manual test to the desktop test plan per customer's request (Bugfix) (#1455) * Add the wake-on-LAN manual test to the desktop test plan per customer's request * Add a manifest to check whether or not wake-on-LAN is supported on this device * Remove the resource job's check, let the tester decide if or not run the test using the new manifest * remove the cert-blocker label since this is a custmer requirement not Canonical's request --- providers/base/units/ethernet/jobs.pxu | 12 ++++++++++-- providers/base/units/ethernet/manifest.pxu | 5 +++++ providers/base/units/ethernet/test-plan.pxu | 10 ++++++++++ .../units/client-cert-desktop-24-04.pxu | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/providers/base/units/ethernet/jobs.pxu b/providers/base/units/ethernet/jobs.pxu index e16fd2582d..9e863190e8 100644 --- a/providers/base/units/ethernet/jobs.pxu +++ b/providers/base/units/ethernet/jobs.pxu @@ -237,6 +237,10 @@ template-resource: device template-filter: device.category == 'NETWORK' and device.mac != 'UNKNOWN' id: ethernet/wol_S5_{interface} template-id: ethernet/wol_S5_interface +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_ethernet_adapter == 'True' + manifest.has_ethernet_wake_on_lan_support== 'True' _summary: Wake on LAN (WOL) test from S5 - {interface} _purpose: Check that another system can wake the System Under Test (SUT) from S5 using the Ethernet port {interface} WOL function. @@ -275,8 +279,10 @@ _steps: _verification: Did the SUT wake up from S4? plugin: user-interact-verify +imports: from com.canonical.plainbox import manifest requires: - sleep.disk == 'supported' + manifest.has_ethernet_adapter == 'True' + manifest.has_ethernet_wake_on_lan_support== 'True' command: systemctl hibernate user: root category_id: com.canonical.plainbox::ethernet @@ -301,8 +307,10 @@ _steps: _verification: Did the SUT wake up from S3? plugin: user-interact-verify +imports: from com.canonical.plainbox import manifest requires: - sleep.mem == 'supported' + manifest.has_ethernet_adapter == 'True' + manifest.has_ethernet_wake_on_lan_support== 'True' command: systemctl suspend user: root category_id: com.canonical.plainbox::ethernet diff --git a/providers/base/units/ethernet/manifest.pxu b/providers/base/units/ethernet/manifest.pxu index 07b465497b..fa998647b8 100644 --- a/providers/base/units/ethernet/manifest.pxu +++ b/providers/base/units/ethernet/manifest.pxu @@ -2,3 +2,8 @@ unit: manifest entry id: has_ethernet_adapter _name: An Ethernet Port value-type: bool + +unit: manifest entry +id: has_ethernet_wake_on_lan_support +_name: Wake-on-LAN support through Ethernet port +value-type: bool \ No newline at end of file diff --git a/providers/base/units/ethernet/test-plan.pxu b/providers/base/units/ethernet/test-plan.pxu index 9099ce9f7f..39b8b64352 100644 --- a/providers/base/units/ethernet/test-plan.pxu +++ b/providers/base/units/ethernet/test-plan.pxu @@ -17,6 +17,16 @@ include: bootstrap_include: device +id: ethernet-wake-on-lan-cert-manual +unit: test plan +_name: Ethernet wake-on-LAN tests (manual) +_description: Ethernet wake-on-LAN tests (manual) +include: + ethernet/wol_S5_interface + ethernet/wol_S3_interface +bootstrap_include: + device + id: ethernet-cert-automated unit: test plan _name: Ethernet tests (automated) diff --git a/providers/certification-client/units/client-cert-desktop-24-04.pxu b/providers/certification-client/units/client-cert-desktop-24-04.pxu index b0770701ec..fb53fd5470 100644 --- a/providers/certification-client/units/client-cert-desktop-24-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-24-04.pxu @@ -70,6 +70,7 @@ nested_part: after-suspend-usb3-cert-full after-suspend-usb-c-cert-full # after-suspend-wireless-cert-full # auto only + ethernet-wake-on-lan-cert-manual info-attachment-cert-manual exclude: keys/hibernate From 679e16dfde9a6d06e0cb927f9260dc13f41d4a42 Mon Sep 17 00:00:00 2001 From: Paolo Gentili Date: Thu, 26 Sep 2024 16:19:31 +0200 Subject: [PATCH 15/25] Zapper canary testplan (New) (#1503) * add: zapper canary testplan * fix: don't fail when manifest cannot be fetched * change: replaced with a nested part instead --- providers/base/units/canary/test-plan.pxu | 11 +++++++++++ tools/lab_dispatch/generic_source.yaml | 2 ++ 2 files changed, 13 insertions(+) diff --git a/providers/base/units/canary/test-plan.pxu b/providers/base/units/canary/test-plan.pxu index 2fee500a7b..d84c885144 100644 --- a/providers/base/units/canary/test-plan.pxu +++ b/providers/base/units/canary/test-plan.pxu @@ -97,3 +97,14 @@ include: com.canonical.certification::after-suspend-ethernet/detect com.canonical.certification::after-suspend-ethernet/ping_.* com.canonical.certification::after-suspend-usb/storage-detect + +id: canary-zapper +unit: test plan +_name: Zapper release self-test (canary test plan) +_description: + This test plan is meant to run on Zapper versions that are candidates for + a release against the latest beta Checkbox. +estimated_duration: 5m +nested_part: + com.canonical.certification::zapper-enabled-automated +include: diff --git a/tools/lab_dispatch/generic_source.yaml b/tools/lab_dispatch/generic_source.yaml index e2350cf80a..c8c143d645 100644 --- a/tools/lab_dispatch/generic_source.yaml +++ b/tools/lab_dispatch/generic_source.yaml @@ -49,11 +49,13 @@ test_data: # retrieve manifest MANIFEST_FILE=manifest.conf + set +e fetch_manifest --manifest_file manifest.conf $CID $HEXR_DEVICE_SECURE_ID if [ $? -ne 0 ]; then echo "Using default manifest" MANIFEST_FILE=$RESOURCES_PATH/manifest.conf fi + set -e ### create checkbox launcher # first dump the location specific infos in the launcher From 536b5df4e4601f0820806b02ac74f67a60f9ca60 Mon Sep 17 00:00:00 2001 From: Fernando Bravo <39527354+fernando79513@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:53:11 +0200 Subject: [PATCH 16/25] Using "New USB device found" for all usbs (BugFix) (#1480) * Using "New USB device found" for all usbs * fixed black --- checkbox-support/checkbox_support/scripts/run_watcher.py | 2 +- .../checkbox_support/tests/test_run_watcher.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/checkbox-support/checkbox_support/scripts/run_watcher.py b/checkbox-support/checkbox_support/scripts/run_watcher.py index ef34a3df9f..2124d8ed21 100644 --- a/checkbox-support/checkbox_support/scripts/run_watcher.py +++ b/checkbox-support/checkbox_support/scripts/run_watcher.py @@ -238,7 +238,7 @@ def _parse_journal_line(self, line_str): ).group(1) # Look for insertion action - if "USB Mass Storage device detected" in line_str or "uas" in line_str: + if "New USB device found" in line_str: self.action = "insertion" # Look for removal action diff --git a/checkbox-support/checkbox_support/tests/test_run_watcher.py b/checkbox-support/checkbox_support/tests/test_run_watcher.py index 3f71e0ec99..865963d4fd 100644 --- a/checkbox-support/checkbox_support/tests/test_run_watcher.py +++ b/checkbox-support/checkbox_support/tests/test_run_watcher.py @@ -345,12 +345,7 @@ def test_usb_storage_parse_journal_line(self): USBStorage._parse_journal_line(mock_usb_storage, line_str) self.assertEqual(mock_usb_storage.driver, "xhci_hcd") - line_str = "USB Mass Storage device detected" - mock_usb_storage = MagicMock() - USBStorage._parse_journal_line(mock_usb_storage, line_str) - self.assertEqual(mock_usb_storage.action, "insertion") - - line_str = "kernel: scsi host0: uas" + line_str = "New USB device found" mock_usb_storage = MagicMock() USBStorage._parse_journal_line(mock_usb_storage, line_str) self.assertEqual(mock_usb_storage.action, "insertion") From 2d42b1ea24a44c91d97129197a239c0603c8d1a7 Mon Sep 17 00:00:00 2001 From: LiaoU3 <58060146+LiaoU3@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:59:28 +0800 Subject: [PATCH 17/25] Add testptp for TGPIO testing (Infra) (#1249) * Add testptp for tgpio testing * Add requirement for testptp to only be built on amd64 --- providers/base/src/EXECUTABLES | 1 + providers/base/src/Makefile | 8 +- providers/base/src/ptp_clock.h | 244 +++++++++++++ providers/base/src/testptp.c | 626 +++++++++++++++++++++++++++++++++ 4 files changed, 877 insertions(+), 2 deletions(-) create mode 100644 providers/base/src/ptp_clock.h create mode 100644 providers/base/src/testptp.c diff --git a/providers/base/src/EXECUTABLES b/providers/base/src/EXECUTABLES index 86276dba6c..4a96a39e4b 100644 --- a/providers/base/src/EXECUTABLES +++ b/providers/base/src/EXECUTABLES @@ -2,3 +2,4 @@ alsa_test clocktest threaded_memtest ptrace_test +testptp diff --git a/providers/base/src/Makefile b/providers/base/src/Makefile index 349448f71b..f7a0a02072 100644 --- a/providers/base/src/Makefile +++ b/providers/base/src/Makefile @@ -1,9 +1,13 @@ .PHONY: -all: alsa_test clocktest threaded_memtest ptrace_test +all: alsa_test clocktest threaded_memtest ptrace_test testptp + +ifeq ($(shell uname -m), x86_64) +all: testptp +endif .PHONY: clean clean: - rm -f alsa_test clocktest threaded_memtest ptrace_test + rm -f alsa_test clocktest threaded_memtest ptrace_test testptp threaded_memtest: CFLAGS += -pthread threaded_memtest: CFLAGS += -Wno-unused-but-set-variable diff --git a/providers/base/src/ptp_clock.h b/providers/base/src/ptp_clock.h new file mode 100644 index 0000000000..053b40d642 --- /dev/null +++ b/providers/base/src/ptp_clock.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * PTP 1588 clock support - user space interface + * + * Copyright (C) 2010 OMICRON electronics GmbH + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _PTP_CLOCK_H_ +#define _PTP_CLOCK_H_ + +#include +#include + +/* + * Bits of the ptp_extts_request.flags field: + */ +#define PTP_ENABLE_FEATURE (1<<0) +#define PTP_RISING_EDGE (1<<1) +#define PTP_FALLING_EDGE (1<<2) +#define PTP_STRICT_FLAGS (1<<3) +#define PTP_EXT_OFFSET (1<<4) +#define PTP_EXTTS_EDGES (PTP_RISING_EDGE | PTP_FALLING_EDGE) + +/* + * flag fields valid for the new PTP_EXTTS_REQUEST2 ioctl. + */ +#define PTP_EXTTS_VALID_FLAGS (PTP_ENABLE_FEATURE | \ + PTP_RISING_EDGE | \ + PTP_FALLING_EDGE | \ + PTP_STRICT_FLAGS | \ + PTP_EXT_OFFSET) + +/* + * flag fields valid for the original PTP_EXTTS_REQUEST ioctl. + * DO NOT ADD NEW FLAGS HERE. + */ +#define PTP_EXTTS_V1_VALID_FLAGS (PTP_ENABLE_FEATURE | \ + PTP_RISING_EDGE | \ + PTP_FALLING_EDGE) + +/* + * flag fields valid for the ptp_extts_event report. + */ +#define PTP_EXTTS_EVENT_VALID (PTP_ENABLE_FEATURE) + +/* + * Bits of the ptp_perout_request.flags field: + */ +#define PTP_PEROUT_ONE_SHOT (1<<0) +#define PTP_PEROUT_DUTY_CYCLE (1<<1) +#define PTP_PEROUT_PHASE (1<<2) + +/* + * flag fields valid for the new PTP_PEROUT_REQUEST2 ioctl. + */ +#define PTP_PEROUT_VALID_FLAGS (PTP_PEROUT_ONE_SHOT | \ + PTP_PEROUT_DUTY_CYCLE | \ + PTP_PEROUT_PHASE) + +/* + * No flags are valid for the original PTP_PEROUT_REQUEST ioctl + */ +#define PTP_PEROUT_V1_VALID_FLAGS (0) + +/* + * struct ptp_clock_time - represents a time value + * + * The sign of the seconds field applies to the whole value. The + * nanoseconds field is always unsigned. The reserved field is + * included for sub-nanosecond resolution, should the demand for + * this ever appear. + * + */ +struct ptp_clock_time { + __s64 sec; /* seconds */ + __u32 nsec; /* nanoseconds */ + __u32 reserved; +}; + +struct ptp_clock_caps { + int max_adj; /* Maximum frequency adjustment in parts per billon. */ + int n_alarm; /* Number of programmable alarms. */ + int n_ext_ts; /* Number of external time stamp channels. */ + int n_per_out; /* Number of programmable periodic signals. */ + int pps; /* Whether the clock supports a PPS callback. */ + int n_pins; /* Number of input/output pins. */ + /* Whether the clock supports precise system-device cross timestamps */ + int cross_timestamping; + /* Whether the clock supports adjust phase */ + int adjust_phase; + int max_phase_adj; /* Maximum phase adjustment in nanoseconds. */ + int rsv[11]; /* Reserved for future use. */ +}; + +struct ptp_extts_request { + unsigned int index; /* Which channel to configure. */ + unsigned int flags; /* Bit field for PTP_xxx flags. */ + unsigned int rsv[2]; /* Reserved for future use. */ +}; + +struct ptp_perout_request { + union { + /* + * Absolute start time. + * Valid only if (flags & PTP_PEROUT_PHASE) is unset. + */ + struct ptp_clock_time start; + /* + * Phase offset. The signal should start toggling at an + * unspecified integer multiple of the period, plus this value. + * The start time should be "as soon as possible". + * Valid only if (flags & PTP_PEROUT_PHASE) is set. + */ + struct ptp_clock_time phase; + }; + struct ptp_clock_time period; /* Desired period, zero means disable. */ + unsigned int index; /* Which channel to configure. */ + unsigned int flags; + union { + /* + * The "on" time of the signal. + * Must be lower than the period. + * Valid only if (flags & PTP_PEROUT_DUTY_CYCLE) is set. + */ + struct ptp_clock_time on; + /* Reserved for future use. */ + unsigned int rsv[4]; + }; +}; + +#define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */ + +struct ptp_sys_offset { + unsigned int n_samples; /* Desired number of measurements. */ + unsigned int rsv[3]; /* Reserved for future use. */ + /* + * Array of interleaved system/phc time stamps. The kernel + * will provide 2*n_samples + 1 time stamps, with the last + * one as a system time stamp. + */ + struct ptp_clock_time ts[2 * PTP_MAX_SAMPLES + 1]; +}; + +struct ptp_sys_offset_extended { + unsigned int n_samples; /* Desired number of measurements. */ + unsigned int rsv[3]; /* Reserved for future use. */ + /* + * Array of [system, phc, system] time stamps. The kernel will provide + * 3*n_samples time stamps. + */ + struct ptp_clock_time ts[PTP_MAX_SAMPLES][3]; +}; + +struct ptp_sys_offset_precise { + struct ptp_clock_time device; + struct ptp_clock_time sys_realtime; + struct ptp_clock_time sys_monoraw; + unsigned int rsv[4]; /* Reserved for future use. */ +}; + +enum ptp_pin_function { + PTP_PF_NONE, + PTP_PF_EXTTS, + PTP_PF_PEROUT, + PTP_PF_PHYSYNC, +}; + +struct ptp_pin_desc { + /* + * Hardware specific human readable pin name. This field is + * set by the kernel during the PTP_PIN_GETFUNC ioctl and is + * ignored for the PTP_PIN_SETFUNC ioctl. + */ + char name[64]; + /* + * Pin index in the range of zero to ptp_clock_caps.n_pins - 1. + */ + unsigned int index; + /* + * Which of the PTP_PF_xxx functions to use on this pin. + */ + unsigned int func; + /* + * The specific channel to use for this function. + * This corresponds to the 'index' field of the + * PTP_EXTTS_REQUEST and PTP_PEROUT_REQUEST ioctls. + */ + unsigned int chan; + /* + * Reserved for future use. + */ + unsigned int rsv[5]; +}; + +#define PTP_CLK_MAGIC '=' + +#define PTP_CLOCK_GETCAPS _IOR(PTP_CLK_MAGIC, 1, struct ptp_clock_caps) +#define PTP_EXTTS_REQUEST _IOW(PTP_CLK_MAGIC, 2, struct ptp_extts_request) +#define PTP_PEROUT_REQUEST _IOW(PTP_CLK_MAGIC, 3, struct ptp_perout_request) +#define PTP_ENABLE_PPS _IOW(PTP_CLK_MAGIC, 4, int) +#define PTP_SYS_OFFSET _IOW(PTP_CLK_MAGIC, 5, struct ptp_sys_offset) +#define PTP_PIN_GETFUNC _IOWR(PTP_CLK_MAGIC, 6, struct ptp_pin_desc) +#define PTP_PIN_SETFUNC _IOW(PTP_CLK_MAGIC, 7, struct ptp_pin_desc) +#define PTP_SYS_OFFSET_PRECISE \ + _IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise) +#define PTP_SYS_OFFSET_EXTENDED \ + _IOWR(PTP_CLK_MAGIC, 9, struct ptp_sys_offset_extended) + +#define PTP_CLOCK_GETCAPS2 _IOR(PTP_CLK_MAGIC, 10, struct ptp_clock_caps) +#define PTP_EXTTS_REQUEST2 _IOW(PTP_CLK_MAGIC, 11, struct ptp_extts_request) +#define PTP_PEROUT_REQUEST2 _IOW(PTP_CLK_MAGIC, 12, struct ptp_perout_request) +#define PTP_ENABLE_PPS2 _IOW(PTP_CLK_MAGIC, 13, int) +#define PTP_SYS_OFFSET2 _IOW(PTP_CLK_MAGIC, 14, struct ptp_sys_offset) +#define PTP_PIN_GETFUNC2 _IOWR(PTP_CLK_MAGIC, 15, struct ptp_pin_desc) +#define PTP_PIN_SETFUNC2 _IOW(PTP_CLK_MAGIC, 16, struct ptp_pin_desc) +#define PTP_SYS_OFFSET_PRECISE2 \ + _IOWR(PTP_CLK_MAGIC, 17, struct ptp_sys_offset_precise) +#define PTP_SYS_OFFSET_EXTENDED2 \ + _IOWR(PTP_CLK_MAGIC, 18, struct ptp_sys_offset_extended) +#define PTP_MASK_CLEAR_ALL _IO(PTP_CLK_MAGIC, 19) +#define PTP_MASK_EN_SINGLE _IOW(PTP_CLK_MAGIC, 20, unsigned int) + +struct ptp_extts_event { + struct ptp_clock_time t; /* Time event occurred. */ + unsigned int index; /* Which channel produced the event. */ + unsigned int flags; /* Event type. */ + unsigned int rsv[2]; /* Reserved for future use. */ +}; + +#endif diff --git a/providers/base/src/testptp.c b/providers/base/src/testptp.c new file mode 100644 index 0000000000..224ac7827f --- /dev/null +++ b/providers/base/src/testptp.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP 1588 clock support - User space test program + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ +#define _GNU_SOURCE +#define __SANE_USERSPACE_TYPES__ /* For PPC64, to get LL64 types */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ptp_clock.h" + +#define DEVICE "/dev/ptp0" + +#ifndef ADJ_SETOFFSET +#define ADJ_SETOFFSET 0x0100 +#endif + +#ifndef CLOCK_INVALID +#define CLOCK_INVALID -1 +#endif + +#define NSEC_PER_SEC 1000000000LL + +/* clock_adjtime is not available in GLIBC < 2.14 */ +#if !__GLIBC_PREREQ(2, 14) +#include +static int clock_adjtime(clockid_t id, struct timex *tx) +{ + return syscall(__NR_clock_adjtime, id, tx); +} +#endif + +static void show_flag_test(int rq_index, unsigned int flags, int err) +{ + printf("PTP_EXTTS_REQUEST%c flags 0x%08x : (%d) %s\n", + rq_index ? '1' + rq_index : ' ', + flags, err, strerror(errno)); + /* sigh, uClibc ... */ + errno = 0; +} + +static void do_flag_test(int fd, unsigned int index) +{ + struct ptp_extts_request extts_request; + unsigned long request[2] = { + PTP_EXTTS_REQUEST, + PTP_EXTTS_REQUEST2, + }; + unsigned int enable_flags[5] = { + PTP_ENABLE_FEATURE, + PTP_ENABLE_FEATURE | PTP_RISING_EDGE, + PTP_ENABLE_FEATURE | PTP_FALLING_EDGE, + PTP_ENABLE_FEATURE | PTP_RISING_EDGE | PTP_FALLING_EDGE, + PTP_ENABLE_FEATURE | (PTP_EXTTS_VALID_FLAGS + 1), + }; + int err, i, j; + + memset(&extts_request, 0, sizeof(extts_request)); + extts_request.index = index; + + for (i = 0; i < 2; i++) { + for (j = 0; j < 5; j++) { + extts_request.flags = enable_flags[j]; + err = ioctl(fd, request[i], &extts_request); + show_flag_test(i, extts_request.flags, err); + + extts_request.flags = 0; + err = ioctl(fd, request[i], &extts_request); + } + } +} + +static clockid_t get_clockid(int fd) +{ +#define CLOCKFD 3 + return (((unsigned int) ~fd) << 3) | CLOCKFD; +} + +static long ppb_to_scaled_ppm(int ppb) +{ + /* + * The 'freq' field in the 'struct timex' is in parts per + * million, but with a 16 bit binary fractional field. + * Instead of calculating either one of + * + * scaled_ppm = (ppb / 1000) << 16 [1] + * scaled_ppm = (ppb << 16) / 1000 [2] + * + * we simply use double precision math, in order to avoid the + * truncation in [1] and the possible overflow in [2]. + */ + return (long) (ppb * 65.536); +} + +static int64_t pctns(struct ptp_clock_time *t) +{ + return t->sec * NSEC_PER_SEC + t->nsec; +} + +static void usage(char *progname) +{ + fprintf(stderr, + "usage: %s [options]\n" + " -c query the ptp clock's capabilities\n" + " -d name device to open\n" + " -e val read 'val' external time stamp events\n" + " -f val adjust the ptp clock frequency by 'val' ppb\n" + " -F chan Enable single channel mask and keep device open for debugfs verification.\n" + " -g get the ptp clock time\n" + " -h prints this message\n" + " -i val index for event/trigger\n" + " -k val measure the time offset between system and phc clock\n" + " for 'val' times (Maximum 25)\n" + " -l list the current pin configuration\n" + " -L pin,val configure pin index 'pin' with function 'val'\n" + " the channel index is taken from the '-i' option\n" + " 'val' specifies the auxiliary function:\n" + " 0 - none\n" + " 1 - external time stamp\n" + " 2 - periodic output\n" + " -n val shift the ptp clock time by 'val' nanoseconds\n" + " -o val phase offset (in nanoseconds) to be provided to the PHC servo\n" + " -p val enable output with a period of 'val' nanoseconds\n" + " -H val set output phase to 'val' nanoseconds (requires -p)\n" + " -w val set output pulse width to 'val' nanoseconds (requires -p)\n" + " -P val enable or disable (val=1|0) the system clock PPS\n" + " -s set the ptp clock time from the system time\n" + " -S set the system time from the ptp clock time\n" + " -t val shift the ptp clock time by 'val' seconds\n" + " -T val set the ptp clock time to 'val' seconds\n" + " -x val get an extended ptp clock time with the desired number of samples (up to %d)\n" + " -X get a ptp clock cross timestamp\n" + " -z test combinations of rising/falling external time stamp flags\n", + progname, PTP_MAX_SAMPLES); +} + +int main(int argc, char *argv[]) +{ + struct ptp_clock_caps caps; + struct ptp_extts_event event; + struct ptp_extts_request extts_request; + struct ptp_perout_request perout_request; + struct ptp_pin_desc desc; + struct timespec ts; + struct timex tx; + struct ptp_clock_time *pct; + struct ptp_sys_offset *sysoff; + struct ptp_sys_offset_extended *soe; + struct ptp_sys_offset_precise *xts; + + char *progname; + unsigned int i; + int c, cnt, fd; + + char *device = DEVICE; + clockid_t clkid; + int adjfreq = 0x7fffffff; + int adjtime = 0; + int adjns = 0; + int adjphase = 0; + int capabilities = 0; + int extts = 0; + int flagtest = 0; + int gettime = 0; + int index = 0; + int list_pins = 0; + int pct_offset = 0; + int getextended = 0; + int getcross = 0; + int n_samples = 0; + int pin_index = -1, pin_func; + int pps = -1; + int seconds = 0; + int settime = 0; + int channel = -1; + + int64_t t1, t2, tp; + int64_t interval, offset; + int64_t perout_phase = -1; + int64_t pulsewidth = -1; + int64_t perout = -1; + + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; + while (EOF != (c = getopt(argc, argv, "cd:e:f:F:ghH:i:k:lL:n:o:p:P:sSt:T:w:x:Xz"))) { + switch (c) { + case 'c': + capabilities = 1; + break; + case 'd': + device = optarg; + break; + case 'e': + extts = atoi(optarg); + break; + case 'f': + adjfreq = atoi(optarg); + break; + case 'F': + channel = atoi(optarg); + break; + case 'g': + gettime = 1; + break; + case 'H': + perout_phase = atoll(optarg); + break; + case 'i': + index = atoi(optarg); + break; + case 'k': + pct_offset = 1; + n_samples = atoi(optarg); + break; + case 'l': + list_pins = 1; + break; + case 'L': + cnt = sscanf(optarg, "%d,%d", &pin_index, &pin_func); + if (cnt != 2) { + usage(progname); + return -1; + } + break; + case 'n': + adjns = atoi(optarg); + break; + case 'o': + adjphase = atoi(optarg); + break; + case 'p': + perout = atoll(optarg); + break; + case 'P': + pps = atoi(optarg); + break; + case 's': + settime = 1; + break; + case 'S': + settime = 2; + break; + case 't': + adjtime = atoi(optarg); + break; + case 'T': + settime = 3; + seconds = atoi(optarg); + break; + case 'w': + pulsewidth = atoi(optarg); + break; + case 'x': + getextended = atoi(optarg); + if (getextended < 1 || getextended > PTP_MAX_SAMPLES) { + fprintf(stderr, + "number of extended timestamp samples must be between 1 and %d; was asked for %d\n", + PTP_MAX_SAMPLES, getextended); + return -1; + } + break; + case 'X': + getcross = 1; + break; + case 'z': + flagtest = 1; + break; + case 'h': + usage(progname); + return 0; + case '?': + default: + usage(progname); + return -1; + } + } + + fd = open(device, O_RDWR); + if (fd < 0) { + fprintf(stderr, "opening %s: %s\n", device, strerror(errno)); + return -1; + } + + clkid = get_clockid(fd); + if (CLOCK_INVALID == clkid) { + fprintf(stderr, "failed to read clock id\n"); + return -1; + } + + if (capabilities) { + if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) { + perror("PTP_CLOCK_GETCAPS"); + } else { + printf("capabilities:\n" + " %d maximum frequency adjustment (ppb)\n" + " %d programmable alarms\n" + " %d external time stamp channels\n" + " %d programmable periodic signals\n" + " %d pulse per second\n" + " %d programmable pins\n" + " %d cross timestamping\n" + " %d adjust_phase\n" + " %d maximum phase adjustment (ns)\n", + caps.max_adj, + caps.n_alarm, + caps.n_ext_ts, + caps.n_per_out, + caps.pps, + caps.n_pins, + caps.cross_timestamping, + caps.adjust_phase, + caps.max_phase_adj); + } + } + + if (0x7fffffff != adjfreq) { + memset(&tx, 0, sizeof(tx)); + tx.modes = ADJ_FREQUENCY; + tx.freq = ppb_to_scaled_ppm(adjfreq); + if (clock_adjtime(clkid, &tx)) { + perror("clock_adjtime"); + } else { + puts("frequency adjustment okay"); + } + } + + if (adjtime || adjns) { + memset(&tx, 0, sizeof(tx)); + tx.modes = ADJ_SETOFFSET | ADJ_NANO; + tx.time.tv_sec = adjtime; + tx.time.tv_usec = adjns; + while (tx.time.tv_usec < 0) { + tx.time.tv_sec -= 1; + tx.time.tv_usec += NSEC_PER_SEC; + } + + if (clock_adjtime(clkid, &tx) < 0) { + perror("clock_adjtime"); + } else { + puts("time shift okay"); + } + } + + if (adjphase) { + memset(&tx, 0, sizeof(tx)); + tx.modes = ADJ_OFFSET | ADJ_NANO; + tx.offset = adjphase; + + if (clock_adjtime(clkid, &tx) < 0) { + perror("clock_adjtime"); + } else { + puts("phase adjustment okay"); + } + } + + if (gettime) { + if (clock_gettime(clkid, &ts)) { + perror("clock_gettime"); + } else { + printf("clock time: %ld.%09ld or %s", + ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec)); + } + } + + if (settime == 1) { + clock_gettime(CLOCK_REALTIME, &ts); + if (clock_settime(clkid, &ts)) { + perror("clock_settime"); + } else { + puts("set time okay"); + } + } + + if (settime == 2) { + clock_gettime(clkid, &ts); + if (clock_settime(CLOCK_REALTIME, &ts)) { + perror("clock_settime"); + } else { + puts("set time okay"); + } + } + + if (settime == 3) { + ts.tv_sec = seconds; + ts.tv_nsec = 0; + if (clock_settime(clkid, &ts)) { + perror("clock_settime"); + } else { + puts("set time okay"); + } + } + + if (pin_index >= 0) { + memset(&desc, 0, sizeof(desc)); + desc.index = pin_index; + desc.func = pin_func; + desc.chan = index; + if (ioctl(fd, PTP_PIN_SETFUNC, &desc)) { + perror("PTP_PIN_SETFUNC"); + } else { + puts("set pin function okay"); + } + } + + if (extts) { + memset(&extts_request, 0, sizeof(extts_request)); + extts_request.index = index; + extts_request.flags = PTP_ENABLE_FEATURE; + if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_request)) { + perror("PTP_EXTTS_REQUEST"); + extts = 0; + } else { + puts("external time stamp request okay"); + } + for (; extts; extts--) { + cnt = read(fd, &event, sizeof(event)); + if (cnt != sizeof(event)) { + perror("read"); + break; + } + printf("event index %u at %lld.%09u\n", event.index, + event.t.sec, event.t.nsec); + fflush(stdout); + } + /* Disable the feature again. */ + extts_request.flags = 0; + if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_request)) { + perror("PTP_EXTTS_REQUEST"); + } + } + + if (flagtest) { + do_flag_test(fd, index); + } + + if (list_pins) { + int n_pins = 0; + if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) { + perror("PTP_CLOCK_GETCAPS"); + } else { + n_pins = caps.n_pins; + } + for (i = 0; i < n_pins; i++) { + desc.index = i; + if (ioctl(fd, PTP_PIN_GETFUNC, &desc)) { + perror("PTP_PIN_GETFUNC"); + break; + } + printf("name %s index %u func %u chan %u\n", + desc.name, desc.index, desc.func, desc.chan); + } + } + + if (pulsewidth >= 0 && perout < 0) { + puts("-w can only be specified together with -p"); + return -1; + } + + if (perout_phase >= 0 && perout < 0) { + puts("-H can only be specified together with -p"); + return -1; + } + + if (perout >= 0) { + if (clock_gettime(clkid, &ts)) { + perror("clock_gettime"); + return -1; + } + memset(&perout_request, 0, sizeof(perout_request)); + perout_request.index = index; + perout_request.period.sec = perout / NSEC_PER_SEC; + perout_request.period.nsec = perout % NSEC_PER_SEC; + perout_request.flags = 0; + if (pulsewidth >= 0) { + perout_request.flags |= PTP_PEROUT_DUTY_CYCLE; + perout_request.on.sec = pulsewidth / NSEC_PER_SEC; + perout_request.on.nsec = pulsewidth % NSEC_PER_SEC; + } + if (perout_phase >= 0) { + perout_request.flags |= PTP_PEROUT_PHASE; + perout_request.phase.sec = perout_phase / NSEC_PER_SEC; + perout_request.phase.nsec = perout_phase % NSEC_PER_SEC; + } else { + perout_request.start.sec = ts.tv_sec + 2; + perout_request.start.nsec = 0; + } + + if (ioctl(fd, PTP_PEROUT_REQUEST2, &perout_request)) { + perror("PTP_PEROUT_REQUEST"); + } else { + puts("periodic output request okay"); + } + } + + if (pps != -1) { + int enable = pps ? 1 : 0; + if (ioctl(fd, PTP_ENABLE_PPS, enable)) { + perror("PTP_ENABLE_PPS"); + } else { + puts("pps for system time request okay"); + } + } + + if (pct_offset) { + if (n_samples <= 0 || n_samples > 25) { + puts("n_samples should be between 1 and 25"); + usage(progname); + return -1; + } + + sysoff = calloc(1, sizeof(*sysoff)); + if (!sysoff) { + perror("calloc"); + return -1; + } + sysoff->n_samples = n_samples; + + if (ioctl(fd, PTP_SYS_OFFSET, sysoff)) + perror("PTP_SYS_OFFSET"); + else + puts("system and phc clock time offset request okay"); + + pct = &sysoff->ts[0]; + for (i = 0; i < sysoff->n_samples; i++) { + t1 = pctns(pct+2*i); + tp = pctns(pct+2*i+1); + t2 = pctns(pct+2*i+2); + interval = t2 - t1; + offset = (t2 + t1) / 2 - tp; + + printf("system time: %lld.%09u\n", + (pct+2*i)->sec, (pct+2*i)->nsec); + printf("phc time: %lld.%09u\n", + (pct+2*i+1)->sec, (pct+2*i+1)->nsec); + printf("system time: %lld.%09u\n", + (pct+2*i+2)->sec, (pct+2*i+2)->nsec); + printf("system/phc clock time offset is %" PRId64 " ns\n" + "system clock time delay is %" PRId64 " ns\n", + offset, interval); + } + + free(sysoff); + } + + if (getextended) { + soe = calloc(1, sizeof(*soe)); + if (!soe) { + perror("calloc"); + return -1; + } + + soe->n_samples = getextended; + + if (ioctl(fd, PTP_SYS_OFFSET_EXTENDED, soe)) { + perror("PTP_SYS_OFFSET_EXTENDED"); + } else { + printf("extended timestamp request returned %d samples\n", + getextended); + + for (i = 0; i < getextended; i++) { + printf("sample #%2d: system time before: %lld.%09u\n", + i, soe->ts[i][0].sec, soe->ts[i][0].nsec); + printf(" phc time: %lld.%09u\n", + soe->ts[i][1].sec, soe->ts[i][1].nsec); + printf(" system time after: %lld.%09u\n", + soe->ts[i][2].sec, soe->ts[i][2].nsec); + } + } + + free(soe); + } + + if (getcross) { + xts = calloc(1, sizeof(*xts)); + if (!xts) { + perror("calloc"); + return -1; + } + + if (ioctl(fd, PTP_SYS_OFFSET_PRECISE, xts)) { + perror("PTP_SYS_OFFSET_PRECISE"); + } else { + puts("system and phc crosstimestamping request okay"); + + printf("device time: %lld.%09u\n", + xts->device.sec, xts->device.nsec); + printf("system time: %lld.%09u\n", + xts->sys_realtime.sec, xts->sys_realtime.nsec); + printf("monoraw time: %lld.%09u\n", + xts->sys_monoraw.sec, xts->sys_monoraw.nsec); + } + + free(xts); + } + + if (channel >= 0) { + if (ioctl(fd, PTP_MASK_CLEAR_ALL)) { + perror("PTP_MASK_CLEAR_ALL"); + } else if (ioctl(fd, PTP_MASK_EN_SINGLE, (unsigned int *)&channel)) { + perror("PTP_MASK_EN_SINGLE"); + } else { + printf("Channel %d exclusively enabled. Check on debugfs.\n", channel); + printf("Press any key to continue\n."); + getchar(); + } + } + + close(fd); + return 0; +} From 0463efb14eb863dac6bad4db3214d2a37fdbfb1a Mon Sep 17 00:00:00 2001 From: Massimiliano Date: Fri, 27 Sep 2024 15:19:31 +0200 Subject: [PATCH 18/25] Add manifest units to expand output (New) (#1489) * Also print manifest to expand * Document and partially de-functionalize code * Add tests for the new export units * Also test text exporter for manifest exporting * Check null tempalte-id --- .../checkbox_ng/launcher/subcommands.py | 68 ++++++++++++++++--- .../checkbox_ng/launcher/test_subcommands.py | 22 ++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/checkbox-ng/checkbox_ng/launcher/subcommands.py b/checkbox-ng/checkbox_ng/launcher/subcommands.py index 55d4666f80..a5d3ef559b 100644 --- a/checkbox-ng/checkbox_ng/launcher/subcommands.py +++ b/checkbox-ng/checkbox_ng/launcher/subcommands.py @@ -24,8 +24,8 @@ from collections import defaultdict from string import Formatter from tempfile import TemporaryDirectory -import textwrap import fnmatch +import itertools import contextlib import gettext import json @@ -1324,6 +1324,37 @@ def register_arguments(self, parser): help=_("output format: 'text' or 'json' (default: %(default)s)"), ) + def _get_relevant_manifest_units(self, jobs_and_templates_list): + """ + Get all manifest units that are cited in the jobs_and_templates_list + resource expressions + """ + # get all manifest units + manifest_units = filter( + lambda unit: unit.unit == "manifest entry", + self.sa._context.unit_list, + ) + # get all jobs/templates that have a requires and do require a manifest + # entry + job_requires = [ + requires + for requires in map( + lambda x: x.get_record_value("requires"), + jobs_and_templates_list, + ) + if requires and "manifest" in requires + ] + + # only return manifest entries that are actually required by any job in + # the list + return filter( + lambda manifest_unit: any( + "manifest.{}".format(manifest_unit.partial_id) in require + for require in job_requires + ), + manifest_units, + ) + def invoked(self, ctx): self.ctx = ctx session_title = "checkbox-expand-{}".format(ctx.args.TEST_PLAN) @@ -1340,31 +1371,48 @@ def invoked(self, ctx): tp = self.sa._context._test_plan_list[0] tp_us = TestPlanUnitSupport(tp) self.override_list = tp_us.override_list + jobs_and_templates_list = select_units( all_jobs_and_templates, [tp.get_mandatory_qualifier()] + [tp.get_qualifier()], ) + relevant_manifest_units = self._get_relevant_manifest_units( + jobs_and_templates_list + ) + units_to_print = itertools.chain( + relevant_manifest_units, iter(jobs_and_templates_list) + ) obj_list = [] - for unit in jobs_and_templates_list: + for unit in units_to_print: obj = unit._raw_data.copy() obj["unit"] = unit.unit obj["id"] = unit.id # To get the fully qualified id - obj["certification-status"] = ( - self.get_effective_certification_status(unit) - ) - if unit.template_id: - obj["template-id"] = unit.template_id + # these two don't make sense for manifest units + if unit.unit != "manifest entry": + obj["certification-status"] = ( + self.get_effective_certification_status(unit) + ) + if unit.template_id: + obj["template-id"] = unit.template_id obj_list.append(obj) - obj_list.sort(key=lambda x: x.get("template-id", x["id"])) + + obj_list.sort(key=lambda x: x.get("template-id", x["id"]) or x["id"]) + if ctx.args.format == "json": - print(json.dumps(obj_list, sort_keys=True)) + json.dump(obj_list, sys.stdout, sort_keys=True) else: for obj in obj_list: if obj["unit"] == "template": print("Template '{}'".format(obj["template-id"])) - else: + elif obj["unit"] == "manifest entry": + print("Manifest '{}'".format(obj["id"])) + elif obj["unit"] == "job": print("Job '{}'".format(obj["id"])) + else: + raise AssertionError( + "Unknown unit type {}".format(obj["unit"]) + ) def get_effective_certification_status(self, unit): if unit.unit == "template": diff --git a/checkbox-ng/checkbox_ng/launcher/test_subcommands.py b/checkbox-ng/checkbox_ng/launcher/test_subcommands.py index 949a5b9004..466411a766 100644 --- a/checkbox-ng/checkbox_ng/launcher/test_subcommands.py +++ b/checkbox-ng/checkbox_ng/launcher/test_subcommands.py @@ -813,6 +813,16 @@ def setUp(self): self.launcher = Expand() self.ctx = Mock() self.ctx.args = Mock(TEST_PLAN="", format="") + + selected_1 = Mock(unit="manifest entry", id="some", partial_id="some") + selected_1._raw_data.copy.return_value = {} + selected_2 = Mock( + unit="manifest entry", id="other", partial_id="other" + ) + selected_2._raw_data.copy.return_value = {} + not_selected = Mock(unit="manifest entry", partial_id="not_selected") + not_selected._raw_data.copy.return_value = {} + self.ctx.sa = Mock( start_new_session=Mock(), get_test_plans=Mock(return_value=["test-plan1", "test-plan2"]), @@ -821,6 +831,7 @@ def setUp(self): _context=Mock( state=Mock(unit_list=[]), _test_plan_list=[Mock()], + unit_list=[selected_1, selected_2, not_selected], ), ) @@ -844,17 +855,22 @@ def test_invoke__text(self, mock_select_units, mock_tpus, stdout): "template-id": "test-template", "id": "test-{res}", "template-summary": "Test Template Summary", + "requires": "manifest.some == 'True'", } ) job1 = JobDefinition( { "id": "job1", + "requires": "manifest.other == 'Other'", } ) mock_select_units.return_value = [job1, template1] self.ctx.args.TEST_PLAN = "test-plan1" self.launcher.invoked(self.ctx) self.assertIn("Template 'test-template'", stdout.getvalue()) + self.assertIn("Manifest 'some'", stdout.getvalue()) + self.assertIn("Manifest 'other'", stdout.getvalue()) + self.assertNotIn("Manifest 'not_selected'", stdout.getvalue()) @patch("sys.stdout", new_callable=StringIO) @patch("checkbox_ng.launcher.subcommands.TestPlanUnitSupport") @@ -865,18 +881,24 @@ def test_invoke__json(self, mock_select_units, mock_tpus, stdout): "template-id": "test-template", "id": "test-{res}", "template-summary": "Test Template Summary", + "requires": "manifest.some == 'True'", } ) job1 = JobDefinition( { "id": "job1", + "requires": "manifest.other == 'Other'", } ) + mock_select_units.return_value = [job1, template1] self.ctx.args.TEST_PLAN = "test-plan1" self.ctx.args.format = "json" self.launcher.invoked(self.ctx) self.assertIn('"template-id": "test-template"', stdout.getvalue()) + self.assertIn('"id": "some"', stdout.getvalue()) + self.assertIn('"id": "other"', stdout.getvalue()) + self.assertNotIn('"id": "not_selected"', stdout.getvalue()) def test_get_effective_certificate_status(self): job1 = JobDefinition( From 26059821197f61fd67410dc7191eb4dcb6a7eac3 Mon Sep 17 00:00:00 2001 From: Isaac Yang <47034756+seankingyang@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:01:15 +0800 Subject: [PATCH 19/25] Correct the camera job cmd with right camera tests (Bugfix) (#1515) Correct the camera job cmd with correct camera tests --- providers/base/units/camera/jobs.pxu | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/base/units/camera/jobs.pxu b/providers/base/units/camera/jobs.pxu index 279697972a..1845a67412 100644 --- a/providers/base/units/camera/jobs.pxu +++ b/providers/base/units/camera/jobs.pxu @@ -35,7 +35,7 @@ _summary: Webcam video display test for {product_slug} estimated_duration: 120.0 depends: camera/detect command: - camera_test.py display -d /dev/{name} + camera_test.py video -d /dev/{name} _purpose: This test will check that the {product_slug} camera works _steps: @@ -81,7 +81,7 @@ _summary: Webcam still image capture test for {product_slug} estimated_duration: 120.0 depends: camera/detect command: - camera_test.py still -d /dev/{name} + camera_test.py image -d /dev/{name} _purpose: This test will check that the {product_slug} works _steps: From 6a3e3c17539934d6c0606d5e99512bfa8f99fca9 Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Tue, 1 Oct 2024 10:02:29 +0200 Subject: [PATCH 20/25] Fix snap refresh verification job dependency (bugfix) (#1519) Fix snap refresh verification job dependency When implementing changes for the gadget/snapd/kernel snap refresh[1], one of the job dependencies was not updated properly. The `snapd/reboot-after-snap-refresh-{type}-{name}-to-stable-rev` job does not exist anymore, so the `snapd/snap-verify-after-refresh-{type}-{name}-to-stable-rev` should depend on the refresh job itself (which was modified in the aforementioned PR to do the reboot itself) [1]: https://github.com/canonical/checkbox/pull/1124 --- providers/base/units/snapd/jobs.pxu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/base/units/snapd/jobs.pxu b/providers/base/units/snapd/jobs.pxu index c172dda487..2b7e99ffc4 100644 --- a/providers/base/units/snapd/jobs.pxu +++ b/providers/base/units/snapd/jobs.pxu @@ -63,7 +63,7 @@ plugin: shell estimated_duration: 30s category_id: snapd user: root -depends: snapd/reboot-after-snap-refresh-{type}-{name}-to-stable-rev +depends: snapd/snap-refresh-{type}-{name}-to-stable-rev command: path="$PLAINBOX_SESSION_SHARE/{name}_snap_revision_info" snap_update_test.py --verify-refresh --info-path "$path" {name} From ba4bbb786bd7d25b300d14151ddd8e620896c034 Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Tue, 1 Oct 2024 12:18:00 +0200 Subject: [PATCH 21/25] Add a manifest entry to check if a SIM card is inserted (New) (#1500) * Add SIM card manifest entry * Add SIM card requirement on WWAN jobs needing it * Align `requires` and `depends` fields in WWAN jobs Use 2 spaces instead of 4 to match the rest of the file. --- providers/base/units/wwan/jobs.pxu | 22 +++++++++++++++------- providers/base/units/wwan/manifest.pxu | 5 +++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/providers/base/units/wwan/jobs.pxu b/providers/base/units/wwan/jobs.pxu index ca269e82e1..7f799cba13 100644 --- a/providers/base/units/wwan/jobs.pxu +++ b/providers/base/units/wwan/jobs.pxu @@ -54,6 +54,7 @@ imports: from com.canonical.plainbox import manifest depends: wwan/check-sim-present-{manufacturer}-{model}-{hw_id}-auto requires: manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' snap.name == 'modem-manager' or package.name == 'modemmanager' unit: template @@ -74,6 +75,7 @@ flags: preserve-locale also-after-suspend preserve-cwd imports: from com.canonical.plainbox import manifest requires: manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' snap.name == 'modem-manager' or package.name == 'modemmanager' unit: template @@ -100,6 +102,7 @@ flags: preserve-locale also-after-suspend preserve-cwd imports: from com.canonical.plainbox import manifest requires: manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' snap.name == 'modem-manager' or package.name == 'modemmanager' unit: template @@ -122,6 +125,7 @@ depends: wwan/check-sim-present-{manufacturer}-{model}-{hw_id}-auto imports: from com.canonical.plainbox import manifest requires: manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' snap.name == 'modem-manager' or package.name == 'modemmanager' id: wwan/detect-manual @@ -140,7 +144,8 @@ flags: also-after-suspend imports: from com.canonical.plainbox import manifest category_id: wwan requires: - manifest.has_wwan_module == 'True' + manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' id: wwan/check-sim-present-manual plugin: manual @@ -158,9 +163,10 @@ flags: also-after-suspend imports: from com.canonical.plainbox import manifest category_id: wwan requires: - manifest.has_wwan_module == 'True' + manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' depends: - wwan/detect-manual + wwan/detect-manual id: wwan/gsm-connection-manual plugin: manual @@ -190,9 +196,10 @@ flags: also-after-suspend category_id: wwan imports: from com.canonical.plainbox import manifest requires: - manifest.has_wwan_module == 'True' + manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' depends: - wwan/check-sim-present-manual + wwan/check-sim-present-manual id: wwan/gsm-connection-interrupted-manual plugin: manual @@ -230,6 +237,7 @@ flags: also-after-suspend category_id: wwan imports: from com.canonical.plainbox import manifest requires: - manifest.has_wwan_module == 'True' + manifest.has_wwan_module == 'True' + manifest.has_sim_card == 'True' depends: - wwan/check-sim-present-manual + wwan/check-sim-present-manual diff --git a/providers/base/units/wwan/manifest.pxu b/providers/base/units/wwan/manifest.pxu index 8420e2e794..42e4cd9512 100644 --- a/providers/base/units/wwan/manifest.pxu +++ b/providers/base/units/wwan/manifest.pxu @@ -8,3 +8,8 @@ unit: manifest entry id: has_wwan_module _name: A WWAN Module value-type: bool + +unit: manifest entry +id: has_sim_card +_name: A working SIM card inserted +value-type: bool From 85ee30405ea7534bf1ae3d717eaae7610105cce4 Mon Sep 17 00:00:00 2001 From: Mauricio Bonetti Date: Wed, 2 Oct 2024 04:19:22 -0300 Subject: [PATCH 22/25] Includes libasound2-dev as a Dependency in Build Instructions (Infra) (#1507) Includes libasound2-dev as a Dependency in Build Instructions - Issue#1506 --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bcdfbc316..6636cdcbf3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ install everything you need in a Python virtual environment. Install the required tools: - $ sudo apt install git python3-virtualenv + $ sudo apt install git python3-virtualenv libasound2-dev Prepare the development environment. If you are an external contributor and plan on submitting some changes, you will have to [fork the Checkbox repository From 92a28be83b86044cdf079d9d8e84891de49d00f9 Mon Sep 17 00:00:00 2001 From: Massimiliano Date: Wed, 2 Oct 2024 16:41:44 +0200 Subject: [PATCH 23/25] Make workflow dispatch checkbox from source into action (infra) (#1517) * Refactor workflow into action * Fix typo and set working directory * Provide secrets via additioanl launcher * Use launcher from the correct location * Print override to screen * Propagate the checkbox_revision from the action input * Partially support noprovision * Allow degraded wait_for_ssh * Call the clean_machine script in the workflow * Also set and refresh zapper snap * Documented new parameter and reset default value * Disable failfast * Shallow fetch the Checkbox repo * Try to change the path in an action aware way * Fix typo Co-authored-by: Paolo Gentili * Document parameters * Use the new git_get scriplet * Check the copy path before copying * Try to move the scriplet to /bin * Use the new name of the script * Now git_get_shallow is in main * Change the action target branch to the dispatch action --------- Co-authored-by: Paolo Gentili --- .../actions/checkbox_source_deb/action.yaml | 83 +++++++++++++++++++ .github/workflows/dispatch_lab_job.yaml | 50 +++++------ tools/lab_dispatch/build_install_deb.py | 1 + tools/lab_dispatch/generic_source.yaml | 28 ++++--- .../checkbox.no-manifest.template.conf | 5 -- 5 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 .github/actions/checkbox_source_deb/action.yaml diff --git a/.github/actions/checkbox_source_deb/action.yaml b/.github/actions/checkbox_source_deb/action.yaml new file mode 100644 index 0000000000..aa59898a5e --- /dev/null +++ b/.github/actions/checkbox_source_deb/action.yaml @@ -0,0 +1,83 @@ +name: Submit a Checkbox Test plan (or subset of it) to the lab +inputs: + data_source: + description: "Target image and provisioning data (ex. `url:` or `distro:`)" + required: false + default: null + queue: + description: "Queue that will run the testing (ex. 202012-28526)" + required: true + test_plan: + description: "Test plan to run (ex. com.canonical.certification::sru)" + required: true + match: + description: "Subset of jobs to run (ex. .*wireless.*)" + required: false + default: ".*" + launcher_override: + description: "Launcher with additional values that will take priority over the defaults" + default: "" + required: false + checkbox_revision: + description: "Revision of checkbox that has to be provisioned (ex. commit_hash, branch name, can be `beta`)" + required: true + zapper_channel: + description: "Zapper channel to be used, will be ignored if no Zapper (ex. edge, beta, stable)" + required: false + default: "beta" +runs: + using: composite + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + shell: bash + run: | + sudo apt install -y -qq gettext + - name: Build test resource + shell: bash + env: + INPUT_DATA_SOURCE: ${{ inputs.data_source }} + INPUT_QUEUE: ${{ inputs.queue }} + INPUT_MATCH: ${{ inputs.match || '.*' }} + INPUT_TEST_PLAN: ${{ inputs.test_plan }} + INPUT_LAUNCHER_OVERRIDE: ${{ inputs.launcher_override }} + INPUT_CHECKBOX_REVISION: ${{ inputs.checkbox_revision }} + INPUT_ZAPPER_CHANNEL: ${{ inputs.zapper_channel || 'beta' }} + working-directory: ${{ github.action_path }}/../../../tools/lab_dispatch + run: | + echo "::group::Building the testflinger job" + if [ -n "$INPUT_DATA_SOURCE" ]; then + INPUT_DATA_SOURCE="provision_data: $INPUT_DATA_SOURCE" + fi + envsubst '$INPUT_CHECKBOX_REVISION $INPUT_DATA_SOURCE $INPUT_QUEUE $INPUT_ZAPPER_CHANNEL' < generic_source.yaml | tee job.yaml + echo "::endgroup::" + + echo "::group::Building the Checkbox launcher" + # this goes from .template. (missing secret, testplan, match etc. to .partial.) + # this is partial as some values are filled in on the agent (like wireless access points names) + envsubst '$INPUT_TEST_PLAN $INPUT_MATCH' < resources/checkbox.no-manifest.template.conf | tee resources/checkbox.no-manifest.partial.conf + echo "::endgroup::" + + echo "::group::Dumping launcher overrides" + echo "$INPUT_LAUNCHER_OVERRIDE" | tee launcher_override.conf + echo "::endgroup::" + - name: Workaroud cwd + shell: bash + run: | + # this allows us to dispatch the action and the attachments with relative + # paths even when called form outside the Checkbox repo + action_path=$(realpath ${{ github.action_path }}/../../../tools/) + workdir_path=$(realpath tools/) + if [ ! -e "$workdir_path" ]; then + cp -rT "$action_path" "$workdir_path" + fi + if [ "$action_path" = "$workdir_path" ]; then + echo "Skipping copy as the action is already running in workdir" + else + cp -rT "$action_path" "$workdir_path" + fi + - name: Submit and monitor job + uses: canonical/testflinger/.github/actions/submit@main + with: + poll: true + job-path: tools/lab_dispatch/job.yaml diff --git a/.github/workflows/dispatch_lab_job.yaml b/.github/workflows/dispatch_lab_job.yaml index 1832bcecd7..d01818f5d6 100644 --- a/.github/workflows/dispatch_lab_job.yaml +++ b/.github/workflows/dispatch_lab_job.yaml @@ -9,6 +9,7 @@ on: # - queue: machine that will run the job (ex. 202012-28526) # - test_plan: Checkbox test plan to run (ex. com.canonical.certification::sru) # - match: subset of jobs to run (ex. .*wireless.*) + # - zapper_channel: refreshes the zapper snap to the channel if provided, default is beta (ex. "beta") # # One possible matrix_to_create would therefore look like this: # matrix_to_create=[{ data_source: "distro: desktop-22-04-2-uefi", queue: "202012-28526", match: ".*wireless.*", test_plan: "com.canonical.certification::sru" }]' @@ -24,39 +25,34 @@ jobs: run-matrix: runs-on: [self-hosted, testflinger] strategy: + fail-fast: false matrix: spec: ${{ fromJson(inputs.matrix_to_create) }} defaults: run: working-directory: tools/lab_dispatch steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install dependencies - run: | - sudo apt install gettext - - name: Build test resource - env: - INPUT_DATA_SOURCE: ${{ matrix.spec.data_source }} - INPUT_QUEUE: ${{ matrix.spec.queue }} - INPUT_MATCH: ${{ matrix.spec.match }} - INPUT_TEST_PLAN: ${{ matrix.spec.test_plan }} - INPUT_PASSWORD_SECRET: ${{ secrets.INPUT_PASSWORD_SECRET }} - run: | - echo "::group::Building the testflinger job" - export INPUT_CHECKBOX_REVISION="$(git rev-parse HEAD)" - envsubst '$INPUT_CHECKBOX_REVISION $INPUT_DATA_SOURCE $INPUT_QUEUE' < generic_source.yaml | tee job.yaml - echo "::endgroup::" - echo "::group::Building the Checkbox launcher" + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get current commit SHA + id: get_sha + run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - # this goes from .template. (missing secret, testplan, match etc. to .partial.) - # this is partial as some values are filled in on the agent (like wireless access points names) - envsubst '$INPUT_TEST_PLAN $INPUT_MATCH $INPUT_PASSWORD_SECRET' < resources/checkbox.no-manifest.template.conf | tee resources/checkbox.no-manifest.partial.conf - echo "::endgroup::" - - name: Submit and monitor job - uses: canonical/testflinger/.github/actions/submit@main + - name: Run the spec + uses: canonical/checkbox/.github/actions/checkbox_source_deb@main with: - poll: true - job-path: tools/lab_dispatch/job.yaml + data_source: ${{ matrix.spec.data_source }} + queue: ${{ matrix.spec.queue }} + test_plan: ${{ matrix.spec.test_plan }} + match: ${{ matrix.spec.match }} + zapper_channel: ${{ matrix.spec.zapper_channel }} + launcher_override: | + [environment] + WPA_BG_PSK = ${{ secrets.INPUT_PASSWORD_SECRET }} + WPA_N_PSK = ${{ secrets.INPUT_PASSWORD_SECRET }} + WPA_AC_PSK = ${{ secrets.INPUT_PASSWORD_SECRET }} + WPA_AX_PSK = ${{ secrets.INPUT_PASSWORD_SECRET }} + WPA3_AX_PSK = ${{ secrets.INPUT_PASSWORD_SECRET }} + checkbox_revision: ${{ steps.get_sha.outputs.sha }} diff --git a/tools/lab_dispatch/build_install_deb.py b/tools/lab_dispatch/build_install_deb.py index d580ac3ba2..6f57b52601 100644 --- a/tools/lab_dispatch/build_install_deb.py +++ b/tools/lab_dispatch/build_install_deb.py @@ -77,6 +77,7 @@ def install_local_package(repo_root, deb_name_glob): "DEBIAN_FRONTEND=noninteractive", "apt-get", "--fix-broken", + "--allow-downgrades", "-y", "install", ] diff --git a/tools/lab_dispatch/generic_source.yaml b/tools/lab_dispatch/generic_source.yaml index c8c143d645..35ea95dbc4 100644 --- a/tools/lab_dispatch/generic_source.yaml +++ b/tools/lab_dispatch/generic_source.yaml @@ -1,8 +1,7 @@ job_queue: $INPUT_QUEUE global_timeout: 3600 output_timeout: 1800 -provision_data: - $INPUT_DATA_SOURCE +$INPUT_DATA_SOURCE test_data: attachments: - local: "tools/lab_dispatch/resources/manifest.conf" @@ -11,6 +10,8 @@ test_data: agent: "checkbox.no-manifest.partial.conf" - local: "tools/lab_dispatch/build_install_deb.py" agent: "build_install_deb.py" + - local: "tools/lab_dispatch/launcher_override.conf" + agent: "launcher_override.conf" test_cmds: | #!/usr/bin/env bash @@ -28,23 +29,28 @@ test_data: source install_tools.sh $TOOLS_PATH # ensure device is available before continuing - wait_for_ssh + wait_for_ssh --allow-degraded - _run sudo add-apt-repository ppa:checkbox-dev/edge _run install_packages git python3 python3-pip dpkg-dev + refresh_zapper_if_needed --channel "$INPUT_ZAPPER_CHANNEL" - wait_for_ssh + wait_for_ssh --allow-degraded + _run clean_machine --im-sure + _run sudo add-apt-repository ppa:checkbox-dev/edge _put $RESOURCES_PATH/build_install_deb.py : - _run git clone https://github.com/canonical/checkbox.git - _run git -C checkbox checkout $CHECKBOX_REVISION + + # clone the Checkbox revision without history (easier for slow systems, preserves RAM/storage/networking) + _run git_get_shallow https://github.com/canonical/checkbox.git commit $CHECKBOX_REVISION + + _run wait_for_packages_complete _run python3 build_install_deb.py --clean checkbox/checkbox-ng \ checkbox/checkbox-support checkbox/providers/resource \ checkbox/providers/base checkbox/providers/sru _run sudo systemctl restart checkbox-ng + _run wait_for_packages_complete - git clone https://github.com/canonical/checkbox.git - git -C checkbox checkout $CHECKBOX_REVISION + git_get_shallow https://github.com/canonical/checkbox.git commit $CHECKBOX_REVISION pipx install --spec checkbox/checkbox-ng checkbox-ng # retrieve manifest @@ -62,9 +68,9 @@ test_data: which envsubst || install_packages gettext envsubst < $RESOURCES_PATH/checkbox.no-manifest.partial.conf > checkbox.no-manifest.conf # then insert the manifest entries via the stacker - stacker --output checkbox.conf checkbox.no-manifest.conf $MANIFEST_FILE + stacker --output checkbox.conf checkbox.no-manifest.conf $MANIFEST_FILE $RESOURCES_PATH/launcher_override.conf - wait_for_ssh + wait_for_ssh --allow-degraded check_for_checkbox_service diff --git a/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf b/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf index f5c95c536b..3bb66095f2 100644 --- a/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf +++ b/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf @@ -16,15 +16,10 @@ type = silent [environment] ROUTERS = multiple WPA_BG_SSID = $WPA_BG_SSID -WPA_BG_PSK = $INPUT_PASSWORD_SECRET WPA_N_SSID = $WPA_N_SSID -WPA_N_PSK = $INPUT_PASSWORD_SECRET WPA_AC_SSID = $WPA_AC_SSID -WPA_AC_PSK = $INPUT_PASSWORD_SECRET WPA_AX_SSID = $WPA_AX_SSID -WPA_AX_PSK = $INPUT_PASSWORD_SECRET WPA3_AX_SSID = $WPA3_AX_SSID -WPA3_AX_PSK = $INPUT_PASSWORD_SECRET OPEN_BG_SSID = $OPEN_BG_SSID OPEN_N_SSID = $OPEN_N_SSID OPEN_AC_SSID = $OPEN_AC_SSID From 17ceef16f85536c098062e01b6445fd8408a6d72 Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Wed, 2 Oct 2024 23:07:17 +0200 Subject: [PATCH 24/25] Use wwan-automated nested part in SRU test plan (bugfix) (#1499) Use wwan-automated nested part in SRU test plan Replace mobilebroadband-cert-automated with wwan-automated nested part, as the wwan-automated test plan includes better coverage for WWAN-related tests. Add after-suspend-wwan-automated counterpart to make sure there are no issues with WWAN after resuming from suspend. This is a follow-up to work done by QA in the certification-client desktop test plans (see https://github.com/canonical/checkbox/pull/821) Fix CER-2738 --- providers/sru/units/sru.pxu | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/providers/sru/units/sru.pxu b/providers/sru/units/sru.pxu index 8bc344c600..7b314eab76 100644 --- a/providers/sru/units/sru.pxu +++ b/providers/sru/units/sru.pxu @@ -54,7 +54,7 @@ nested_part: mediacard-cert-automated mediacard-automated memory-automated - mobilebroadband-cert-automated + wwan-automated ethernet-cert-automated networking-cert-automated optical-cert-automated @@ -68,6 +68,7 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full + after-suspend-wwan-automated after-suspend-touchscreen-cert-automated after-suspend-wireless-cert-automated # The following tests should run BEFORE the automated tests. The reboot and From 597b01f29db56d980be3678eda83ce9c4d947df4 Mon Sep 17 00:00:00 2001 From: Massimiliano Date: Thu, 3 Oct 2024 11:59:33 +0200 Subject: [PATCH 25/25] Update workflow validation action to the latest version (infra) (#1526) Update action to the latest version --- .github/workflows/validate_workflows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate_workflows.yaml b/.github/workflows/validate_workflows.yaml index f8f02eabaa..df40cdcb78 100644 --- a/.github/workflows/validate_workflows.yaml +++ b/.github/workflows/validate_workflows.yaml @@ -16,7 +16,7 @@ jobs: uses: asdf-vm/actions/install@v3 with: tool_versions: | - action-validator 0.5.1 + action-validator 0.6.0 - name: Lint Actions run: | find .github/workflows -type f \( -iname \*.yaml -o -iname \*.yml \) \