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 7b122b4754..d01818f5d6 100644 --- a/.github/workflows/dispatch_lab_job.yaml +++ b/.github/workflows/dispatch_lab_job.yaml @@ -1,14 +1,58 @@ -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.*) + # - 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" }]' + # + # 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: + fail-fast: false + 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 + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get current commit SHA + id: get_sha + run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Run the spec + uses: canonical/checkbox/.github/actions/checkbox_source_deb@main + with: + 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/.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/.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 \) \ 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 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( 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/checkbox-support/checkbox_support/scripts/run_watcher.py b/checkbox-support/checkbox_support/scripts/run_watcher.py index ede02a8ee7..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 @@ -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..865963d4fd 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, @@ -344,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") @@ -428,6 +424,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 +640,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/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-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 08d06fd22f..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: @@ -83,14 +87,17 @@ nested_part: com.canonical.certification::rtc-automated 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: @@ -112,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: @@ -145,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 .* + 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]: 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) 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/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/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; +} 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( 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 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: 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/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/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' 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} 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 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: 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/base/units/wwan/jobs.pxu b/providers/base/units/wwan/jobs.pxu index e169edaf26..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 @@ -226,10 +233,11 @@ _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: - 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 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..fb53fd5470 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 @@ -69,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 @@ -102,7 +104,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 +125,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. 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() 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 diff --git a/tools/lab_dispatch/build_install_deb.py b/tools/lab_dispatch/build_install_deb.py new file mode 100644 index 0000000000..6f57b52601 --- /dev/null +++ b/tools/lab_dispatch/build_install_deb.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import os +import shutil +import argparse +import functools +import subprocess + +from copy import copy +from pathlib import Path + + +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(): + 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(str(package_path / "debian"), str(repo_root)) + + +@github_group("Installing build depends") +def install_build_depends(repo_root): + subprocess.check_call( + [ + "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, 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)) + print(f"==== Installing {package_list} ====") + package_list = [str(x.resolve()) for x in package_list] + subprocess.check_call( + [ + "sudo", + "DEBIAN_FRONTEND=noninteractive", + "apt-get", + "--fix-broken", + "--allow-downgrades", + "-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..35ea95dbc4 --- /dev/null +++ b/tools/lab_dispatch/generic_source.yaml @@ -0,0 +1,83 @@ +job_queue: $INPUT_QUEUE +global_timeout: 3600 +output_timeout: 1800 +$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" + - local: "tools/lab_dispatch/launcher_override.conf" + agent: "launcher_override.conf" + test_cmds: | + #!/usr/bin/env bash + + set -x + set -e + + # 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 --allow-degraded + + _run install_packages git python3 python3-pip dpkg-dev + refresh_zapper_if_needed --channel "$INPUT_ZAPPER_CHANNEL" + + 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 : + + # 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_get_shallow https://github.com/canonical/checkbox.git commit $CHECKBOX_REVISION + pipx install --spec checkbox/checkbox-ng checkbox-ng + + # 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 + 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 $RESOURCES_PATH/launcher_override.conf + + wait_for_ssh --allow-degraded + + 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..3bb66095f2 --- /dev/null +++ b/tools/lab_dispatch/resources/checkbox.no-manifest.template.conf @@ -0,0 +1,35 @@ +[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_N_SSID = $WPA_N_SSID +WPA_AC_SSID = $WPA_AC_SSID +WPA_AX_SSID = $WPA_AX_SSID +WPA3_AX_SSID = $WPA3_AX_SSID +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