From 79599f81d991aab1397bc16de343899a07c49fb7 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Mon, 19 Jun 2023 16:47:26 +0800 Subject: [PATCH 01/15] Changing gpu test strategy to prime/reverse-prime gpu offload without depending on index For Nvidia GPU, the prime/reverse prime offload is not supported before version 435.17. Therefore, This new strategy is only for 22.04+. For backward compatibility, this PR add new test plans for 22.04+ as follow: graphics-gpu-cert-full graphics-gpu-cert-automated graphics-gpu-cert-manual after-suspend-graphics-gpu-cert-full after-suspend-graphics-gpu-cert-automated after-suspend-graphics-gpu-cert-manual monitor-gpu-cert-full monitor-gpu-cert-automated monitor-gpu-cert-manual after-suspend-monitor-gpu-cert-full after-suspend-monitor-gpu-cert-automated after-suspend-monitor-gpu-cert-manual And add new python script "prime_offload_tester.py" to execute command with prime/reverse prime setting for new test jobs as follow: Auto test: graphics/{index}_auto_glxgears_{product_slug} graphics/{index}_auto_glxgears_fullscreen_{product_slug} Manual: graphics/{index}_valid_glxgears_{product_slug} graphics/{index}_valid_glxgears_fullscreen_{product_slug} --- providers/base/bin/prime_offload_tester.py | 328 ++++++++++++++++++ .../base/tests/test_prime_offload_tester.py | 271 +++++++++++++++ providers/base/units/graphics/jobs.pxu | 72 ++++ providers/base/units/graphics/test-plan.pxu | 89 ++++- providers/base/units/monitor/test-plan.pxu | 89 +++++ providers/base/units/suspend/suspend.pxu | 1 + .../units/client-cert-desktop-22-04.pxu | 31 +- .../units/client-cert-odm-desktop-22-04.pxu | 32 +- .../resource/bin/graphics_card_resource.py | 3 + 9 files changed, 868 insertions(+), 48 deletions(-) create mode 100755 providers/base/bin/prime_offload_tester.py create mode 100755 providers/base/tests/test_prime_offload_tester.py diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py new file mode 100755 index 0000000000..eaadbe3353 --- /dev/null +++ b/providers/base/bin/prime_offload_tester.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +"""prime offload test module.""" +# This file is part of Checkbox. +# +# Copyright 2023 Canonical Ltd. +# Written by: +# Hanhsuan Lee +# +# Checkbox is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, +# as published by the Free Software Foundation. +# +# Checkbox 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 Checkbox. If not, see . + +import sys +import threading +import subprocess +import time +import re +import json +import argparse +import logging +from enum import IntEnum + + +class PrimeOffloaderError(IntEnum): + """ + A class used to define PrimeOffloader Error code + + :attr NO_ERROR: process success + :type NO_ERROR: int + + :attr NO_CARD_ID: couldn't find card id + :type NO_CARD_ID: int + + :attr NO_CARD_NAME: couldn't find card name + :type NO_CARD_NAME: int + + :attr OFFLOAD_FAIL: couldn't find process on specific GPU + :type OFFLOAD_FAIL: int + + :attr NOT_SUPPORT_NV_PRIME: this system couldn't support nv prime offload + :type NOT_SUPPORT_NV_PRIME: int + + :attr NV_DRIVER_ERROR: some errors form nvidia driver + :type NV_DRIVER_ERROR: int + + """ + + NO_ERROR = 0 + NO_CARD_ID = -1 + NO_CARD_NAME = -2 + OFFLOAD_FAIL = -3 + NOT_SUPPORT_NV_PRIME = -4 + NV_DRIVER_ERROR = -5 + + +class PrimeOffloader: + """ + A class used to execute process to specific GPU. + Have to run this as root. + + :attr logger: console logger + :type logger: obj + + :attr check_result: + store the result of checking offloading is ok or not. + :type check_result: int + """ + + logger = logging.getLogger() + + check_result = PrimeOffloaderError.OFFLOAD_FAIL + + def find_card_id(self, pci_name): + """ + use pci name to find card id under /sys/kernel/debug/dri + + :param pci_name: pci device name in NNNN:NN:NN.N format + :type card_name: str + """ + cmd = "sudo grep -lr --include=name \"{}\"" \ + " /sys/kernel/debug/dri 2>/dev/null".format(pci_name) + try: + if not re.match("[0-9]{4}:[0-9]{2}:[0-9]{2}.[0-9]", pci_name): + return PrimeOffloaderError.NO_CARD_ID + + card_path = subprocess.run(cmd, shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + card_id = card_path.stdout.split('/')[5] + return card_id + except (ValueError, IndexError, AttributeError): + return PrimeOffloaderError.NO_CARD_ID + + def find_card_name(self, pci_name): + """ + use pci name to find card name by lshw + + :param pci_name: pci device name in NNNN:NN:NN.N format + :type card_name: str + """ + cmd = 'sudo lshw -c display -json' + try: + card_infos = subprocess.run(cmd, shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + infos = json.loads(card_infos.stdout) + for info in infos: + if pci_name in info['businfo']: + return info['product'] + except (ValueError, IndexError, TypeError): + return None + + def check_offload(self, cmd, card_id, card_name): + """ + Use to check provided command is executed on specific GPU. + + .. note:: + While setting prime offload environment such as DRI_PRIME, + the process will be listed under kernel debug interface. + The location of kernel debug interface is + /sys/kernel/debug/dri/, + and the process could be found in + /sys/kernel/debug/dri//clients + + :param cmd: command that running under prime offload + :type cmd: str + + :param card_id: card id of dri device + :type card_id: str + + :param card_name: card name of dri device + :type card_name: str + """ + cmd_without_args = cmd.split(' ')[0] + for index in range(1, 11): + time.sleep(2) + try: + read_clients_cmd = \ + "sudo cat /sys/kernel/debug/dri/{}/clients".format(card_id) + clients = subprocess.run(read_clients_cmd, shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + if cmd_without_args in clients.stdout: + self.logger.info("Checking success:") + self.logger.info(" Offload process:[{}]".format(cmd)) + self.logger.info(" Card ID:[{}]".format(card_id)) + self.logger.info(" Device Name:[{}]".format(card_name)) + self.check_result = PrimeOffloaderError.NO_ERROR + return + self.check_result = PrimeOffloaderError.OFFLOAD_FAIL + except (OSError, TypeError): + # Missing file or permissions? + self.logger.info("Couldn't open file for" + "reading clients of dri device") + self.check_result = PrimeOffloaderError.OFFLOAD_FAIL + return + self.logger.info("Checking fail:") + self.logger.info(" Couldn't find process [{}]".format(cmd) + + " running after check {} times".format(index)) + self.check_result = PrimeOffloaderError.OFFLOAD_FAIL + + def check_nv_offload_env(self): + """ + prime offload of nvidia driver is limited. + Only on-demand mode is supported. + """ + # nvidia-smi ship with NVIDIA GPU display drivers on Linux + # https://developer.nvidia.com/nvidia-system-management-interface + # check prime-select to make sure system with nv driver. + # If there is no nv driver, prime offload is fine for other drivers. + ps = subprocess.run("whereis prime-select | cut -d ':' -f 2", + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + if 'prime-select' not in ps.stdout: + return PrimeOffloaderError.NO_ERROR + + # prime offload could running on on-demand mode only + mode = subprocess.run("prime-select query", + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + if "on-demand" not in mode.stdout: + self.logger.info("System isn't on-demand mode") + return PrimeOffloaderError.NOT_SUPPORT_NV_PRIME + + # prime offload couldn't running on nvlink active or inactive + # Therefore, only return empty string is supported environment. + nvlink = subprocess.run("nvidia-smi nvlink -s", + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + if len(nvlink.stdout) != 0: + if re.search('error', nvlink.stdout, re.IGNORECASE): + self.logger.info("nvidia driver error") + return PrimeOffloaderError.NV_DRIVER_ERROR + self.logger.info("NVLINK detected") + return PrimeOffloaderError.NOT_SUPPORT_NV_PRIME + + return PrimeOffloaderError.NO_ERROR + + def run_offload_cmd(self, cmd, pci_name, driver, timeout): + """ + run offload command and check it runs on correct GPU + + :param cmd: command that running under prime offload + :type cmd: str + + :param pci_name: pci device name in NNNN:NN:NN.N format + :type pci_name: str + + :param driver: GPU driver, such as i915, amdgpu, nvidia + :type driver: str + """ + card_id = self.find_card_id(pci_name) + if card_id == PrimeOffloaderError.NO_CARD_ID: + self.logger.info("Couldn't find card id," + " please check your pci name") + return PrimeOffloaderError.NO_CARD_ID + + card_name = self.find_card_name(pci_name) + if card_name is None: + self.logger.info("Couldn't find card name," + " please check your pci name") + return PrimeOffloaderError.NO_CARD_NAME + + # run offload command in other process + dri_pci_name_format = re.sub("[:.]", "_", pci_name) + + if timeout > 0: + tmp_cmd = "timeout {} {}".format(timeout, cmd) + else: + tmp_cmd = cmd + + if driver in ('nvidia', 'pcieport'): + offload_cmd = "__NV_PRIME_RENDER_OFFLOAD=1" \ + " __GLX_VENDOR_LIBRARY_NAME=nvidia {}" \ + .format(tmp_cmd) + else: + offload_cmd = "DRI_PRIME=pci-{} {}" \ + .format(dri_pci_name_format, tmp_cmd) + + # if nv driver under nvidia mode, prime/reverse prime couldn't work. + if self.check_nv_offload_env() \ + == PrimeOffloaderError.NOT_SUPPORT_NV_PRIME: + self.logger.info("Couldn't use nv prime offload" + " on this system environment") + offload_cmd = tmp_cmd + # Couldn't work, always return NO_ERROR + self.check_result = PrimeOffloaderError.NO_ERROR + else: + # use other thread to check offload is correctly or not + check_thread = threading.Thread(target=self.check_offload, + args=(cmd, card_id, card_name)) + check_thread.start() + + offload = subprocess.Popen(offload_cmd, + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + self.logger.info("offload command:[{}]".format(offload_cmd)) + + # redirect offload command output real time + while offload.poll() is None: + line = offload.stdout.readline().strip() + self.logger.info(line) + return PrimeOffloaderError.NO_ERROR + + def main(self) -> int: + """ + main function for command line processing + """ + parser = argparse.ArgumentParser( + prog="Prime offload tester", + description="Test prime offload feature", + ) + + parser.add_argument( + "-c", "--command", type=str, default='glxgears', + help='command to offload to specific GPU (default: %(default)s)' + ) + parser.add_argument( + "-p", "--pci", type=str, default='0000:00:02.0', + help='pci name in NNNN:NN:NN.N format (default: %(default)s)' + ) + parser.add_argument( + "-d", "--driver", type=str, default='i915', + help='Type of GPU driver (default: %(default)s)' + ) + parser.add_argument( + "-t", "--timeout", type=int, default=0, + help='executing command duration in second (default: %(default)s).' + ' If provide 0, then the command will be executed' + ' without timeout.' + ) + + args = parser.parse_args() + + # create self.logger.formatter + log_formatter = logging.Formatter(fmt='%(message)s') + + # create logger + self.logger.setLevel(logging.INFO) + + # create console handler + console_handler = logging.StreamHandler() + console_handler.setFormatter(log_formatter) + + # Add console handler to logger + self.logger.addHandler(console_handler) + + # run_offload_cmd("glxgears", "0000:00:02.0", "i915", 0) + self.run_offload_cmd(args.command, args.pci, args.driver, args.timeout) + + return self.check_result + + +if __name__ == "__main__": + sys.exit(PrimeOffloader().main()) diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py new file mode 100755 index 0000000000..19f6ffa62d --- /dev/null +++ b/providers/base/tests/test_prime_offload_tester.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +# Copyright 2023 Canonical Ltd. +# Written by: +# Hanhsuan Lee +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest +from unittest.mock import Mock, patch + +from prime_offload_tester import * + + +class FindCardIdTests(unittest.TestCase): + """ + This function should extract card id from debug file system by pci name + (pci bus information) + """ + + @patch("subprocess.run") + def test_pci_name_format_check(self, mock_run): + # correct format + mock_run.return_value = Mock(stdout="/sys/kernel/debug/dri/card0/name") + card_id = PrimeOffloader().find_card_id("0000:00:00.0") + self.assertEqual(card_id, "card0") + + # error format - with alphabet + card_id = PrimeOffloader().find_card_id("000r:00:00.0") + self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + + # error format - digital position error + card_id = PrimeOffloader().find_card_id("0000:00:000.0") + self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + + @patch("subprocess.run") + def test_id_not_found(self, mock_run): + # empty string + mock_run.return_value = Mock(stdout="") + card_id = PrimeOffloader().find_card_id("0000:00:00.0") + self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + + # None + mock_run.return_value = Mock(stdout=None) + card_id = PrimeOffloader().find_card_id("0000:00:00.0") + self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + + +class FindCardNameTests(unittest.TestCase): + """ + This function should extract card name from lshw by pci name + (pci bus information) + """ + + lshw_output = '[\ + {\ + "id" : "display",\ + "class" : "display",\ + "claimed" : true,\ + "handle" : "PCI:0000:00:02.0",\ + "description" : "VGA compatible controller",\ + "product" : "TigerLake-LP GT2 [Iris Xe Graphics]",\ + "vendor" : "Intel Corporation",\ + "physid" : "2",\ + "businfo" : "pci@0000:00:02.0",\ + "logicalname" : "/dev/fb0",\ + "version" : "01",\ + "width" : 64,\ + "clock" : 33000000,\ + "configuration" : {\ + "depth" : "32",\ + "driver" : "i915",\ + "latency" : "0",\ + "mode" : "1920x1080",\ + "resolution" : "1920,1080",\ + "visual" : "truecolor",\ + "xres" : "1920",\ + "yres" : "1080"\ + },\ + "capabilities" : {\ + "pciexpress" : "PCI Express",\ + "msi" : "Message Signalled Interrupts",\ + "pm" : "Power Management",\ + "vga_controller" : true,\ + "bus_master" : "bus mastering",\ + "cap_list" : "PCI capabilities listing",\ + "rom" : "extension ROM",\ + "fb" : "framebuffer"\ + }\ + }\ + ]' + + @patch("subprocess.run") + def test_name_found_check(self, mock_run): + mock_run.return_value = Mock(stdout=self.lshw_output) + card_name = PrimeOffloader().find_card_name("0000:00:02.0") + self.assertEqual(card_name, "TigerLake-LP GT2 [Iris Xe Graphics]") + + @patch("subprocess.run") + def test_name_not_found_check(self, mock_run): + # pci_name error + mock_run.return_value = Mock(stdout=self.lshw_output) + card_name = PrimeOffloader().find_card_name("0000:00:00.0") + self.assertEqual(card_name, None) + + # empty string + mock_run.return_value = Mock(stdout="") + card_name = PrimeOffloader().find_card_name("0000:00:00.0") + self.assertEqual(card_name, None) + + # None + mock_run.return_value = Mock(stdout=None) + card_name = PrimeOffloader().find_card_name("0000:00:00.0") + self.assertEqual(card_name, None) + + +class CheckOffloadTests(unittest.TestCase): + """ + This function will check process is showed in specific dri devide + debug file system + """ + + @patch("subprocess.run") + def test_offload_succ_check(self, mock_run): + cmd = "echo" + mock_run.return_value = Mock(stdout=cmd) + pf = PrimeOffloader() + rv = pf.check_offload(cmd, "card_id", "card_name") + self.assertEqual(rv, None) + self.assertEqual(pf.check_result, + PrimeOffloaderError.NO_ERROR) + + @patch("subprocess.run") + def test_offload_fail_check(self, mock_run): + # cmd, card id and card name are checked and won't be None + + # cmd isn't showed in debug file system + # empty string + cmd = "echo" + mock_run.return_value = Mock(stdout="") + pf = PrimeOffloader() + rv = pf.check_offload(cmd, "card_id", "card_name") + self.assertEqual(rv, None) + self.assertEqual(pf.check_result, + PrimeOffloaderError.OFFLOAD_FAIL) + + # None + cmd = "echo" + mock_run.return_value = Mock(stdout=None) + pf = PrimeOffloader() + rv = pf.check_offload(cmd, "card_id", "card_name") + self.assertEqual(rv, None) + self.assertEqual(pf.check_result, + PrimeOffloaderError.OFFLOAD_FAIL) + + # OS Error + # Missing file or permissions + cmd = "echo" + mock_run.side_effect = OSError + pf = PrimeOffloader() + rv = pf.check_offload(cmd, "card_id", "card_name") + self.assertEqual(rv, None) + self.assertEqual(pf.check_result, + PrimeOffloaderError.OFFLOAD_FAIL) + + +class CheckNvOffloadEnvTests(unittest.TestCase): + """ + This function will check this system could use prime offload or not. + Only on-demand mode is supported for NV driver. + """ + + @patch("subprocess.run") + def test_no_prime_select_check(self, mock_run): + mock_run.return_value = Mock(stdout="") + rv = PrimeOffloader().check_nv_offload_env() + self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) + + @patch("subprocess.run") + def test_on_demand_check(self, mock_run): + # with nv driver, not on-demand mode + mock_run.return_value = Mock(stdout="prime-select") + rv = PrimeOffloader().check_nv_offload_env() + self.assertEqual(rv, PrimeOffloaderError.NOT_SUPPORT_NV_PRIME) + + @patch("subprocess.run") + def test_nvlink_check(self, mock_run): + # with nv driver, on-demand mode. This might be NVLINK environment + mock_run.return_value = Mock(stdout="prime-select on-demand") + rv = PrimeOffloader().check_nv_offload_env() + self.assertEqual(rv, PrimeOffloaderError.NOT_SUPPORT_NV_PRIME) + + # with nv driver, on-demand mode, nv driver error + mock_run.return_value = Mock(stdout="prime-select on-demand error") + rv = PrimeOffloader().check_nv_offload_env() + self.assertEqual(rv, PrimeOffloaderError.NV_DRIVER_ERROR) + + # with nv driver, on-demand mode, no nv driver error + mock_run.return_value = Mock(stdout=["prime-select on-demand", + "prime-select on-demand", + ""]) + rv = PrimeOffloader().check_nv_offload_env() + self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) + + +class RunOffloadCmdTests(unittest.TestCase): + """ + This function is the entry point to run the command with prime offload, + if the environment is supported. + """ + + def test_condition_check(self): + # no card id + pf = PrimeOffloader() + pf.find_card_id = Mock(return_value=PrimeOffloaderError.NO_CARD_ID) + rv = pf.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) + self.assertEqual(rv, PrimeOffloaderError.NO_CARD_ID) + + # no card name + pf.find_card_id = Mock(return_value="card0") + pf.find_card_name = Mock(return_value=None) + rv = pf.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) + self.assertEqual(rv, PrimeOffloaderError.NO_CARD_NAME) + + @patch("subprocess.Popen") + def test_offload_cmd_check(self, mock_open): + # non NV driver + pf = PrimeOffloader() + pf.find_card_id = Mock(return_value="card0") + pf.find_card_name = Mock(return_value="Intel") + pf.check_nv_offload_env =\ + Mock(return_value=PrimeOffloaderError.NO_ERROR) + pf.check_offload = Mock(return_value="") + rv = pf.run_offload_cmd("echo", "0000:00:00.0", "xxx", 0) + # check run_offload_cmd executing correct command + mock_open.assert_called_with('DRI_PRIME=pci-0000_00_00_0 echo', + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + # check check_offload function get correct args + pf.check_offload.assert_called_with('echo', 'card0', 'Intel') + + # NV driver + pf = PrimeOffloader() + pf.find_card_id = Mock(return_value="card0") + pf.find_card_name = Mock(return_value="NV") + pf.check_nv_offload_env =\ + Mock(return_value=PrimeOffloaderError.NO_ERROR) + pf.check_offload = Mock(return_value="") + rv = pf.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 0) + # check run_offload_cmd executing correct command + mock_open.assert_called_with('__NV_PRIME_RENDER_OFFLOAD=1' + ' __GLX_VENDOR_LIBRARY_NAME=nvidia echo', + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + # check check_offload function get correct args + pf.check_offload.assert_called_with('echo', 'card0', 'NV') + + +if __name__ == '__main__': + unittest.main() diff --git a/providers/base/units/graphics/jobs.pxu b/providers/base/units/graphics/jobs.pxu index 34c2294303..a1dc95df9b 100644 --- a/providers/base/units/graphics/jobs.pxu +++ b/providers/base/units/graphics/jobs.pxu @@ -289,6 +289,78 @@ command: fi _summary: Check the OpenGL renderer (AMD GPU and DRI_PRIME=1, nvidia GPU and __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia) +unit: template +template-resource: graphics_card +plugin: shell +category_id: com.canonical.plainbox::graphics +id: graphics/{index}_auto_glxgears_{product_slug} +flags: also-after-suspend +requires: executable.name == 'glxgears' +command: + # shellcheck disable=SC1091 + prime_offload_tester.py -c glxgears -p {pci_device_name} -d {driver} -t 30 +estimated_duration: 30s +summary: + Auto-Test that glxgears works for {vendor} {product} + +unit: template +template-resource: graphics_card +plugin: user-interact-verify +category_id: com.canonical.plainbox::graphics +id: graphics/{index}_valid_glxgears_{product_slug} +flags: also-after-suspend +requires: executable.name == 'glxgears' +command: + # shellcheck disable=SC1091 + prime_offload_tester.py -c glxgears -p {pci_device_name} -d {driver} -t 30 +estimated_duration: 30s +summary: + Maunal-Test that glxgears works for {vendor} {product} +purpose: + This test tests the basic 3D capabilities of your {vendor} {product} video card +steps: + 1. Click "Test" to execute an OpenGL demo, and will be closed after 30s. + 2. Verify that the animation is not jerky or slow. +verification: + 1. Did the 3d animation appear? + 2. Was the animation free from slowness/jerkiness? + +unit: template +template-resource: graphics_card +plugin: shell +category_id: com.canonical.plainbox::graphics +id: graphics/{index}_auto_glxgears_fullscreen_{product_slug} +flags: also-after-suspend +requires: executable.name == 'glxgears' +command: + # shellcheck disable=SC1091 + prime_offload_tester.py -c "glxgears -fullscreen" -p {pci_device_name} -d {driver} -t 30 +estimated_duration: 30s +summary: + Auto-Test that glxgears works on fullscreen for {vendor} {product} + +unit: template +template-resource: graphics_card +plugin: user-interact-verify +category_id: com.canonical.plainbox::graphics +id: graphics/{index}_valid_glxgears_fullscreen_{product_slug} +flags: also-after-suspend +requires: executable.name == 'glxgears' +command: + # shellcheck disable=SC1091 + prime_offload_tester.py -c "glxgears -fullscreen" -p {pci_device_name} -d {driver} -t 30 +estimated_duration: 30s +summary: + Maunal-Test that glxgears works on fullscreen for {vendor} {product} +purpose: + This test tests the basic fullscreen 3D capabilities of your {vendor} {product} video card +steps: + 1. Click "Test" to execute an OpenGL demo, and will be closed after 30s. + 2. Verify that the animation is not jerky or slow. +verification: + 1. Did the 3d animation appear? + 2. Was the animation free from slowness/jerkiness? + unit: template template-resource: graphics_card plugin: user-interact-verify diff --git a/providers/base/units/graphics/test-plan.pxu b/providers/base/units/graphics/test-plan.pxu index b9a4e32157..1e0c325d5b 100644 --- a/providers/base/units/graphics/test-plan.pxu +++ b/providers/base/units/graphics/test-plan.pxu @@ -1,3 +1,90 @@ +id: graphics-gpu-cert-full +unit: test plan +_name: Graphics tests (GPU) +_description: + Graphics tests (GPU) +include: +bootstrap_include: + graphics_card +nested_part: + com.canonical.certification::graphics-gpu-cert-automated + com.canonical.certification::graphics-gpu-cert-manual + +id: graphics-gpu-cert-automated +unit: test plan +_name: Graphics tests (Automated) +_description: + Graphics tests (Automated) +include: + graphics/VESA_drivers_not_in_use certification-status=blocker + graphics/1_driver_version_.* certification-status=blocker + graphics/1_gl_support_.* certification-status=blocker + graphics/1_minimum_resolution_.* + graphics/2_driver_version_.* certification-status=blocker + graphics/2_minimum_resolution_.* + graphics/1_auto_glxgears_.* certification-status=blocker + graphics/2_auto_glxgears_.* certification-status=blocker + suspend/resolution_before_suspend certification-status=blocker +bootstrap_include: + graphics_card + +id: graphics-gpu-cert-manual +unit: test plan +_name: Graphics tests (Manual) +_description: + Graphics tests (Manual) +include: + miscellanea/chvt + graphics/1_maximum_resolution_.* certification-status=blocker + graphics/1_rotation_.* certification-status=blocker + graphics/1_video_.* certification-status=blocker + graphics/1_cycle_resolution_.* certification-status=non-blocker + graphics/1_valid_glxgears_.* certification-status=blocker + graphics/2_valid_glxgears_.* certification-status=blocker +bootstrap_include: + graphics_card + +id: after-suspend-graphics-gpu-cert-full +unit: test plan +_name: After suspend tests +_description: After suspend tests +include: +nested_part: + com.canonical.certification::after-suspend-graphics-gpu-cert-automated + com.canonical.certification::after-suspend-graphics-gpu-cert-manual + +id: after-suspend-graphics-gpu-cert-automated +unit: test plan +_name: After suspend tests (GPU automated) +_description: After suspend tests (GPU automated) +include: + suspend/suspend-time-check certification-status=non-blocker + suspend/suspend-single-log-attach certification-status=non-blocker + after-suspend-graphics/VESA_drivers_not_in_use certification-status=blocker + after-suspend-graphics/1_driver_version_.* certification-status=blocker + after-suspend-graphics/1_gl_support_.* certification-status=blocker + after-suspend-graphics/1_minimum_resolution_.* + after-suspend-graphics/1_auto_glxgears_.* certification-status=blocker + after-suspend-graphics/2_auto_glxgears_.* certification-status=blocker + suspend/resolution_after_suspend certification-status=blocker + +id: after-suspend-graphics-gpu-cert-manual +unit: test plan +_name: After suspend tests (GPU manual) +_description: After suspend tests (GPU manual) +include: + power-management/lid_close_suspend_open certification-status=blocker + power-management/lid certification-status=blocker + after-suspend-miscellanea/chvt + suspend/display_after_suspend certification-status=blocker + after-suspend-graphics/1_maximum_resolution_.* certification-status=blocker + after-suspend-graphics/1_rotation_.* certification-status=blocker + after-suspend-graphics/1_video_.* certification-status=blocker + after-suspend-graphics/1_cycle_resolution_.* certification-status=non-blocker + after-suspend-graphics/1_valid_glxgears_.* certification-status=blocker + after-suspend-graphics/2_valid_glxgears_.* certification-status=blocker + suspend/1_xrandr_screens_after_suspend.tar.gz_auto + id: graphics-integrated-gpu-cert-full unit: test plan _name: Graphics tests (integrated GPU) @@ -228,4 +315,4 @@ include: suspend/2_display_after_suspend_.*_graphics certification-status=blocker suspend/2_glxgears_after_suspend_.*_graphics certification-status=blocker suspend/2_rotation_after_suspend_.*_graphics certification-status=blocker - suspend/2_video_after_suspend_.*_graphics certification-status=blocker \ No newline at end of file + suspend/2_video_after_suspend_.*_graphics certification-status=blocker diff --git a/providers/base/units/monitor/test-plan.pxu b/providers/base/units/monitor/test-plan.pxu index 7c7b73a8ba..78fbf96730 100644 --- a/providers/base/units/monitor/test-plan.pxu +++ b/providers/base/units/monitor/test-plan.pxu @@ -1,3 +1,92 @@ +id: monitor-gpu-cert-full +unit: test plan +_name: Monitor tests (GPU) +_description: + Monitor tests (GPU) +include: +bootstrap_include: + graphics_card +nested_part: + com.canonical.certification::monitor-gpu-cert-manual + com.canonical.certification::monitor-gpu-cert-automated + +id: monitor-gpu-cert-automated +unit: test plan +_name: Monitor tests (GPU) (Automated) +_description: + Monitor tests (GPU) (Automated) +include: +bootstrap_include: + graphics_card + +id: monitor-gpu-cert-manual +unit: test plan +_name: Monitor tests (GPU) (Manual) +_description: + Monitor tests (GPU) (Manual) +include: + monitor/1_powersaving_.* certification-status=blocker + power-management/light_sensor + monitor/1_dim_brightness_.* certification-status=blocker + monitor/1_displayport_.* certification-status=blocker + audio/1_playback_displayport_.* certification-status=blocker + monitor/1_type-c_displayport_.* certification-status=blocker + audio/1_playback_type-c_displayport_.* certification-status=blocker + monitor/1_type-c_hdmi_.* certification-status=blocker + audio/1_playback_type-c_hdmi_.* certification-status=blocker + monitor/1_type-c_vga_.* certification-status=blocker + monitor/1_dvi_.* certification-status=blocker + monitor/1_hdmi_.* certification-status=blocker + audio/1_playback_hdmi_.* certification-status=blocker + monitor/1_thunderbolt3_.* certification-status=non-blocker + audio/1_playback_thunderbolt3_.* certification-status=non-blocker + thunderbolt3/daisy-chain certification-status=non-blocker +bootstrap_include: + graphics_card + +id: after-suspend-monitor-gpu-cert-full +unit: test plan +_name: Monitor tests (after suspend, GPU) +_description: Monitor tests (after suspend, GPU) +include: +nested_part: + after-suspend-monitor-gpu-cert-manual + after-suspend-monitor-gpu-cert-automated + +id: after-suspend-monitor-gpu-cert-automated +unit: test plan +_name: Monitor tests (after suspend, GPU) (Automated) +_description: + Monitor tests (after suspend, GPU) (Automated) +include: +bootstrap_include: + graphics_card + +id: after-suspend-monitor-gpu-cert-manual +unit: test plan +_name: Monitor tests (after suspend, GPU) (Manual) +_description: + Monitor tests (after suspend, GPU) (Manual) +include: + after-suspend-monitor/1_powersaving_.* certification-status=blocker + after-suspend-power-management/light_sensor + after-suspend-monitor/1_dim_brightness_.* certification-status=blocker + after-suspend-monitor/1_displayport_.* certification-status=blocker + after-suspend-audio/1_playback_displayport_.* certification-status=blocker + after-suspend-monitor/1_type-c_displayport_.* certification-status=blocker + after-suspend-audio/1_playback_type-c_displayport_.* certification-status=blocker + after-suspend-monitor/1_type-c_hdmi_.* certification-status=blocker + after-suspend-audio/1_playback_type-c_hdmi_.* certification-status=blocker + after-suspend-monitor/1_type-c_vga_.* certification-status=blocker + after-suspend-monitor/1_dvi_.* certification-status=blocker + after-suspend-monitor/1_hdmi_.* certification-status=blocker + after-suspend-audio/1_playback_hdmi_.* certification-status=blocker + after-suspend-monitor/1_thunderbolt3_.* certification-status=non-blocker + after-suspend-audio/1_playback_thunderbolt3_.* certification-status=non-blocker + after-suspend-thunderbolt3/daisy-chain certification-status=non-blocker +bootstrap_include: + graphics_card + id: monitor-integrated-gpu-cert-full unit: test plan _name: Monitor tests (integrated GPU) diff --git a/providers/base/units/suspend/suspend.pxu b/providers/base/units/suspend/suspend.pxu index d1c45640e0..ac93aeac34 100644 --- a/providers/base/units/suspend/suspend.pxu +++ b/providers/base/units/suspend/suspend.pxu @@ -338,6 +338,7 @@ _summary: Attaches the log from the single hybrid sleep/resume test plugin: shell category_id: com.canonical.plainbox::suspend id: suspend/suspend-time-check +depends: suspend/suspend_advanced_auto estimated_duration: 1.2 command: [ -e "$PLAINBOX_SESSION_SHARE"/suspend_single_times.log ] && sleep_time_check.py "$PLAINBOX_SESSION_SHARE"/suspend_single_times.log _summary: Ensure time to suspend/resume is under threshold 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 146cb76546..c2b0a56865 100644 --- a/providers/certification-client/units/client-cert-desktop-22-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-22-04.pxu @@ -24,10 +24,8 @@ nested_part: bluetooth-cert-manual camera-cert-manual thunderbolt-cert-manual - monitor-integrated-gpu-cert-manual - graphics-integrated-gpu-cert-manual - graphics-discrete-gpu-cert-manual - monitor-discrete-gpu-cert-manual + monitor-gpu-cert-manual + graphics-gpu-cert-manual cpu-cert-manual input-cert-manual disk-cert-manual @@ -51,14 +49,8 @@ nested_part: wireless-cert-manual # start of suspend related tests # suspend point - # Test discrete card first, if present, since it's the one we will be using - # after coming back from suspend if the system has hybrid graphics. - after-suspend-graphics-discrete-gpu-cert-manual - after-suspend-monitor-discrete-gpu-cert-full - # Now we ask to switch to the integrated graphics card. This requires a - # restart of checkbox. - after-suspend-graphics-integrated-gpu-cert-manual - after-suspend-monitor-integrated-gpu-cert-full + after-suspend-graphics-gpu-cert-manual + after-suspend-monitor-gpu-cert-manual suspend-key-led-oops-check-cert after-suspend-audio-cert-full after-suspend-bluetooth-cert-manual @@ -98,10 +90,8 @@ nested_part: bluetooth-cert-automated camera-cert-automated thunderbolt-cert-automated - monitor-integrated-gpu-cert-automated - graphics-integrated-gpu-cert-automated - graphics-discrete-gpu-cert-automated - monitor-discrete-gpu-cert-automated + monitor-gpu-cert-automated + graphics-gpu-cert-automated cpu-cert-automated input-cert-automated disk-cert-automated @@ -126,14 +116,9 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full - # Test discrete card first, if present, since it's the one we will be using - # after coming back from suspend if the system has hybrid graphics. after-suspend-audio-cert-automated - after-suspend-graphics-discrete-gpu-cert-automated - # after-suspend-monitor-discrete-gpu-cert-automated # not defined - # Now we ask to switch to the integrated graphics card. - after-suspend-graphics-integrated-gpu-cert-automated - # after-suspend-monitor-integrated-gpu-cert-automated # not defined + after-suspend-graphics-gpu-cert-automated + after-suspend-monitor-gpu-cert-automated after-suspend-cpu-cert-automated after-suspend-input-cert-automated after-suspend-disk-cert-automated 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 af3cd4493e..a2590df277 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 @@ -13,10 +13,8 @@ nested_part: camera-cert-manual edac-manual thunderbolt-cert-manual - monitor-integrated-gpu-cert-manual - graphics-integrated-gpu-cert-manual - graphics-discrete-gpu-cert-manual - monitor-discrete-gpu-cert-manual + monitor-gpu-cert-manual + graphics-gpu-cert-manual input-cert-manual disk-cert-manual keys-cert-manual @@ -39,14 +37,8 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full - # Test discrete card first, if present, since it's the one we will be using - # after coming back from suspend if the system has hybrid graphics. - after-suspend-graphics-discrete-gpu-cert-manual - after-suspend-monitor-discrete-gpu-cert-full - # Now we ask to switch to the integrated graphics card. This requires a - # restart of checkbox. - after-suspend-graphics-integrated-gpu-cert-manual - after-suspend-monitor-integrated-gpu-cert-full + after-suspend-graphics-gpu-cert-manual + after-suspend-monitor-gpu-cert-manual suspend-key-led-oops-check-cert after-suspend-audio-cert-full # no-manual only after-suspend-bluetooth-cert-manual @@ -115,10 +107,8 @@ nested_part: camera-cert-automated edac-automated thunderbolt-cert-automated - monitor-integrated-gpu-cert-automated - graphics-integrated-gpu-cert-automated - graphics-discrete-gpu-cert-automated - monitor-discrete-gpu-cert-automated + monitor-gpu-cert-automated + graphics-gpu-cert-automated input-cert-automated disk-cert-manual keys-cert-automated @@ -140,14 +130,8 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full - # Test discrete card first, if present, since it's the one we will be using - # after coming back from suspend if the system has hybrid graphics. - after-suspend-graphics-discrete-gpu-cert-automated - # after-suspend-monitor-discrete-gpu-cert-automated # not defined - # Now we ask to switch to the integrated graphics card. This requires a - # restart of checkbox. - after-suspend-graphics-integrated-gpu-cert-automated - # after-suspend-monitor-integrated-gpu-cert-automated # not defined + after-suspend-graphics-gpu-cert-automated + after-suspend-monitor-gpu-cert-automated suspend-key-led-oops-check-cert # after-suspend-audio-cert-automated # after-suspend-camera-cert-automated diff --git a/providers/resource/bin/graphics_card_resource.py b/providers/resource/bin/graphics_card_resource.py index 9a0c2435cc..78e5e5d859 100755 --- a/providers/resource/bin/graphics_card_resource.py +++ b/providers/resource/bin/graphics_card_resource.py @@ -105,6 +105,9 @@ def udev_devices(lines): key, value = line.split(":", 1) key = key.strip() record[key] = value.strip() + if key == 'path': + v = value.strip().split('/') + record['pci_device_name'] = v[len(v) - 1] except ValueError: # If a line has no colon it's suspicious, maybe a # bogus input file. Let's discard it. From 0d3acb1956966ab7568e330f194d3396a8af0144 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 31 Oct 2023 10:11:51 +0800 Subject: [PATCH 02/15] Add more unit test for graphics_card_resource.py and prime_offload_tester.py --- .../base/tests/test_prime_offload_tester.py | 24 +++++++++++-- .../tests/test_graphics_card_resource.py | 35 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100755 providers/resource/tests/test_graphics_card_resource.py diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 19f6ffa62d..ea61f96c1f 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -205,9 +205,9 @@ def test_nvlink_check(self, mock_run): self.assertEqual(rv, PrimeOffloaderError.NV_DRIVER_ERROR) # with nv driver, on-demand mode, no nv driver error - mock_run.return_value = Mock(stdout=["prime-select on-demand", - "prime-select on-demand", - ""]) + mock_run.side_effect = [Mock(stdout="prime-select on-demand"), + Mock(stdout="prime-select on-demand"), + Mock(stdout="")] rv = PrimeOffloader().check_nv_offload_env() self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) @@ -249,6 +249,23 @@ def test_offload_cmd_check(self, mock_open): # check check_offload function get correct args pf.check_offload.assert_called_with('echo', 'card0', 'Intel') + # non NV driver with timeout setting + pf = PrimeOffloader() + pf.find_card_id = Mock(return_value="card0") + pf.find_card_name = Mock(return_value="Intel") + pf.check_nv_offload_env =\ + Mock(return_value=PrimeOffloaderError.NO_ERROR) + pf.check_offload = Mock(return_value="") + rv = pf.run_offload_cmd("echo", "0000:00:00.0", "xxx", 1) + # check run_offload_cmd executing correct command + mock_open.assert_called_with("DRI_PRIME=pci-0000_00_00_0 " + "timeout 1 echo", + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + # check check_offload function get correct args + pf.check_offload.assert_called_with('echo', 'card0', 'Intel') + # NV driver pf = PrimeOffloader() pf.find_card_id = Mock(return_value="card0") @@ -265,6 +282,7 @@ def test_offload_cmd_check(self, mock_open): universal_newlines=True) # check check_offload function get correct args pf.check_offload.assert_called_with('echo', 'card0', 'NV') + print(rv) if __name__ == '__main__': diff --git a/providers/resource/tests/test_graphics_card_resource.py b/providers/resource/tests/test_graphics_card_resource.py new file mode 100755 index 0000000000..4231a96b11 --- /dev/null +++ b/providers/resource/tests/test_graphics_card_resource.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# This file is part of Checkbox. +# +# Copyright 2023 Canonical Ltd. +# Authors: Hanhsuan Lee +# +# Checkbox is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, +# as published by the Free Software Foundation. +# +# Checkbox 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 Checkbox. If not, see . + +import unittest +from graphics_card_resource import * + + +class UdevDevicesTests(unittest.TestCase): + record_line = ["path: /devices/pci0000:00/0000:00:02.1/0000:01:00.0"] + + def test_success(self): + record = udev_devices(self.record_line) + record_list = list(record) + self.assertEqual(len(record_list), 1) + self.assertEqual(record_list[0]['pci_device_name'], "0000:01:00.0") + + +if __name__ == '__main__': + unittest.main() From 2c0f5862ef3be943ad3e2fc37f941193167850c1 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 31 Oct 2023 14:04:58 +0800 Subject: [PATCH 03/15] Add one more unit test --- .../base/tests/test_prime_offload_tester.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index ea61f96c1f..9c3eded2a5 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -282,7 +282,21 @@ def test_offload_cmd_check(self, mock_open): universal_newlines=True) # check check_offload function get correct args pf.check_offload.assert_called_with('echo', 'card0', 'NV') - print(rv) + + # Not support nvidia prime + pf = PrimeOffloader() + pf.find_card_id = Mock(return_value="card0") + pf.find_card_name = Mock(return_value="NV") + pf.check_nv_offload_env =\ + Mock(return_value=PrimeOffloaderError.NOT_SUPPORT_NV_PRIME) + pf.check_offload = Mock(return_value="") + rv = pf.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 0) + # check run_offload_cmd executing correct command + mock_open.assert_called_with('echo', + shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) if __name__ == '__main__': From 098b0600133a68c73c2ce835d357befa14aa870e Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 31 Oct 2023 15:52:02 +0800 Subject: [PATCH 04/15] move parse arguments to single function for unit testing --- providers/base/bin/prime_offload_tester.py | 13 +++- .../base/tests/test_prime_offload_tester.py | 60 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index eaadbe3353..8dafef4e5b 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -275,9 +275,9 @@ def run_offload_cmd(self, cmd, pci_name, driver, timeout): self.logger.info(line) return PrimeOffloaderError.NO_ERROR - def main(self) -> int: + def parse_args(self, args=sys.argv[1:]): """ - main function for command line processing + command line arguments parsing """ parser = argparse.ArgumentParser( prog="Prime offload tester", @@ -302,8 +302,15 @@ def main(self) -> int: ' If provide 0, then the command will be executed' ' without timeout.' ) + return parser.parse_args(args) + + + def main(self) -> int: + """ + main function for command line processing + """ - args = parser.parse_args() + args = self.parse_args() # create self.logger.formatter log_formatter = logging.Formatter(fmt='%(message)s') diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 9c3eded2a5..d217d799d9 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -298,6 +298,66 @@ def test_offload_cmd_check(self, mock_open): universal_newlines=True) self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) +class parseArgsTests(unittest.TestCase): + def test_success(self): + pf = PrimeOffloader() + # no arguments, load default + args = [] + rv = pf.parse_args(args) + self.assertEqual(rv.command, "glxgears") + self.assertEqual(rv.pci, "0000:00:02.0") + self.assertEqual(rv.driver, "i915") + self.assertEqual(rv.timeout, 0) + + pf = PrimeOffloader() + # change command + args = ["-c", "glxgears -fullscreen"] + rv = pf.parse_args(args) + self.assertEqual(rv.command, "glxgears -fullscreen") + self.assertEqual(rv.pci, "0000:00:02.0") + self.assertEqual(rv.driver, "i915") + self.assertEqual(rv.timeout, 0) + + pf = PrimeOffloader() + # change pci + args = ["-p", "0000:00:01.0"] + rv = pf.parse_args(args) + self.assertEqual(rv.command, "glxgears") + self.assertEqual(rv.pci, "0000:00:01.0") + self.assertEqual(rv.driver, "i915") + self.assertEqual(rv.timeout, 0) + + pf = PrimeOffloader() + # change driver + args = ["-d", "nvidia"] + rv = pf.parse_args(args) + self.assertEqual(rv.command, "glxgears") + self.assertEqual(rv.pci, "0000:00:02.0") + self.assertEqual(rv.driver, "nvidia") + self.assertEqual(rv.timeout, 0) + + pf = PrimeOffloader() + # change timeout + args = ["-t", "5"] + rv = pf.parse_args(args) + self.assertEqual(rv.command, "glxgears") + self.assertEqual(rv.pci, "0000:00:02.0") + self.assertEqual(rv.driver, "i915") + self.assertEqual(rv.timeout, 5) + + pf = PrimeOffloader() + # change all + args = ["-c", "glxgears -fullscreen", + "-p", "0000:00:01.0", + "-d", "nvidia", + "-t", "5"] + rv = pf.parse_args(args) + self.assertEqual(rv.command, "glxgears -fullscreen") + self.assertEqual(rv.pci, "0000:00:01.0") + self.assertEqual(rv.driver, "nvidia") + self.assertEqual(rv.timeout, 5) + + if __name__ == '__main__': unittest.main() From 93c56348983bbdf1859004f583fd2fa7ca987347 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 31 Oct 2023 15:58:39 +0800 Subject: [PATCH 05/15] Fix flake8 error --- providers/base/bin/prime_offload_tester.py | 1 - providers/base/tests/test_prime_offload_tester.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index 8dafef4e5b..0ad685f4cb 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -304,7 +304,6 @@ def parse_args(self, args=sys.argv[1:]): ) return parser.parse_args(args) - def main(self) -> int: """ main function for command line processing diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index d217d799d9..00191883c7 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -298,7 +298,8 @@ def test_offload_cmd_check(self, mock_open): universal_newlines=True) self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) -class parseArgsTests(unittest.TestCase): + +class ParseArgsTests(unittest.TestCase): def test_success(self): pf = PrimeOffloader() # no arguments, load default @@ -358,6 +359,5 @@ def test_success(self): self.assertEqual(rv.timeout, 5) - if __name__ == '__main__': unittest.main() From 652bac124d704d0ee04864277edb3e174ed0100c Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Mon, 18 Dec 2023 13:22:10 +0800 Subject: [PATCH 06/15] 1. Refactory to be more like python 2. add extra method for avoid checking fail by 6.5 kernel bug --- providers/base/bin/prime_offload_tester.py | 348 +++++----- .../base/tests/test_prime_offload_tester.py | 649 ++++++++++++------ providers/base/units/graphics/jobs.pxu | 4 + 3 files changed, 642 insertions(+), 359 deletions(-) mode change 100755 => 100644 providers/base/bin/prime_offload_tester.py diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py old mode 100755 new mode 100644 index 0ad685f4cb..a6519fb77c --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -"""prime offload test module.""" # This file is part of Checkbox. # # Copyright 2023 Canonical Ltd. @@ -26,39 +25,7 @@ import json import argparse import logging -from enum import IntEnum - - -class PrimeOffloaderError(IntEnum): - """ - A class used to define PrimeOffloader Error code - - :attr NO_ERROR: process success - :type NO_ERROR: int - - :attr NO_CARD_ID: couldn't find card id - :type NO_CARD_ID: int - - :attr NO_CARD_NAME: couldn't find card name - :type NO_CARD_NAME: int - - :attr OFFLOAD_FAIL: couldn't find process on specific GPU - :type OFFLOAD_FAIL: int - - :attr NOT_SUPPORT_NV_PRIME: this system couldn't support nv prime offload - :type NOT_SUPPORT_NV_PRIME: int - - :attr NV_DRIVER_ERROR: some errors form nvidia driver - :type NV_DRIVER_ERROR: int - - """ - - NO_ERROR = 0 - NO_CARD_ID = -1 - NO_CARD_NAME = -2 - OFFLOAD_FAIL = -3 - NOT_SUPPORT_NV_PRIME = -4 - NV_DRIVER_ERROR = -5 +import os class PrimeOffloader: @@ -75,8 +42,7 @@ class PrimeOffloader: """ logger = logging.getLogger() - - check_result = PrimeOffloaderError.OFFLOAD_FAIL + check_result = False def find_card_id(self, pci_name): """ @@ -84,20 +50,27 @@ def find_card_id(self, pci_name): :param pci_name: pci device name in NNNN:NN:NN.N format :type card_name: str + + : returns : card id + : rtype : str """ - cmd = "sudo grep -lr --include=name \"{}\"" \ - " /sys/kernel/debug/dri 2>/dev/null".format(pci_name) - try: - if not re.match("[0-9]{4}:[0-9]{2}:[0-9]{2}.[0-9]", pci_name): - return PrimeOffloaderError.NO_CARD_ID + if not re.match("[0-9]{4}:[0-9]{2}:[0-9]{2}.[0-9]", pci_name): + raise RuntimeError("pci name format error") - card_path = subprocess.run(cmd, shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) - card_id = card_path.stdout.split('/')[5] - return card_id - except (ValueError, IndexError, AttributeError): - return PrimeOffloaderError.NO_CARD_ID + try: + cmd = ["grep", + "-lr", + "--include=name", + pci_name, + "/sys/kernel/debug/dri"] + + card_path = subprocess.check_output(cmd, + universal_newlines=True) + return card_path.split('/')[5] + except (IndexError, AttributeError) as e: + raise RuntimeError("return value format error {}".format(e)) + except subprocess.CalledProcessError as e: + raise RuntimeError(e) def find_card_name(self, pci_name): """ @@ -105,20 +78,51 @@ def find_card_name(self, pci_name): :param pci_name: pci device name in NNNN:NN:NN.N format :type card_name: str + + : returns : card name + : rtype : str """ - cmd = 'sudo lshw -c display -json' + cmd = ["lshw", "-c", "display", "-json"] try: - card_infos = subprocess.run(cmd, shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) - infos = json.loads(card_infos.stdout) + card_infos = subprocess.check_output(cmd, + universal_newlines=True) + infos = json.loads(card_infos) for info in infos: if pci_name in info['businfo']: return info['product'] - except (ValueError, IndexError, TypeError): - return None + raise RuntimeError("Card name not found") + except (KeyError, TypeError, json.decoder.JSONDecodeError) as e: + raise RuntimeError("return value format error {}".format(e)) + except subprocess.CalledProcessError as e: + raise RuntimeError(e) + + def check_for_kernel_bug(self, b, a): + """ + for 6.5 kernel, a bug of dir debugfs should be fixed. + before that, we have to check the difference + https://warthogs.atlassian.net/browse/OEMQA-3459 + + :param b: clients before test + :type b: str + + :param a: clients after test + :type a: str + + : returns : check result + : rtype : Boolean + """ + line_b = b.strip().splitlines() + line_a = a.strip().splitlines() + + len_b = len(line_b) + len_a = len(line_a) - def check_offload(self, cmd, card_id, card_name): + if (len_b == (len_a - 1) + and any(x in line_a[len_a - 1] for x in ('Xorg', 'Xwayland'))): + return True + return False + + def check_offload(self, cmd, card_id, card_name, timeout): """ Use to check provided command is executed on specific GPU. @@ -131,41 +135,56 @@ def check_offload(self, cmd, card_id, card_name): /sys/kernel/debug/dri//clients :param cmd: command that running under prime offload - :type cmd: str + :type cmd: list :param card_id: card id of dri device :type card_id: str :param card_name: card name of dri device :type card_name: str + + :param timeout: timeout for offloaded command + :type timeout: int """ - cmd_without_args = cmd.split(' ')[0] + if timeout == 0: + delay = 2 + else: + delay = timeout / 10 + + pre_test_clients = "" + for index in range(1, 11): - time.sleep(2) + time.sleep(delay) try: - read_clients_cmd = \ - "sudo cat /sys/kernel/debug/dri/{}/clients".format(card_id) - clients = subprocess.run(read_clients_cmd, shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) - if cmd_without_args in clients.stdout: + read_clients_cmd = ["cat", + "/sys/kernel/debug/dri/{}/clients" + .format(card_id)] + clients = subprocess.check_output(read_clients_cmd, + universal_newlines=True) + if not pre_test_clients: + pre_test_clients = clients + continue + if cmd[0] in clients: + self.logger.info("Checking success:") + self.logger.info(" Offload process:[{}]".format(cmd)) + self.logger.info(" Card ID:[{}]".format(card_id)) + self.logger.info(" Device Name:[{}]".format(card_name)) + return + elif self.check_for_kernel_bug(pre_test_clients, clients): + self.logger.info("correct for 6.5 kernel bug only") self.logger.info("Checking success:") self.logger.info(" Offload process:[{}]".format(cmd)) self.logger.info(" Card ID:[{}]".format(card_id)) self.logger.info(" Device Name:[{}]".format(card_name)) - self.check_result = PrimeOffloaderError.NO_ERROR return - self.check_result = PrimeOffloaderError.OFFLOAD_FAIL - except (OSError, TypeError): + except (subprocess.CalledProcessError, OSError, TypeError) as e: # Missing file or permissions? - self.logger.info("Couldn't open file for" - "reading clients of dri device") - self.check_result = PrimeOffloaderError.OFFLOAD_FAIL - return + self.logger.info(e) + self.check_result = True self.logger.info("Checking fail:") self.logger.info(" Couldn't find process [{}]".format(cmd) + " running after check {} times".format(index)) - self.check_result = PrimeOffloaderError.OFFLOAD_FAIL + self.check_result = True def check_nv_offload_env(self): """ @@ -176,36 +195,33 @@ def check_nv_offload_env(self): # https://developer.nvidia.com/nvidia-system-management-interface # check prime-select to make sure system with nv driver. # If there is no nv driver, prime offload is fine for other drivers. - ps = subprocess.run("whereis prime-select | cut -d ':' -f 2", - shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) - if 'prime-select' not in ps.stdout: - return PrimeOffloaderError.NO_ERROR - - # prime offload could running on on-demand mode only - mode = subprocess.run("prime-select query", - shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) - if "on-demand" not in mode.stdout: - self.logger.info("System isn't on-demand mode") - return PrimeOffloaderError.NOT_SUPPORT_NV_PRIME + try: + ps = subprocess.check_output(["which", "prime-select"], + universal_newlines=True) + + if 'prime-select' in ps: + # prime offload could running on on-demand mode only + mode = subprocess.check_output(["prime-select", "query"], + universal_newlines=True) + if "on-demand" not in mode: + raise RuntimeError("System isn't on-demand mode") + else: + self.logger.info( + "No prime-select, it should be ok to run prime offload") + return + except subprocess.CalledProcessError: + self.logger.info( + "No prime-select, it should be ok to run prime offload") + return # prime offload couldn't running on nvlink active or inactive # Therefore, only return empty string is supported environment. - nvlink = subprocess.run("nvidia-smi nvlink -s", - shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) - if len(nvlink.stdout) != 0: - if re.search('error', nvlink.stdout, re.IGNORECASE): - self.logger.info("nvidia driver error") - return PrimeOffloaderError.NV_DRIVER_ERROR - self.logger.info("NVLINK detected") - return PrimeOffloaderError.NOT_SUPPORT_NV_PRIME - - return PrimeOffloaderError.NO_ERROR + nvlink = subprocess.check_output(["nvidia-smi", "nvlink", "-s"], + universal_newlines=True) + if nvlink: + if 'error' in nvlink.lower(): + raise RuntimeError("nvidia driver error") + raise RuntimeError("NVLINK detected") def run_offload_cmd(self, cmd, pci_name, driver, timeout): """ @@ -219,65 +235,70 @@ def run_offload_cmd(self, cmd, pci_name, driver, timeout): :param driver: GPU driver, such as i915, amdgpu, nvidia :type driver: str + + :param timeout: timeout for offloaded command + :type timeout: int """ card_id = self.find_card_id(pci_name) - if card_id == PrimeOffloaderError.NO_CARD_ID: - self.logger.info("Couldn't find card id," - " please check your pci name") - return PrimeOffloaderError.NO_CARD_ID - card_name = self.find_card_name(pci_name) - if card_name is None: - self.logger.info("Couldn't find card name," - " please check your pci name") - return PrimeOffloaderError.NO_CARD_NAME # run offload command in other process dri_pci_name_format = re.sub("[:.]", "_", pci_name) - if timeout > 0: - tmp_cmd = "timeout {} {}".format(timeout, cmd) - else: - tmp_cmd = cmd + if "timeout" in cmd: + raise RuntimeError("Put timeout in command isn't allowed") - if driver in ('nvidia', 'pcieport'): - offload_cmd = "__NV_PRIME_RENDER_OFFLOAD=1" \ - " __GLX_VENDOR_LIBRARY_NAME=nvidia {}" \ - .format(tmp_cmd) + cmd = cmd.split() + if timeout: + offload_cmd = ["timeout", str(timeout)] + cmd else: - offload_cmd = "DRI_PRIME=pci-{} {}" \ - .format(dri_pci_name_format, tmp_cmd) + offload_cmd = cmd - # if nv driver under nvidia mode, prime/reverse prime couldn't work. - if self.check_nv_offload_env() \ - == PrimeOffloaderError.NOT_SUPPORT_NV_PRIME: - self.logger.info("Couldn't use nv prime offload" - " on this system environment") - offload_cmd = tmp_cmd - # Couldn't work, always return NO_ERROR - self.check_result = PrimeOffloaderError.NO_ERROR + env = os.environ.copy() + if driver in ('nvidia', 'pcieport'): + offload_env ={"__NV_PRIME_RENDER_OFFLOAD": "1", + "__GLX_VENDOR_LIBRARY_NAME": "nvidia"} else: - # use other thread to check offload is correctly or not - check_thread = threading.Thread(target=self.check_offload, - args=(cmd, card_id, card_name)) - check_thread.start() + offload_env = {"DRI_PRIME": "pci-{}".format(dri_pci_name_format)} - offload = subprocess.Popen(offload_cmd, - shell=True, - stdout=subprocess.PIPE, - universal_newlines=True) + env.update(offload_env) + self.logger.info("prime offload env: {}".format(offload_env)) - self.logger.info("offload command:[{}]".format(offload_cmd)) - - # redirect offload command output real time - while offload.poll() is None: - line = offload.stdout.readline().strip() - self.logger.info(line) - return PrimeOffloaderError.NO_ERROR + # if nv driver under nvidia mode, prime/reverse prime couldn't work. + self.check_nv_offload_env() + + # use other thread to check offload is correctly or not + check_thread = threading.Thread(target=self.check_offload, + args=(cmd, card_id, + card_name, + timeout)) + check_thread.start() + # sleep 5 seconds for waiting check_offload thread get clients before testing + time.sleep(5) + try: + with subprocess.Popen(offload_cmd, env=env, + stdout=subprocess.PIPE, + universal_newlines=True) as offload: + + self.logger.info("offload command:[{}]".format(offload_cmd)) + + # redirect offload command output real time + while offload.poll() is None: + line = offload.stdout.readline().strip() + self.logger.info(line) + check_thread.join() + if self.check_result: + raise RuntimeError("offload failed") + except subprocess.CalledProcessError as e: + self.logger.info(e) + raise RuntimeError("run offload command failed") def parse_args(self, args=sys.argv[1:]): """ command line arguments parsing + + :param args: arguments from sys + :type args: sys.argv """ parser = argparse.ArgumentParser( prog="Prime offload tester", @@ -304,31 +325,32 @@ def parse_args(self, args=sys.argv[1:]): ) return parser.parse_args(args) - def main(self) -> int: - """ - main function for command line processing - """ - - args = self.parse_args() - # create self.logger.formatter - log_formatter = logging.Formatter(fmt='%(message)s') - - # create logger - self.logger.setLevel(logging.INFO) +if __name__ == "__main__": + po = PrimeOffloader() - # create console handler - console_handler = logging.StreamHandler() - console_handler.setFormatter(log_formatter) + args = po.parse_args() - # Add console handler to logger - self.logger.addHandler(console_handler) + # create self.logger.formatter + log_formatter = logging.Formatter(fmt='%(message)s') - # run_offload_cmd("glxgears", "0000:00:02.0", "i915", 0) - self.run_offload_cmd(args.command, args.pci, args.driver, args.timeout) + # create logger + po.logger.setLevel(logging.INFO) - return self.check_result + # create console handler + console_handler = logging.StreamHandler() + console_handler.setFormatter(log_formatter) + # Add console handler to logger + po.logger.addHandler(console_handler) -if __name__ == "__main__": - sys.exit(PrimeOffloader().main()) + # run_offload_cmd("glxgears", "0000:00:02.0", "i915", 0) + try: + po.run_offload_cmd(args.command, + args.pci, + args.driver, + args.timeout) + sys.exit(0) + except RuntimeError as e: + po.logger.info(e) + sys.exit(1) diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 00191883c7..28f13d3a74 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -16,7 +16,7 @@ # along with this program. If not, see . import unittest -from unittest.mock import Mock, patch +from unittest.mock import patch, MagicMock from prime_offload_tester import * @@ -27,32 +27,62 @@ class FindCardIdTests(unittest.TestCase): (pci bus information) """ - @patch("subprocess.run") - def test_pci_name_format_check(self, mock_run): + @patch("subprocess.check_output") + def test_pci_name_format_check(self, mock_check): + po = PrimeOffloader() # correct format - mock_run.return_value = Mock(stdout="/sys/kernel/debug/dri/card0/name") - card_id = PrimeOffloader().find_card_id("0000:00:00.0") - self.assertEqual(card_id, "card0") + mock_check.return_value = "/sys/kernel/debug/dri/0/name" + self.assertEqual(po.find_card_id("0000:00:00.0"), "0") + mock_check.assert_called_with(["grep", + "-lr", + "--include=name", + "0000:00:00.0", + "/sys/kernel/debug/dri"], + universal_newlines=True) # error format - with alphabet - card_id = PrimeOffloader().find_card_id("000r:00:00.0") - self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + with self.assertRaises(RuntimeError): + po.find_card_id("000r:00:00.0") # error format - digital position error - card_id = PrimeOffloader().find_card_id("0000:00:000.0") - self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + with self.assertRaises(RuntimeError): + po.find_card_id("0000:00:000.0") - @patch("subprocess.run") - def test_id_not_found(self, mock_run): + @patch("subprocess.check_output") + def test_id_not_found(self, mock_check): + po = PrimeOffloader() # empty string - mock_run.return_value = Mock(stdout="") - card_id = PrimeOffloader().find_card_id("0000:00:00.0") - self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + mock_check.return_value = "" + with self.assertRaises(RuntimeError): + po.find_card_id("0000:00:00.0") + mock_check.assert_called_with(["grep", + "-lr", + "--include=name", + "0000:00:00.0", + "/sys/kernel/debug/dri"], + universal_newlines=True) # None - mock_run.return_value = Mock(stdout=None) - card_id = PrimeOffloader().find_card_id("0000:00:00.0") - self.assertEqual(card_id, PrimeOffloaderError.NO_CARD_ID) + mock_check.return_value = None + with self.assertRaises(RuntimeError): + po.find_card_id("0000:00:00.0") + mock_check.assert_called_with(["grep", + "-lr", + "--include=name", + "0000:00:00.0", + "/sys/kernel/debug/dri"], + universal_newlines=True) + + # subprocess error + mock_check.side_effect = subprocess.CalledProcessError(-1, "test") + with self.assertRaises(RuntimeError): + po.find_card_id("0000:00:00.0") + mock_check.assert_called_with(["grep", + "-lr", + "--include=name", + "0000:00:00.0", + "/sys/kernel/debug/dri"], + universal_newlines=True) class FindCardNameTests(unittest.TestCase): @@ -61,66 +91,137 @@ class FindCardNameTests(unittest.TestCase): (pci bus information) """ - lshw_output = '[\ - {\ - "id" : "display",\ - "class" : "display",\ - "claimed" : true,\ - "handle" : "PCI:0000:00:02.0",\ - "description" : "VGA compatible controller",\ - "product" : "TigerLake-LP GT2 [Iris Xe Graphics]",\ - "vendor" : "Intel Corporation",\ - "physid" : "2",\ - "businfo" : "pci@0000:00:02.0",\ - "logicalname" : "/dev/fb0",\ - "version" : "01",\ - "width" : 64,\ - "clock" : 33000000,\ - "configuration" : {\ - "depth" : "32",\ - "driver" : "i915",\ - "latency" : "0",\ - "mode" : "1920x1080",\ - "resolution" : "1920,1080",\ - "visual" : "truecolor",\ - "xres" : "1920",\ - "yres" : "1080"\ - },\ - "capabilities" : {\ - "pciexpress" : "PCI Express",\ - "msi" : "Message Signalled Interrupts",\ - "pm" : "Power Management",\ - "vga_controller" : true,\ - "bus_master" : "bus mastering",\ - "cap_list" : "PCI capabilities listing",\ - "rom" : "extension ROM",\ - "fb" : "framebuffer"\ - }\ - }\ - ]' - - @patch("subprocess.run") - def test_name_found_check(self, mock_run): - mock_run.return_value = Mock(stdout=self.lshw_output) - card_name = PrimeOffloader().find_card_name("0000:00:02.0") - self.assertEqual(card_name, "TigerLake-LP GT2 [Iris Xe Graphics]") - - @patch("subprocess.run") - def test_name_not_found_check(self, mock_run): + lshw_output = """ + [ + { + "id" : "display", + "class" : "display", + "claimed" : true, + "handle" : "PCI:0000:00:02.0", + "description" : "VGA compatible controller", + "product" : "TigerLake-LP GT2 [Iris Xe Graphics]", + "vendor" : "Intel Corporation", + "physid" : "2", + "businfo" : "pci@0000:00:02.0", + "logicalname" : "/dev/fb0", + "version" : "01", + "width" : 64, + "clock" : 33000000, + "configuration" : { + "depth" : "32", + "driver" : "i915", + "latency" : "0", + "mode" : "1920x1080", + "resolution" : "1920,1080", + "visual" : "truecolor", + "xres" : "1920", + "yres" : "1080" + }, + "capabilities" : { + "pciexpress" : "PCI Express", + "msi" : "Message Signalled Interrupts", + "pm" : "Power Management", + "vga_controller" : true, + "bus_master" : "bus mastering", + "cap_list" : "PCI capabilities listing", + "rom" : "extension ROM", + "fb" : "framebuffer" + } + } + ]""" + + lshw_output_err = """ + [ + { + "id" : "display", + "class" : "display", + "claimed" : true, + "handle" : "PCI:0000:00:02.0", + "description" : "VGA compatible controller", + "product" : "TigerLake-LP GT2 [Iris Xe Graphics]", + "vendor" : "Intel Corporation", + "physid" : "2", + "logicalname" : "/dev/fb0", + "version" : "01", + "width" : 64, + "clock" : 33000000, + "configuration" : { + "depth" : "32", + "driver" : "i915", + "latency" : "0", + "mode" : "1920x1080", + "resolution" : "1920,1080", + "visual" : "truecolor", + "xres" : "1920", + "yres" : "1080" + } + } + ]""" + + @patch("subprocess.check_output") + def test_name_found_check(self, mock_check): + po = PrimeOffloader() + mock_check.return_value = self.lshw_output + self.assertEqual(po.find_card_name("0000:00:02.0"), + "TigerLake-LP GT2 [Iris Xe Graphics]") + mock_check.assert_called_with(["lshw", + "-c", + "display", + "-json"], + universal_newlines=True) + + @patch("subprocess.check_output") + def test_name_not_found_check(self, mock_check): + po = PrimeOffloader() # pci_name error - mock_run.return_value = Mock(stdout=self.lshw_output) - card_name = PrimeOffloader().find_card_name("0000:00:00.0") - self.assertEqual(card_name, None) + mock_check.return_value = self.lshw_output + with self.assertRaises(RuntimeError): + po.find_card_name("0000:00:00.0") + mock_check.assert_called_with(["lshw", + "-c", + "display", + "-json"], + universal_newlines=True) + + # no businfo in lshw output + mock_check.return_value = self.lshw_output_err + with self.assertRaises(RuntimeError): + po.find_card_name("0000:00:00.0") + mock_check.assert_called_with(["lshw", + "-c", + "display", + "-json"], + universal_newlines=True) # empty string - mock_run.return_value = Mock(stdout="") - card_name = PrimeOffloader().find_card_name("0000:00:00.0") - self.assertEqual(card_name, None) + mock_check.return_value = "" + with self.assertRaises(RuntimeError): + po.find_card_name("0000:00:00.0") + mock_check.assert_called_with(["lshw", + "-c", + "display", + "-json"], + universal_newlines=True) # None - mock_run.return_value = Mock(stdout=None) - card_name = PrimeOffloader().find_card_name("0000:00:00.0") - self.assertEqual(card_name, None) + mock_check.return_value = None + with self.assertRaises(RuntimeError): + po.find_card_name("0000:00:00.0") + mock_check.assert_called_with(["lshw", + "-c", + "display", + "-json"], + universal_newlines=True) + + # subprocess error + mock_check.side_effect = subprocess.CalledProcessError(-1, "test") + with self.assertRaises(RuntimeError): + po.find_card_name("0000:00:00.0") + mock_check.assert_called_with(["lshw", + "-c", + "display", + "-json"], + universal_newlines=True) class CheckOffloadTests(unittest.TestCase): @@ -129,48 +230,95 @@ class CheckOffloadTests(unittest.TestCase): debug file system """ - @patch("subprocess.run") - def test_offload_succ_check(self, mock_run): - cmd = "echo" - mock_run.return_value = Mock(stdout=cmd) - pf = PrimeOffloader() - rv = pf.check_offload(cmd, "card_id", "card_name") - self.assertEqual(rv, None) - self.assertEqual(pf.check_result, - PrimeOffloaderError.NO_ERROR) - - @patch("subprocess.run") - def test_offload_fail_check(self, mock_run): - # cmd, card id and card name are checked and won't be None + @patch("subprocess.check_output") + def test_offload_succ_check(self, mock_check): + cmd = ["echo"] + mock_check.return_value = cmd + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 1), + None) + self.assertFalse(po.check_result) + mock_check.assert_called_with(["cat", + "/sys/kernel/debug/dri/card_id/clients" + ], + universal_newlines=True) + + # for 6.5 kernel. check the difference + mock_check.side_effect = ["Xorg", "Xorg\nXorg"] + + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + None) + self.assertFalse(po.check_result) + mock_check.assert_called_with(["cat", + "/sys/kernel/debug/dri/card_id/clients" + ], + universal_newlines=True) + + # for 6.5 kernel. check the difference + mock_check.side_effect = ["Xwayland", "Xwayland\nXwayland"] + + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + None) + self.assertFalse(po.check_result) + mock_check.assert_called_with(["cat", + "/sys/kernel/debug/dri/card_id/clients" + ], + universal_newlines=True) + + @patch("subprocess.check_output") + def test_offload_fail_check(self, mock_check): # cmd isn't showed in debug file system # empty string - cmd = "echo" - mock_run.return_value = Mock(stdout="") - pf = PrimeOffloader() - rv = pf.check_offload(cmd, "card_id", "card_name") - self.assertEqual(rv, None) - self.assertEqual(pf.check_result, - PrimeOffloaderError.OFFLOAD_FAIL) + cmd = ["echo"] + mock_check.return_value = "" + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + None) + self.assertTrue(po.check_result) + mock_check.assert_called_with( + ["cat", + "/sys/kernel/debug/dri/card_id/clients" + ], + universal_newlines=True) # None - cmd = "echo" - mock_run.return_value = Mock(stdout=None) - pf = PrimeOffloader() - rv = pf.check_offload(cmd, "card_id", "card_name") - self.assertEqual(rv, None) - self.assertEqual(pf.check_result, - PrimeOffloaderError.OFFLOAD_FAIL) + mock_check.return_value = None + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + None) + self.assertTrue(po.check_result) # OS Error # Missing file or permissions - cmd = "echo" - mock_run.side_effect = OSError - pf = PrimeOffloader() - rv = pf.check_offload(cmd, "card_id", "card_name") - self.assertEqual(rv, None) - self.assertEqual(pf.check_result, - PrimeOffloaderError.OFFLOAD_FAIL) + mock_check.side_effect = OSError + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + None) + self.assertTrue(po.check_result) + + # no match process + mock_check.side_effect = ["xxx", + "test\ndemo", + "test\ndemo", + "test\ndemo", + "test\ndemo", + "test\ndemo", + "test\ndemo", + "test\ndemo", + "test\ndemo", + "test\ndemo" + ] + po = PrimeOffloader() + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + None) + self.assertTrue(po.check_result) + mock_check.assert_called_with(["cat", + "/sys/kernel/debug/dri/card_id/clients" + ], + universal_newlines=True) class CheckNvOffloadEnvTests(unittest.TestCase): @@ -179,37 +327,112 @@ class CheckNvOffloadEnvTests(unittest.TestCase): Only on-demand mode is supported for NV driver. """ - @patch("subprocess.run") - def test_no_prime_select_check(self, mock_run): - mock_run.return_value = Mock(stdout="") - rv = PrimeOffloader().check_nv_offload_env() - self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) - - @patch("subprocess.run") - def test_on_demand_check(self, mock_run): + @patch("subprocess.check_output") + def test_no_prime_select_check(self, mock_check): + po = PrimeOffloader() + # subprocess failed + mock_check.side_effect = subprocess.CalledProcessError(-1, "which") + self.assertEqual(None, po.check_nv_offload_env()) + mock_check.assert_called_with(["which", + "prime-select" + ], + universal_newlines=True) + + mock_check.side_effect = ["xxxxx"] + self.assertEqual(None, po.check_nv_offload_env()) + mock_check.assert_called_with(["which", + "prime-select" + ], + universal_newlines=True) + + @patch("subprocess.check_output") + def test_on_demand_check(self, mock_check): + po = PrimeOffloader() # with nv driver, not on-demand mode - mock_run.return_value = Mock(stdout="prime-select") - rv = PrimeOffloader().check_nv_offload_env() - self.assertEqual(rv, PrimeOffloaderError.NOT_SUPPORT_NV_PRIME) - - @patch("subprocess.run") - def test_nvlink_check(self, mock_run): + mock_check.return_value = "prime-select" + with self.assertRaises(RuntimeError): + po.check_nv_offload_env() + mock_check.assert_called_with(["prime-select", + "query"], + universal_newlines=True) + + @patch("subprocess.check_output") + def test_nvlink_check(self, mock_check): + po = PrimeOffloader() # with nv driver, on-demand mode. This might be NVLINK environment - mock_run.return_value = Mock(stdout="prime-select on-demand") - rv = PrimeOffloader().check_nv_offload_env() - self.assertEqual(rv, PrimeOffloaderError.NOT_SUPPORT_NV_PRIME) + mock_check.return_value = "prime-select on-demand" + with self.assertRaises(RuntimeError): + po.check_nv_offload_env() + mock_check.assert_called_with(["nvidia-smi", + "nvlink", + "-s"], + universal_newlines=True) # with nv driver, on-demand mode, nv driver error - mock_run.return_value = Mock(stdout="prime-select on-demand error") - rv = PrimeOffloader().check_nv_offload_env() - self.assertEqual(rv, PrimeOffloaderError.NV_DRIVER_ERROR) + mock_check.side_effect = ["prime-select", "on-demand", "error"] + with self.assertRaises(RuntimeError): + po.check_nv_offload_env() + mock_check.assert_called_with(["nvidia-smi", + "nvlink", + "-s"], + universal_newlines=True) # with nv driver, on-demand mode, no nv driver error - mock_run.side_effect = [Mock(stdout="prime-select on-demand"), - Mock(stdout="prime-select on-demand"), - Mock(stdout="")] - rv = PrimeOffloader().check_nv_offload_env() - self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) + mock_check.side_effect = ["prime-select", + "on-demand", + ""] + self.assertEqual(None, po.check_nv_offload_env()) + mock_check.assert_called_with(["nvidia-smi", + "nvlink", + "-s"], + universal_newlines=True) + + +class CheckForKernelBugTests(unittest.TestCase): + """ + avoid kernel bug only + """ + + def test_check(self): + before = """ + command pid dev master a uid magic + systemd-logind 1331 0 y y 0 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + librewolf 4268 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + """ + correct = """ + command pid dev master a uid magic + systemd-logind 1331 0 y y 0 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + librewolf 4268 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + """ + wrong = """ + command pid dev master a uid magic + systemd-logind 1331 0 y y 0 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + librewolf 4268 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + Xwayland 4272 128 n n 1000 0 + demo 5272 128 n n 1000 0 + """ + po = PrimeOffloader() + + # same + self.assertEqual(False, po.check_for_kernel_bug(before, before)) + + # wrong + self.assertEqual(False, po.check_for_kernel_bug(before, wrong)) + + # correct + self.assertEqual(True, po.check_for_kernel_bug(before, correct)) class RunOffloadCmdTests(unittest.TestCase): @@ -219,140 +442,174 @@ class RunOffloadCmdTests(unittest.TestCase): """ def test_condition_check(self): + po = PrimeOffloader() # no card id - pf = PrimeOffloader() - pf.find_card_id = Mock(return_value=PrimeOffloaderError.NO_CARD_ID) - rv = pf.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) - self.assertEqual(rv, PrimeOffloaderError.NO_CARD_ID) + po.find_card_id = MagicMock(side_effect=RuntimeError) + with self.assertRaises(RuntimeError): + po.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) # no card name - pf.find_card_id = Mock(return_value="card0") - pf.find_card_name = Mock(return_value=None) - rv = pf.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) - self.assertEqual(rv, PrimeOffloaderError.NO_CARD_NAME) + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(side_effect=RuntimeError) + with self.assertRaises(RuntimeError): + po.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) + + # timeout in command + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="Card") + with self.assertRaises(RuntimeError): + po.run_offload_cmd("timeout 10 echo", + "0000:00:00.0", + "driver", + 0) + + # check_nv_offload_env failed + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="Card") + po.check_nv_offload_env = MagicMock(side_effect=RuntimeError) + with self.assertRaises(RuntimeError): + po.run_offload_cmd("echo", + "0000:00:00.0", + "driver", + 0) @patch("subprocess.Popen") def test_offload_cmd_check(self, mock_open): + nv_env = {'__NV_PRIME_RENDER_OFFLOAD': '1', + '__GLX_VENDOR_LIBRARY_NAME': 'nvidia'} + o_env = {'DRI_PRIME': 'pci-0000_00_00_0'} + # non NV driver - pf = PrimeOffloader() - pf.find_card_id = Mock(return_value="card0") - pf.find_card_name = Mock(return_value="Intel") - pf.check_nv_offload_env =\ - Mock(return_value=PrimeOffloaderError.NO_ERROR) - pf.check_offload = Mock(return_value="") - rv = pf.run_offload_cmd("echo", "0000:00:00.0", "xxx", 0) + po = PrimeOffloader() + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="Intel") + po.check_nv_offload_env = MagicMock(return_value=None) + po.check_offload = MagicMock(return_value="") + os.environ.copy = MagicMock(return_value={}) + po.run_offload_cmd("echo", "0000:00:00.0", "xxx", 0) # check run_offload_cmd executing correct command - mock_open.assert_called_with('DRI_PRIME=pci-0000_00_00_0 echo', - shell=True, + mock_open.assert_called_with(["echo"], + env=o_env, stdout=subprocess.PIPE, universal_newlines=True) # check check_offload function get correct args - pf.check_offload.assert_called_with('echo', 'card0', 'Intel') + po.check_offload.assert_called_with(["echo"], '0', 'Intel', 0) # non NV driver with timeout setting - pf = PrimeOffloader() - pf.find_card_id = Mock(return_value="card0") - pf.find_card_name = Mock(return_value="Intel") - pf.check_nv_offload_env =\ - Mock(return_value=PrimeOffloaderError.NO_ERROR) - pf.check_offload = Mock(return_value="") - rv = pf.run_offload_cmd("echo", "0000:00:00.0", "xxx", 1) + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="Intel") + po.check_nv_offload_env = MagicMock(return_value=None) + po.check_offload = MagicMock(return_value="") + os.environ.copy = MagicMock(return_value={}) + po.run_offload_cmd("echo", "0000:00:00.0", "xxx", 1) # check run_offload_cmd executing correct command - mock_open.assert_called_with("DRI_PRIME=pci-0000_00_00_0 " - "timeout 1 echo", - shell=True, + mock_open.assert_called_with(["timeout", "1", "echo"], + env=o_env, stdout=subprocess.PIPE, universal_newlines=True) # check check_offload function get correct args - pf.check_offload.assert_called_with('echo', 'card0', 'Intel') + po.check_offload.assert_called_with(["echo"], '0', 'Intel', 1) # NV driver - pf = PrimeOffloader() - pf.find_card_id = Mock(return_value="card0") - pf.find_card_name = Mock(return_value="NV") - pf.check_nv_offload_env =\ - Mock(return_value=PrimeOffloaderError.NO_ERROR) - pf.check_offload = Mock(return_value="") - rv = pf.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 0) + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="NV") + po.check_nv_offload_env = MagicMock(return_value=None) + po.check_offload = MagicMock(return_value="") + os.environ.copy = MagicMock(return_value={}) + po.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 1) # check run_offload_cmd executing correct command - mock_open.assert_called_with('__NV_PRIME_RENDER_OFFLOAD=1' - ' __GLX_VENDOR_LIBRARY_NAME=nvidia echo', - shell=True, + mock_open.assert_called_with(["timeout", "1", "echo"], + env=nv_env, stdout=subprocess.PIPE, universal_newlines=True) # check check_offload function get correct args - pf.check_offload.assert_called_with('echo', 'card0', 'NV') - - # Not support nvidia prime - pf = PrimeOffloader() - pf.find_card_id = Mock(return_value="card0") - pf.find_card_name = Mock(return_value="NV") - pf.check_nv_offload_env =\ - Mock(return_value=PrimeOffloaderError.NOT_SUPPORT_NV_PRIME) - pf.check_offload = Mock(return_value="") - rv = pf.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 0) + po.check_offload.assert_called_with(["echo"], '0', 'NV', 1) + + # subprocess error + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="NV") + po.check_nv_offload_env = MagicMock(return_value=None) + po.check_offload = MagicMock(return_value="") + os.environ.copy = MagicMock(return_value={}) + mock_open.side_effect = subprocess.CalledProcessError(-1, "test") + with self.assertRaises(RuntimeError): + po.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 1) # check run_offload_cmd executing correct command - mock_open.assert_called_with('echo', - shell=True, + mock_open.assert_called_with(["timeout", "1", "echo"], + env=nv_env, stdout=subprocess.PIPE, universal_newlines=True) - self.assertEqual(rv, PrimeOffloaderError.NO_ERROR) + # check check_offload function get correct args + po.check_offload.assert_called_with(["echo"], '0', 'NV', 1) + + # check offload failed + po.find_card_id = MagicMock(return_value="0") + po.find_card_name = MagicMock(return_value="NV") + po.check_nv_offload_env = MagicMock(return_value=None) + po.check_offload = MagicMock(return_value="") + os.environ.copy = MagicMock(return_value={}) + po.check_result = True + mock_open.side_effect = None + with self.assertRaises(RuntimeError): + po.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 1) + # check run_offload_cmd executing correct command + mock_open.assert_called_with(["timeout", "1", "echo"], + env=nv_env, + stdout=subprocess.PIPE, + universal_newlines=True) + # check check_offload function get correct args + po.check_offload.assert_called_with(["echo"], '0', 'NV', 1) class ParseArgsTests(unittest.TestCase): def test_success(self): - pf = PrimeOffloader() + po = PrimeOffloader() # no arguments, load default args = [] - rv = pf.parse_args(args) + rv = po.parse_args(args) self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "i915") self.assertEqual(rv.timeout, 0) - pf = PrimeOffloader() # change command args = ["-c", "glxgears -fullscreen"] - rv = pf.parse_args(args) + rv = po.parse_args(args) self.assertEqual(rv.command, "glxgears -fullscreen") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "i915") self.assertEqual(rv.timeout, 0) - pf = PrimeOffloader() # change pci args = ["-p", "0000:00:01.0"] - rv = pf.parse_args(args) + rv = po.parse_args(args) self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:01.0") self.assertEqual(rv.driver, "i915") self.assertEqual(rv.timeout, 0) - pf = PrimeOffloader() # change driver args = ["-d", "nvidia"] - rv = pf.parse_args(args) + rv = po.parse_args(args) self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "nvidia") self.assertEqual(rv.timeout, 0) - pf = PrimeOffloader() # change timeout args = ["-t", "5"] - rv = pf.parse_args(args) + rv = po.parse_args(args) self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "i915") self.assertEqual(rv.timeout, 5) - pf = PrimeOffloader() # change all args = ["-c", "glxgears -fullscreen", "-p", "0000:00:01.0", "-d", "nvidia", "-t", "5"] - rv = pf.parse_args(args) + rv = po.parse_args(args) self.assertEqual(rv.command, "glxgears -fullscreen") self.assertEqual(rv.pci, "0000:00:01.0") self.assertEqual(rv.driver, "nvidia") diff --git a/providers/base/units/graphics/jobs.pxu b/providers/base/units/graphics/jobs.pxu index a1dc95df9b..8e6cedc2ad 100644 --- a/providers/base/units/graphics/jobs.pxu +++ b/providers/base/units/graphics/jobs.pxu @@ -296,6 +296,7 @@ category_id: com.canonical.plainbox::graphics id: graphics/{index}_auto_glxgears_{product_slug} flags: also-after-suspend requires: executable.name == 'glxgears' +user: root command: # shellcheck disable=SC1091 prime_offload_tester.py -c glxgears -p {pci_device_name} -d {driver} -t 30 @@ -310,6 +311,7 @@ category_id: com.canonical.plainbox::graphics id: graphics/{index}_valid_glxgears_{product_slug} flags: also-after-suspend requires: executable.name == 'glxgears' +user: root command: # shellcheck disable=SC1091 prime_offload_tester.py -c glxgears -p {pci_device_name} -d {driver} -t 30 @@ -332,6 +334,7 @@ category_id: com.canonical.plainbox::graphics id: graphics/{index}_auto_glxgears_fullscreen_{product_slug} flags: also-after-suspend requires: executable.name == 'glxgears' +user: root command: # shellcheck disable=SC1091 prime_offload_tester.py -c "glxgears -fullscreen" -p {pci_device_name} -d {driver} -t 30 @@ -346,6 +349,7 @@ category_id: com.canonical.plainbox::graphics id: graphics/{index}_valid_glxgears_fullscreen_{product_slug} flags: also-after-suspend requires: executable.name == 'glxgears' +user: root command: # shellcheck disable=SC1091 prime_offload_tester.py -c "glxgears -fullscreen" -p {pci_device_name} -d {driver} -t 30 From 2b5cfc717870657f32c6021741793ac40718b99e Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Mon, 18 Dec 2023 13:35:41 +0800 Subject: [PATCH 07/15] Fix flake8 error --- providers/base/bin/prime_offload_tester.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index a6519fb77c..eee8220624 100644 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -256,8 +256,8 @@ def run_offload_cmd(self, cmd, pci_name, driver, timeout): env = os.environ.copy() if driver in ('nvidia', 'pcieport'): - offload_env ={"__NV_PRIME_RENDER_OFFLOAD": "1", - "__GLX_VENDOR_LIBRARY_NAME": "nvidia"} + offload_env = {"__NV_PRIME_RENDER_OFFLOAD": "1", + "__GLX_VENDOR_LIBRARY_NAME": "nvidia"} else: offload_env = {"DRI_PRIME": "pci-{}".format(dri_pci_name_format)} @@ -273,7 +273,8 @@ def run_offload_cmd(self, cmd, pci_name, driver, timeout): card_name, timeout)) check_thread.start() - # sleep 5 seconds for waiting check_offload thread get clients before testing + # sleep 5 seconds for waiting check_offload + # thread get clients before testing time.sleep(5) try: with subprocess.Popen(offload_cmd, env=env, From 8df58bf8d06e41f1bf3f19afd79b54ad4b154eb5 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Thu, 21 Dec 2023 14:53:31 +0800 Subject: [PATCH 08/15] add executable permission --- providers/base/bin/prime_offload_tester.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 providers/base/bin/prime_offload_tester.py diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py old mode 100644 new mode 100755 From 5c1fa998df2b2271c172cea625ce9c68d970ce04 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 16 Jan 2024 13:44:45 +0800 Subject: [PATCH 09/15] 1. Move changes of job and test-plan to another PR 2. Bug of 6.5 kernel is released in proposed kernel 6.5.0.16 and have tested. Therefore, removing workaround. --- providers/base/bin/prime_offload_tester.py | 109 ++++++------------ .../base/tests/test_prime_offload_tester.py | 91 +++------------ providers/base/units/graphics/jobs.pxu | 76 ------------ 3 files changed, 53 insertions(+), 223 deletions(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index eee8220624..c0bf17d7ec 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -44,18 +44,18 @@ class PrimeOffloader: logger = logging.getLogger() check_result = False - def find_card_id(self, pci_name): + def find_card_id(self, pci_name: str): """ use pci name to find card id under /sys/kernel/debug/dri :param pci_name: pci device name in NNNN:NN:NN.N format :type card_name: str - : returns : card id - : rtype : str + :returns: card id + :rtype: str """ if not re.match("[0-9]{4}:[0-9]{2}:[0-9]{2}.[0-9]", pci_name): - raise RuntimeError("pci name format error") + raise SystemExit("pci name format error") try: cmd = ["grep", @@ -67,12 +67,12 @@ def find_card_id(self, pci_name): card_path = subprocess.check_output(cmd, universal_newlines=True) return card_path.split('/')[5] - except (IndexError, AttributeError) as e: - raise RuntimeError("return value format error {}".format(e)) + except IndexError as e: + raise SystemExit("return value format error {}".format(repr(e))) except subprocess.CalledProcessError as e: - raise RuntimeError(e) + raise SystemExit(repr(e)) - def find_card_name(self, pci_name): + def find_card_name(self, pci_name: str): """ use pci name to find card name by lshw @@ -90,39 +90,14 @@ def find_card_name(self, pci_name): for info in infos: if pci_name in info['businfo']: return info['product'] - raise RuntimeError("Card name not found") + raise SystemExit("Card name not found") except (KeyError, TypeError, json.decoder.JSONDecodeError) as e: - raise RuntimeError("return value format error {}".format(e)) + raise SystemExit("return value format error {}".format(e)) except subprocess.CalledProcessError as e: - raise RuntimeError(e) - - def check_for_kernel_bug(self, b, a): - """ - for 6.5 kernel, a bug of dir debugfs should be fixed. - before that, we have to check the difference - https://warthogs.atlassian.net/browse/OEMQA-3459 - - :param b: clients before test - :type b: str - - :param a: clients after test - :type a: str - - : returns : check result - : rtype : Boolean - """ - line_b = b.strip().splitlines() - line_a = a.strip().splitlines() - - len_b = len(line_b) - len_a = len(line_a) - - if (len_b == (len_a - 1) - and any(x in line_a[len_a - 1] for x in ('Xorg', 'Xwayland'))): - return True - return False + raise SystemExit(e) - def check_offload(self, cmd, card_id, card_name, timeout): + def check_offload(self, cmd: list, card_id: str, + card_name: str, timeout: str): """ Use to check provided command is executed on specific GPU. @@ -170,13 +145,6 @@ def check_offload(self, cmd, card_id, card_name, timeout): self.logger.info(" Card ID:[{}]".format(card_id)) self.logger.info(" Device Name:[{}]".format(card_name)) return - elif self.check_for_kernel_bug(pre_test_clients, clients): - self.logger.info("correct for 6.5 kernel bug only") - self.logger.info("Checking success:") - self.logger.info(" Offload process:[{}]".format(cmd)) - self.logger.info(" Card ID:[{}]".format(card_id)) - self.logger.info(" Device Name:[{}]".format(card_name)) - return except (subprocess.CalledProcessError, OSError, TypeError) as e: # Missing file or permissions? self.logger.info(e) @@ -223,7 +191,8 @@ def check_nv_offload_env(self): raise RuntimeError("nvidia driver error") raise RuntimeError("NVLINK detected") - def run_offload_cmd(self, cmd, pci_name, driver, timeout): + def run_offload_cmd(self, cmd: str, pci_name: str, + driver: str, timeout: int): """ run offload command and check it runs on correct GPU @@ -273,9 +242,6 @@ def run_offload_cmd(self, cmd, pci_name, driver, timeout): card_name, timeout)) check_thread.start() - # sleep 5 seconds for waiting check_offload - # thread get clients before testing - time.sleep(5) try: with subprocess.Popen(offload_cmd, env=env, stdout=subprocess.PIPE, @@ -326,32 +292,33 @@ def parse_args(self, args=sys.argv[1:]): ) return parser.parse_args(args) + def main(self): + args = self.parse_args() -if __name__ == "__main__": - po = PrimeOffloader() + # create self.logger.formatter + log_formatter = logging.Formatter(fmt='%(message)s') - args = po.parse_args() + # create logger + self.logger.setLevel(logging.INFO) - # create self.logger.formatter - log_formatter = logging.Formatter(fmt='%(message)s') + # create console handler + console_handler = logging.StreamHandler() + console_handler.setFormatter(log_formatter) - # create logger - po.logger.setLevel(logging.INFO) + # Add console handler to logger + self.logger.addHandler(console_handler) - # create console handler - console_handler = logging.StreamHandler() - console_handler.setFormatter(log_formatter) + # run_offload_cmd("glxgears", "0000:00:02.0", "i915", 0) + try: + self.run_offload_cmd(args.command, + args.pci, + args.driver, + args.timeout) + sys.exit(0) + except RuntimeError as e: + self.logger.info(e) + sys.exit(1) - # Add console handler to logger - po.logger.addHandler(console_handler) - # run_offload_cmd("glxgears", "0000:00:02.0", "i915", 0) - try: - po.run_offload_cmd(args.command, - args.pci, - args.driver, - args.timeout) - sys.exit(0) - except RuntimeError as e: - po.logger.info(e) - sys.exit(1) +if __name__ == "__main__": + PrimeOffloader().main() diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 28f13d3a74..9b17420eb0 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -41,11 +41,11 @@ def test_pci_name_format_check(self, mock_check): universal_newlines=True) # error format - with alphabet - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_id("000r:00:00.0") # error format - digital position error - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_id("0000:00:000.0") @patch("subprocess.check_output") @@ -53,18 +53,7 @@ def test_id_not_found(self, mock_check): po = PrimeOffloader() # empty string mock_check.return_value = "" - with self.assertRaises(RuntimeError): - po.find_card_id("0000:00:00.0") - mock_check.assert_called_with(["grep", - "-lr", - "--include=name", - "0000:00:00.0", - "/sys/kernel/debug/dri"], - universal_newlines=True) - - # None - mock_check.return_value = None - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_id("0000:00:00.0") mock_check.assert_called_with(["grep", "-lr", @@ -75,7 +64,7 @@ def test_id_not_found(self, mock_check): # subprocess error mock_check.side_effect = subprocess.CalledProcessError(-1, "test") - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_id("0000:00:00.0") mock_check.assert_called_with(["grep", "-lr", @@ -175,7 +164,7 @@ def test_name_not_found_check(self, mock_check): po = PrimeOffloader() # pci_name error mock_check.return_value = self.lshw_output - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_name("0000:00:00.0") mock_check.assert_called_with(["lshw", "-c", @@ -185,7 +174,7 @@ def test_name_not_found_check(self, mock_check): # no businfo in lshw output mock_check.return_value = self.lshw_output_err - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_name("0000:00:00.0") mock_check.assert_called_with(["lshw", "-c", @@ -195,7 +184,7 @@ def test_name_not_found_check(self, mock_check): # empty string mock_check.return_value = "" - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_name("0000:00:00.0") mock_check.assert_called_with(["lshw", "-c", @@ -205,7 +194,7 @@ def test_name_not_found_check(self, mock_check): # None mock_check.return_value = None - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_name("0000:00:00.0") mock_check.assert_called_with(["lshw", "-c", @@ -215,7 +204,7 @@ def test_name_not_found_check(self, mock_check): # subprocess error mock_check.side_effect = subprocess.CalledProcessError(-1, "test") - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.find_card_name("0000:00:00.0") mock_check.assert_called_with(["lshw", "-c", @@ -230,8 +219,9 @@ class CheckOffloadTests(unittest.TestCase): debug file system """ + @patch('time.sleep', return_value=None) @patch("subprocess.check_output") - def test_offload_succ_check(self, mock_check): + def test_offload_succ_check(self, mock_check, mock_sleep): cmd = ["echo"] mock_check.return_value = cmd po = PrimeOffloader() @@ -243,9 +233,6 @@ def test_offload_succ_check(self, mock_check): ], universal_newlines=True) - # for 6.5 kernel. check the difference - mock_check.side_effect = ["Xorg", "Xorg\nXorg"] - po = PrimeOffloader() self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), None) @@ -255,9 +242,6 @@ def test_offload_succ_check(self, mock_check): ], universal_newlines=True) - # for 6.5 kernel. check the difference - mock_check.side_effect = ["Xwayland", "Xwayland\nXwayland"] - po = PrimeOffloader() self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), None) @@ -267,8 +251,9 @@ def test_offload_succ_check(self, mock_check): ], universal_newlines=True) + @patch('time.sleep', return_value=None) @patch("subprocess.check_output") - def test_offload_fail_check(self, mock_check): + def test_offload_fail_check(self, mock_check, mock_sleep): # cmd isn't showed in debug file system # empty string @@ -388,53 +373,6 @@ def test_nvlink_check(self, mock_check): universal_newlines=True) -class CheckForKernelBugTests(unittest.TestCase): - """ - avoid kernel bug only - """ - - def test_check(self): - before = """ - command pid dev master a uid magic - systemd-logind 1331 0 y y 0 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - librewolf 4268 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - """ - correct = """ - command pid dev master a uid magic - systemd-logind 1331 0 y y 0 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - librewolf 4268 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - """ - wrong = """ - command pid dev master a uid magic - systemd-logind 1331 0 y y 0 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - librewolf 4268 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - Xwayland 4272 128 n n 1000 0 - demo 5272 128 n n 1000 0 - """ - po = PrimeOffloader() - - # same - self.assertEqual(False, po.check_for_kernel_bug(before, before)) - - # wrong - self.assertEqual(False, po.check_for_kernel_bug(before, wrong)) - - # correct - self.assertEqual(True, po.check_for_kernel_bug(before, correct)) - - class RunOffloadCmdTests(unittest.TestCase): """ This function is the entry point to run the command with prime offload, @@ -473,8 +411,9 @@ def test_condition_check(self): "driver", 0) + @patch('time.sleep', return_value=None) @patch("subprocess.Popen") - def test_offload_cmd_check(self, mock_open): + def test_offload_cmd_check(self, mock_open, mock_sleep): nv_env = {'__NV_PRIME_RENDER_OFFLOAD': '1', '__GLX_VENDOR_LIBRARY_NAME': 'nvidia'} o_env = {'DRI_PRIME': 'pci-0000_00_00_0'} diff --git a/providers/base/units/graphics/jobs.pxu b/providers/base/units/graphics/jobs.pxu index 8e6cedc2ad..34c2294303 100644 --- a/providers/base/units/graphics/jobs.pxu +++ b/providers/base/units/graphics/jobs.pxu @@ -289,82 +289,6 @@ command: fi _summary: Check the OpenGL renderer (AMD GPU and DRI_PRIME=1, nvidia GPU and __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia) -unit: template -template-resource: graphics_card -plugin: shell -category_id: com.canonical.plainbox::graphics -id: graphics/{index}_auto_glxgears_{product_slug} -flags: also-after-suspend -requires: executable.name == 'glxgears' -user: root -command: - # shellcheck disable=SC1091 - prime_offload_tester.py -c glxgears -p {pci_device_name} -d {driver} -t 30 -estimated_duration: 30s -summary: - Auto-Test that glxgears works for {vendor} {product} - -unit: template -template-resource: graphics_card -plugin: user-interact-verify -category_id: com.canonical.plainbox::graphics -id: graphics/{index}_valid_glxgears_{product_slug} -flags: also-after-suspend -requires: executable.name == 'glxgears' -user: root -command: - # shellcheck disable=SC1091 - prime_offload_tester.py -c glxgears -p {pci_device_name} -d {driver} -t 30 -estimated_duration: 30s -summary: - Maunal-Test that glxgears works for {vendor} {product} -purpose: - This test tests the basic 3D capabilities of your {vendor} {product} video card -steps: - 1. Click "Test" to execute an OpenGL demo, and will be closed after 30s. - 2. Verify that the animation is not jerky or slow. -verification: - 1. Did the 3d animation appear? - 2. Was the animation free from slowness/jerkiness? - -unit: template -template-resource: graphics_card -plugin: shell -category_id: com.canonical.plainbox::graphics -id: graphics/{index}_auto_glxgears_fullscreen_{product_slug} -flags: also-after-suspend -requires: executable.name == 'glxgears' -user: root -command: - # shellcheck disable=SC1091 - prime_offload_tester.py -c "glxgears -fullscreen" -p {pci_device_name} -d {driver} -t 30 -estimated_duration: 30s -summary: - Auto-Test that glxgears works on fullscreen for {vendor} {product} - -unit: template -template-resource: graphics_card -plugin: user-interact-verify -category_id: com.canonical.plainbox::graphics -id: graphics/{index}_valid_glxgears_fullscreen_{product_slug} -flags: also-after-suspend -requires: executable.name == 'glxgears' -user: root -command: - # shellcheck disable=SC1091 - prime_offload_tester.py -c "glxgears -fullscreen" -p {pci_device_name} -d {driver} -t 30 -estimated_duration: 30s -summary: - Maunal-Test that glxgears works on fullscreen for {vendor} {product} -purpose: - This test tests the basic fullscreen 3D capabilities of your {vendor} {product} video card -steps: - 1. Click "Test" to execute an OpenGL demo, and will be closed after 30s. - 2. Verify that the animation is not jerky or slow. -verification: - 1. Did the 3d animation appear? - 2. Was the animation free from slowness/jerkiness? - unit: template template-resource: graphics_card plugin: user-interact-verify From dfe7cf7d5e833d66923b8f42a4dc37f72a406e02 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 16 Jan 2024 15:21:35 +0800 Subject: [PATCH 10/15] 1. Move change of jobs and test plan to another PR 2. add more unit tests --- .../base/tests/test_prime_offload_tester.py | 19 ++++ providers/base/units/graphics/test-plan.pxu | 89 +------------------ providers/base/units/monitor/test-plan.pxu | 89 ------------------- providers/base/units/suspend/suspend.pxu | 1 - .../units/client-cert-desktop-22-04.pxu | 31 +++++-- .../units/client-cert-odm-desktop-22-04.pxu | 32 +++++-- 6 files changed, 67 insertions(+), 194 deletions(-) diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 9b17420eb0..1d513144db 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -555,5 +555,24 @@ def test_success(self): self.assertEqual(rv.timeout, 5) +class MainTests(unittest.TestCase): + @patch("prime_offload_tester.PrimeOffloader.parse_args") + @patch("prime_offload_tester.PrimeOffloader.run_offload_cmd") + def test_run_offload_cmd_succ(self, mock_run_offload, mock_parse_args): + po = PrimeOffloader() + with self.assertRaises(SystemExit) as cm: + po.main() + self.assertEqual(cm.exception.code, 0) + + @patch("prime_offload_tester.PrimeOffloader.parse_args") + @patch("prime_offload_tester.PrimeOffloader.run_offload_cmd") + def test_run_offload_cmd_fail(self, mock_run_offload, mock_parse_args): + po = PrimeOffloader() + mock_run_offload.side_effect = RuntimeError + with self.assertRaises(SystemExit) as cm: + po.main() + self.assertEqual(cm.exception.code, 1) + + if __name__ == '__main__': unittest.main() diff --git a/providers/base/units/graphics/test-plan.pxu b/providers/base/units/graphics/test-plan.pxu index 1e0c325d5b..b9a4e32157 100644 --- a/providers/base/units/graphics/test-plan.pxu +++ b/providers/base/units/graphics/test-plan.pxu @@ -1,90 +1,3 @@ -id: graphics-gpu-cert-full -unit: test plan -_name: Graphics tests (GPU) -_description: - Graphics tests (GPU) -include: -bootstrap_include: - graphics_card -nested_part: - com.canonical.certification::graphics-gpu-cert-automated - com.canonical.certification::graphics-gpu-cert-manual - -id: graphics-gpu-cert-automated -unit: test plan -_name: Graphics tests (Automated) -_description: - Graphics tests (Automated) -include: - graphics/VESA_drivers_not_in_use certification-status=blocker - graphics/1_driver_version_.* certification-status=blocker - graphics/1_gl_support_.* certification-status=blocker - graphics/1_minimum_resolution_.* - graphics/2_driver_version_.* certification-status=blocker - graphics/2_minimum_resolution_.* - graphics/1_auto_glxgears_.* certification-status=blocker - graphics/2_auto_glxgears_.* certification-status=blocker - suspend/resolution_before_suspend certification-status=blocker -bootstrap_include: - graphics_card - -id: graphics-gpu-cert-manual -unit: test plan -_name: Graphics tests (Manual) -_description: - Graphics tests (Manual) -include: - miscellanea/chvt - graphics/1_maximum_resolution_.* certification-status=blocker - graphics/1_rotation_.* certification-status=blocker - graphics/1_video_.* certification-status=blocker - graphics/1_cycle_resolution_.* certification-status=non-blocker - graphics/1_valid_glxgears_.* certification-status=blocker - graphics/2_valid_glxgears_.* certification-status=blocker -bootstrap_include: - graphics_card - -id: after-suspend-graphics-gpu-cert-full -unit: test plan -_name: After suspend tests -_description: After suspend tests -include: -nested_part: - com.canonical.certification::after-suspend-graphics-gpu-cert-automated - com.canonical.certification::after-suspend-graphics-gpu-cert-manual - -id: after-suspend-graphics-gpu-cert-automated -unit: test plan -_name: After suspend tests (GPU automated) -_description: After suspend tests (GPU automated) -include: - suspend/suspend-time-check certification-status=non-blocker - suspend/suspend-single-log-attach certification-status=non-blocker - after-suspend-graphics/VESA_drivers_not_in_use certification-status=blocker - after-suspend-graphics/1_driver_version_.* certification-status=blocker - after-suspend-graphics/1_gl_support_.* certification-status=blocker - after-suspend-graphics/1_minimum_resolution_.* - after-suspend-graphics/1_auto_glxgears_.* certification-status=blocker - after-suspend-graphics/2_auto_glxgears_.* certification-status=blocker - suspend/resolution_after_suspend certification-status=blocker - -id: after-suspend-graphics-gpu-cert-manual -unit: test plan -_name: After suspend tests (GPU manual) -_description: After suspend tests (GPU manual) -include: - power-management/lid_close_suspend_open certification-status=blocker - power-management/lid certification-status=blocker - after-suspend-miscellanea/chvt - suspend/display_after_suspend certification-status=blocker - after-suspend-graphics/1_maximum_resolution_.* certification-status=blocker - after-suspend-graphics/1_rotation_.* certification-status=blocker - after-suspend-graphics/1_video_.* certification-status=blocker - after-suspend-graphics/1_cycle_resolution_.* certification-status=non-blocker - after-suspend-graphics/1_valid_glxgears_.* certification-status=blocker - after-suspend-graphics/2_valid_glxgears_.* certification-status=blocker - suspend/1_xrandr_screens_after_suspend.tar.gz_auto - id: graphics-integrated-gpu-cert-full unit: test plan _name: Graphics tests (integrated GPU) @@ -315,4 +228,4 @@ include: suspend/2_display_after_suspend_.*_graphics certification-status=blocker suspend/2_glxgears_after_suspend_.*_graphics certification-status=blocker suspend/2_rotation_after_suspend_.*_graphics certification-status=blocker - suspend/2_video_after_suspend_.*_graphics certification-status=blocker + suspend/2_video_after_suspend_.*_graphics certification-status=blocker \ No newline at end of file diff --git a/providers/base/units/monitor/test-plan.pxu b/providers/base/units/monitor/test-plan.pxu index 78fbf96730..7c7b73a8ba 100644 --- a/providers/base/units/monitor/test-plan.pxu +++ b/providers/base/units/monitor/test-plan.pxu @@ -1,92 +1,3 @@ -id: monitor-gpu-cert-full -unit: test plan -_name: Monitor tests (GPU) -_description: - Monitor tests (GPU) -include: -bootstrap_include: - graphics_card -nested_part: - com.canonical.certification::monitor-gpu-cert-manual - com.canonical.certification::monitor-gpu-cert-automated - -id: monitor-gpu-cert-automated -unit: test plan -_name: Monitor tests (GPU) (Automated) -_description: - Monitor tests (GPU) (Automated) -include: -bootstrap_include: - graphics_card - -id: monitor-gpu-cert-manual -unit: test plan -_name: Monitor tests (GPU) (Manual) -_description: - Monitor tests (GPU) (Manual) -include: - monitor/1_powersaving_.* certification-status=blocker - power-management/light_sensor - monitor/1_dim_brightness_.* certification-status=blocker - monitor/1_displayport_.* certification-status=blocker - audio/1_playback_displayport_.* certification-status=blocker - monitor/1_type-c_displayport_.* certification-status=blocker - audio/1_playback_type-c_displayport_.* certification-status=blocker - monitor/1_type-c_hdmi_.* certification-status=blocker - audio/1_playback_type-c_hdmi_.* certification-status=blocker - monitor/1_type-c_vga_.* certification-status=blocker - monitor/1_dvi_.* certification-status=blocker - monitor/1_hdmi_.* certification-status=blocker - audio/1_playback_hdmi_.* certification-status=blocker - monitor/1_thunderbolt3_.* certification-status=non-blocker - audio/1_playback_thunderbolt3_.* certification-status=non-blocker - thunderbolt3/daisy-chain certification-status=non-blocker -bootstrap_include: - graphics_card - -id: after-suspend-monitor-gpu-cert-full -unit: test plan -_name: Monitor tests (after suspend, GPU) -_description: Monitor tests (after suspend, GPU) -include: -nested_part: - after-suspend-monitor-gpu-cert-manual - after-suspend-monitor-gpu-cert-automated - -id: after-suspend-monitor-gpu-cert-automated -unit: test plan -_name: Monitor tests (after suspend, GPU) (Automated) -_description: - Monitor tests (after suspend, GPU) (Automated) -include: -bootstrap_include: - graphics_card - -id: after-suspend-monitor-gpu-cert-manual -unit: test plan -_name: Monitor tests (after suspend, GPU) (Manual) -_description: - Monitor tests (after suspend, GPU) (Manual) -include: - after-suspend-monitor/1_powersaving_.* certification-status=blocker - after-suspend-power-management/light_sensor - after-suspend-monitor/1_dim_brightness_.* certification-status=blocker - after-suspend-monitor/1_displayport_.* certification-status=blocker - after-suspend-audio/1_playback_displayport_.* certification-status=blocker - after-suspend-monitor/1_type-c_displayport_.* certification-status=blocker - after-suspend-audio/1_playback_type-c_displayport_.* certification-status=blocker - after-suspend-monitor/1_type-c_hdmi_.* certification-status=blocker - after-suspend-audio/1_playback_type-c_hdmi_.* certification-status=blocker - after-suspend-monitor/1_type-c_vga_.* certification-status=blocker - after-suspend-monitor/1_dvi_.* certification-status=blocker - after-suspend-monitor/1_hdmi_.* certification-status=blocker - after-suspend-audio/1_playback_hdmi_.* certification-status=blocker - after-suspend-monitor/1_thunderbolt3_.* certification-status=non-blocker - after-suspend-audio/1_playback_thunderbolt3_.* certification-status=non-blocker - after-suspend-thunderbolt3/daisy-chain certification-status=non-blocker -bootstrap_include: - graphics_card - id: monitor-integrated-gpu-cert-full unit: test plan _name: Monitor tests (integrated GPU) diff --git a/providers/base/units/suspend/suspend.pxu b/providers/base/units/suspend/suspend.pxu index ac93aeac34..d1c45640e0 100644 --- a/providers/base/units/suspend/suspend.pxu +++ b/providers/base/units/suspend/suspend.pxu @@ -338,7 +338,6 @@ _summary: Attaches the log from the single hybrid sleep/resume test plugin: shell category_id: com.canonical.plainbox::suspend id: suspend/suspend-time-check -depends: suspend/suspend_advanced_auto estimated_duration: 1.2 command: [ -e "$PLAINBOX_SESSION_SHARE"/suspend_single_times.log ] && sleep_time_check.py "$PLAINBOX_SESSION_SHARE"/suspend_single_times.log _summary: Ensure time to suspend/resume is under threshold 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 c2b0a56865..146cb76546 100644 --- a/providers/certification-client/units/client-cert-desktop-22-04.pxu +++ b/providers/certification-client/units/client-cert-desktop-22-04.pxu @@ -24,8 +24,10 @@ nested_part: bluetooth-cert-manual camera-cert-manual thunderbolt-cert-manual - monitor-gpu-cert-manual - graphics-gpu-cert-manual + monitor-integrated-gpu-cert-manual + graphics-integrated-gpu-cert-manual + graphics-discrete-gpu-cert-manual + monitor-discrete-gpu-cert-manual cpu-cert-manual input-cert-manual disk-cert-manual @@ -49,8 +51,14 @@ nested_part: wireless-cert-manual # start of suspend related tests # suspend point - after-suspend-graphics-gpu-cert-manual - after-suspend-monitor-gpu-cert-manual + # Test discrete card first, if present, since it's the one we will be using + # after coming back from suspend if the system has hybrid graphics. + after-suspend-graphics-discrete-gpu-cert-manual + after-suspend-monitor-discrete-gpu-cert-full + # Now we ask to switch to the integrated graphics card. This requires a + # restart of checkbox. + after-suspend-graphics-integrated-gpu-cert-manual + after-suspend-monitor-integrated-gpu-cert-full suspend-key-led-oops-check-cert after-suspend-audio-cert-full after-suspend-bluetooth-cert-manual @@ -90,8 +98,10 @@ nested_part: bluetooth-cert-automated camera-cert-automated thunderbolt-cert-automated - monitor-gpu-cert-automated - graphics-gpu-cert-automated + monitor-integrated-gpu-cert-automated + graphics-integrated-gpu-cert-automated + graphics-discrete-gpu-cert-automated + monitor-discrete-gpu-cert-automated cpu-cert-automated input-cert-automated disk-cert-automated @@ -116,9 +126,14 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full + # Test discrete card first, if present, since it's the one we will be using + # after coming back from suspend if the system has hybrid graphics. after-suspend-audio-cert-automated - after-suspend-graphics-gpu-cert-automated - after-suspend-monitor-gpu-cert-automated + after-suspend-graphics-discrete-gpu-cert-automated + # after-suspend-monitor-discrete-gpu-cert-automated # not defined + # Now we ask to switch to the integrated graphics card. + after-suspend-graphics-integrated-gpu-cert-automated + # after-suspend-monitor-integrated-gpu-cert-automated # not defined after-suspend-cpu-cert-automated after-suspend-input-cert-automated after-suspend-disk-cert-automated 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 a2590df277..af3cd4493e 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 @@ -13,8 +13,10 @@ nested_part: camera-cert-manual edac-manual thunderbolt-cert-manual - monitor-gpu-cert-manual - graphics-gpu-cert-manual + monitor-integrated-gpu-cert-manual + graphics-integrated-gpu-cert-manual + graphics-discrete-gpu-cert-manual + monitor-discrete-gpu-cert-manual input-cert-manual disk-cert-manual keys-cert-manual @@ -37,8 +39,14 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full - after-suspend-graphics-gpu-cert-manual - after-suspend-monitor-gpu-cert-manual + # Test discrete card first, if present, since it's the one we will be using + # after coming back from suspend if the system has hybrid graphics. + after-suspend-graphics-discrete-gpu-cert-manual + after-suspend-monitor-discrete-gpu-cert-full + # Now we ask to switch to the integrated graphics card. This requires a + # restart of checkbox. + after-suspend-graphics-integrated-gpu-cert-manual + after-suspend-monitor-integrated-gpu-cert-full suspend-key-led-oops-check-cert after-suspend-audio-cert-full # no-manual only after-suspend-bluetooth-cert-manual @@ -107,8 +115,10 @@ nested_part: camera-cert-automated edac-automated thunderbolt-cert-automated - monitor-gpu-cert-automated - graphics-gpu-cert-automated + monitor-integrated-gpu-cert-automated + graphics-integrated-gpu-cert-automated + graphics-discrete-gpu-cert-automated + monitor-discrete-gpu-cert-automated input-cert-automated disk-cert-manual keys-cert-automated @@ -130,8 +140,14 @@ nested_part: before-suspend-reference-cert-full # suspend point after-suspend-reference-cert-full - after-suspend-graphics-gpu-cert-automated - after-suspend-monitor-gpu-cert-automated + # Test discrete card first, if present, since it's the one we will be using + # after coming back from suspend if the system has hybrid graphics. + after-suspend-graphics-discrete-gpu-cert-automated + # after-suspend-monitor-discrete-gpu-cert-automated # not defined + # Now we ask to switch to the integrated graphics card. This requires a + # restart of checkbox. + after-suspend-graphics-integrated-gpu-cert-automated + # after-suspend-monitor-integrated-gpu-cert-automated # not defined suspend-key-led-oops-check-cert # after-suspend-audio-cert-automated # after-suspend-camera-cert-automated From c5212d65bb7ccb4346f65aad0e6e96642299e212 Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Wed, 21 Feb 2024 12:51:54 +0800 Subject: [PATCH 11/15] Fix pci BDF format check error ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/x86/pci/early.c?id=refs/tags/v3.12.7#n65 https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation --- providers/base/bin/prime_offload_tester.py | 3 ++- providers/base/tests/test_prime_offload_tester.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index c0bf17d7ec..933fa8a65b 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -54,7 +54,8 @@ def find_card_id(self, pci_name: str): :returns: card id :rtype: str """ - if not re.match("[0-9]{4}:[0-9]{2}:[0-9]{2}.[0-9]", pci_name): + pci_name_format = "[0-9]{4}:[0-9,a-f]{2}:[0-9,a-f]{2}.[0-9]" + if not re.match(pci_name_format, pci_name.lower()): raise SystemExit("pci name format error") try: diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 1d513144db..140507c9fe 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -39,6 +39,8 @@ def test_pci_name_format_check(self, mock_check): "0000:00:00.0", "/sys/kernel/debug/dri"], universal_newlines=True) + # should work with hex vaule + self.assertEqual(po.find_card_id("0000:c6:F0.0"), "0") # error format - with alphabet with self.assertRaises(SystemExit): From cb3892fa234d63b2963576c359fde8d4bac873c7 Mon Sep 17 00:00:00 2001 From: hanhsuan <32028620+hanhsuan@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:04:10 +0800 Subject: [PATCH 12/15] Update providers/base/bin/prime_offload_tester.py Co-authored-by: kissiel --- providers/base/bin/prime_offload_tester.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index 933fa8a65b..f0f59ff559 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -165,23 +165,12 @@ def check_nv_offload_env(self): # check prime-select to make sure system with nv driver. # If there is no nv driver, prime offload is fine for other drivers. try: - ps = subprocess.check_output(["which", "prime-select"], - universal_newlines=True) - - if 'prime-select' in ps: - # prime offload could running on on-demand mode only - mode = subprocess.check_output(["prime-select", "query"], - universal_newlines=True) - if "on-demand" not in mode: - raise RuntimeError("System isn't on-demand mode") - else: - self.logger.info( - "No prime-select, it should be ok to run prime offload") - return - except subprocess.CalledProcessError: + if "on-demand" not in subprocess.check_output( + ["prime-select", "query"], universal_newlines=True): + raise SystemExit("System isn't on-demand mode") + except subprocess.FileNotFoundError: self.logger.info( "No prime-select, it should be ok to run prime offload") - return # prime offload couldn't running on nvlink active or inactive # Therefore, only return empty string is supported environment. From 520fba5dd995d711208349fe4817f9a5e07fbeff Mon Sep 17 00:00:00 2001 From: hanhsuan <32028620+hanhsuan@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:04:26 +0800 Subject: [PATCH 13/15] Update providers/base/bin/prime_offload_tester.py Co-authored-by: kissiel --- providers/base/bin/prime_offload_tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index f0f59ff559..ebd4e1bbdc 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -44,7 +44,7 @@ class PrimeOffloader: logger = logging.getLogger() check_result = False - def find_card_id(self, pci_name: str): + def find_card_id(self, pci_name: str) -> str: """ use pci name to find card id under /sys/kernel/debug/dri From a57f17b8a6dae23b4160f6f4435ba474238f0eb9 Mon Sep 17 00:00:00 2001 From: hanhsuan <32028620+hanhsuan@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:04:52 +0800 Subject: [PATCH 14/15] Update providers/base/bin/prime_offload_tester.py Co-authored-by: kissiel --- providers/base/bin/prime_offload_tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index ebd4e1bbdc..df245619ca 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -73,7 +73,7 @@ def find_card_id(self, pci_name: str) -> str: except subprocess.CalledProcessError as e: raise SystemExit(repr(e)) - def find_card_name(self, pci_name: str): + def find_card_name(self, pci_name: str) -> str: """ use pci name to find card name by lshw From 2cf82cb0b3c5d34cc4b5c879e800da146be6572c Mon Sep 17 00:00:00 2001 From: hanhsuan Date: Tue, 5 Mar 2024 13:18:33 +0800 Subject: [PATCH 15/15] 1. move the get clients from check_offload to get_client 2. fix docstring error 3. change default to 20s and the logic in the check_offload 4. change RuntimeError to SystemExit --- providers/base/bin/prime_offload_tester.py | 138 +++++++------- .../base/tests/test_prime_offload_tester.py | 173 ++++++------------ 2 files changed, 119 insertions(+), 192 deletions(-) diff --git a/providers/base/bin/prime_offload_tester.py b/providers/base/bin/prime_offload_tester.py index df245619ca..0aecf2a233 100755 --- a/providers/base/bin/prime_offload_tester.py +++ b/providers/base/bin/prime_offload_tester.py @@ -34,11 +34,11 @@ class PrimeOffloader: Have to run this as root. :attr logger: console logger - :type logger: obj + :type logger: RootLogger :attr check_result: store the result of checking offloading is ok or not. - :type check_result: int + :type check_result: bool """ logger = logging.getLogger() @@ -49,10 +49,8 @@ def find_card_id(self, pci_name: str) -> str: use pci name to find card id under /sys/kernel/debug/dri :param pci_name: pci device name in NNNN:NN:NN.N format - :type card_name: str :returns: card id - :rtype: str """ pci_name_format = "[0-9]{4}:[0-9,a-f]{2}:[0-9,a-f]{2}.[0-9]" if not re.match(pci_name_format, pci_name.lower()): @@ -71,17 +69,15 @@ def find_card_id(self, pci_name: str) -> str: except IndexError as e: raise SystemExit("return value format error {}".format(repr(e))) except subprocess.CalledProcessError as e: - raise SystemExit(repr(e)) + raise SystemExit("run command failed {}".format(repr(e))) def find_card_name(self, pci_name: str) -> str: """ use pci name to find card name by lshw :param pci_name: pci device name in NNNN:NN:NN.N format - :type card_name: str - : returns : card name - : rtype : str + :returns: card name """ cmd = ["lshw", "-c", "display", "-json"] try: @@ -95,12 +91,12 @@ def find_card_name(self, pci_name: str) -> str: except (KeyError, TypeError, json.decoder.JSONDecodeError) as e: raise SystemExit("return value format error {}".format(e)) except subprocess.CalledProcessError as e: - raise SystemExit(e) + raise SystemExit("run command failed {}".format(repr(e))) - def check_offload(self, cmd: list, card_id: str, - card_name: str, timeout: str): + def get_clients(self, card_id: str) -> str: """ - Use to check provided command is executed on specific GPU. + Use to get clients that running on specific GPU + by reading debugfs. .. note:: While setting prime offload environment such as DRI_PRIME, @@ -111,48 +107,45 @@ def check_offload(self, cmd: list, card_id: str, /sys/kernel/debug/dri//clients :param cmd: command that running under prime offload - :type cmd: list + """ + read_clients_cmd = ["cat", + "/sys/kernel/debug/dri/{}/clients" + .format(card_id)] + try: + return subprocess.check_output(read_clients_cmd, + universal_newlines=True) + except subprocess.CalledProcessError: + self.logger.info("Couldn't get clients on specific GPU{}" + .format(card_id)) + + def check_offload(self, cmd: list, card_id: str, + card_name: str, timeout: str): + """ + Use to check provided command is executed on specific GPU. + + :param cmd: command that running under prime offload :param card_id: card id of dri device - :type card_id: str :param card_name: card name of dri device - :type card_name: str :param timeout: timeout for offloaded command - :type timeout: int """ - if timeout == 0: - delay = 2 - else: - delay = timeout / 10 + delay = timeout / 10 - pre_test_clients = "" + deadline = time.time() + timeout - for index in range(1, 11): + while time.time() < deadline: time.sleep(delay) - try: - read_clients_cmd = ["cat", - "/sys/kernel/debug/dri/{}/clients" - .format(card_id)] - clients = subprocess.check_output(read_clients_cmd, - universal_newlines=True) - if not pre_test_clients: - pre_test_clients = clients - continue - if cmd[0] in clients: - self.logger.info("Checking success:") - self.logger.info(" Offload process:[{}]".format(cmd)) - self.logger.info(" Card ID:[{}]".format(card_id)) - self.logger.info(" Device Name:[{}]".format(card_name)) - return - except (subprocess.CalledProcessError, OSError, TypeError) as e: - # Missing file or permissions? - self.logger.info(e) - self.check_result = True + clients = self.get_clients(card_id) + if clients and cmd[0] in clients: + self.logger.info("Checking success:") + self.logger.info(" Offload process:[{}]".format(cmd)) + self.logger.info(" Card ID:[{}]".format(card_id)) + self.logger.info(" Device Name:[{}]".format(card_name)) + return self.logger.info("Checking fail:") - self.logger.info(" Couldn't find process [{}]".format(cmd) + - " running after check {} times".format(index)) + self.logger.info(" Couldn't find process [{}]".format(cmd)) self.check_result = True def check_nv_offload_env(self): @@ -162,41 +155,37 @@ def check_nv_offload_env(self): """ # nvidia-smi ship with NVIDIA GPU display drivers on Linux # https://developer.nvidia.com/nvidia-system-management-interface - # check prime-select to make sure system with nv driver. + # check prime-select to make sure the nv driver is included. # If there is no nv driver, prime offload is fine for other drivers. try: if "on-demand" not in subprocess.check_output( ["prime-select", "query"], universal_newlines=True): raise SystemExit("System isn't on-demand mode") - except subprocess.FileNotFoundError: + + # prime offload couldn't running on nvlink active or inactive + # Therefore, only return empty string is supported environment. + nvlink = subprocess.check_output(["nvidia-smi", "nvlink", "-s"], + universal_newlines=True) + if nvlink: + if 'error' in nvlink.lower(): + raise SystemExit("nvidia driver error") + raise SystemExit("NVLINK detected") + except FileNotFoundError: self.logger.info( "No prime-select, it should be ok to run prime offload") - # prime offload couldn't running on nvlink active or inactive - # Therefore, only return empty string is supported environment. - nvlink = subprocess.check_output(["nvidia-smi", "nvlink", "-s"], - universal_newlines=True) - if nvlink: - if 'error' in nvlink.lower(): - raise RuntimeError("nvidia driver error") - raise RuntimeError("NVLINK detected") - def run_offload_cmd(self, cmd: str, pci_name: str, driver: str, timeout: int): """ run offload command and check it runs on correct GPU :param cmd: command that running under prime offload - :type cmd: str :param pci_name: pci device name in NNNN:NN:NN.N format - :type pci_name: str :param driver: GPU driver, such as i915, amdgpu, nvidia - :type driver: str :param timeout: timeout for offloaded command - :type timeout: int """ card_id = self.find_card_id(pci_name) card_name = self.find_card_name(pci_name) @@ -205,12 +194,19 @@ def run_offload_cmd(self, cmd: str, pci_name: str, dri_pci_name_format = re.sub("[:.]", "_", pci_name) if "timeout" in cmd: - raise RuntimeError("Put timeout in command isn't allowed") + raise SystemExit("Put timeout in command isn't allowed") cmd = cmd.split() - if timeout: + if timeout > 0: offload_cmd = ["timeout", str(timeout)] + cmd else: + # if timeout <=0 will make check_offload failed. + # Set the timeout to the default value + log_str = ("Timeout {}s is invalid," + " remove the timeout setting" + " and change check_offload to run 20s".format(timeout)) + self.logger.info(log_str) + timeout = 20 offload_cmd = cmd env = os.environ.copy() @@ -245,10 +241,9 @@ def run_offload_cmd(self, cmd: str, pci_name: str, self.logger.info(line) check_thread.join() if self.check_result: - raise RuntimeError("offload failed") + raise SystemExit("offload to specific GPU failed") except subprocess.CalledProcessError as e: - self.logger.info(e) - raise RuntimeError("run offload command failed") + raise SystemExit("run offload command failed {}".format(repr(e))) def parse_args(self, args=sys.argv[1:]): """ @@ -275,10 +270,8 @@ def parse_args(self, args=sys.argv[1:]): help='Type of GPU driver (default: %(default)s)' ) parser.add_argument( - "-t", "--timeout", type=int, default=0, + "-t", "--timeout", type=int, default=20, help='executing command duration in second (default: %(default)s).' - ' If provide 0, then the command will be executed' - ' without timeout.' ) return parser.parse_args(args) @@ -299,15 +292,10 @@ def main(self): self.logger.addHandler(console_handler) # run_offload_cmd("glxgears", "0000:00:02.0", "i915", 0) - try: - self.run_offload_cmd(args.command, - args.pci, - args.driver, - args.timeout) - sys.exit(0) - except RuntimeError as e: - self.logger.info(e) - sys.exit(1) + self.run_offload_cmd(args.command, + args.pci, + args.driver, + args.timeout) if __name__ == "__main__": diff --git a/providers/base/tests/test_prime_offload_tester.py b/providers/base/tests/test_prime_offload_tester.py index 140507c9fe..3cba0f007c 100755 --- a/providers/base/tests/test_prime_offload_tester.py +++ b/providers/base/tests/test_prime_offload_tester.py @@ -214,6 +214,22 @@ def test_name_not_found_check(self, mock_check): "-json"], universal_newlines=True) + @patch("subprocess.check_output") + def test_get_clients(self, mock_check): + po = PrimeOffloader() + mock_check.return_value = "echo" + self.assertEqual(po.get_clients(0), "echo") + mock_check.assert_called_with(["cat", + "/sys/kernel/debug/dri/0/clients" + ], + universal_newlines=True) + + # subprocess failed + mock_check.side_effect = subprocess.CalledProcessError(-1, "fail") + with self.assertRaises(subprocess.CalledProcessError): + po.check_nv_offload_env() + self.assertEqual(po.get_clients(0), None) + class CheckOffloadTests(unittest.TestCase): """ @@ -222,90 +238,32 @@ class CheckOffloadTests(unittest.TestCase): """ @patch('time.sleep', return_value=None) - @patch("subprocess.check_output") - def test_offload_succ_check(self, mock_check, mock_sleep): + @patch("prime_offload_tester.PrimeOffloader.get_clients") + def test_offload_succ_check(self, mock_client, mock_sleep): cmd = ["echo"] - mock_check.return_value = cmd + mock_client.return_value = cmd po = PrimeOffloader() self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 1), None) - self.assertFalse(po.check_result) - mock_check.assert_called_with(["cat", - "/sys/kernel/debug/dri/card_id/clients" - ], - universal_newlines=True) - - po = PrimeOffloader() - self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), - None) - self.assertFalse(po.check_result) - mock_check.assert_called_with(["cat", - "/sys/kernel/debug/dri/card_id/clients" - ], - universal_newlines=True) - - po = PrimeOffloader() - self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), - None) - self.assertFalse(po.check_result) - mock_check.assert_called_with(["cat", - "/sys/kernel/debug/dri/card_id/clients" - ], - universal_newlines=True) + self.assertEqual(po.check_result, False) @patch('time.sleep', return_value=None) - @patch("subprocess.check_output") - def test_offload_fail_check(self, mock_check, mock_sleep): - - # cmd isn't showed in debug file system - # empty string + @patch("prime_offload_tester.PrimeOffloader.get_clients") + def test_offload_fail_check(self, mock_client, mock_sleep): cmd = ["echo"] - mock_check.return_value = "" + # get_clients return string that doesn't include cmd + mock_client.return_value = "" po = PrimeOffloader() - self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), - None) - self.assertTrue(po.check_result) - mock_check.assert_called_with( - ["cat", - "/sys/kernel/debug/dri/card_id/clients" - ], - universal_newlines=True) - - # None - mock_check.return_value = None - po = PrimeOffloader() - self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 1), None) - self.assertTrue(po.check_result) + self.assertEqual(po.check_result, True) - # OS Error - # Missing file or permissions - mock_check.side_effect = OSError - po = PrimeOffloader() - self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), - None) - self.assertTrue(po.check_result) - - # no match process - mock_check.side_effect = ["xxx", - "test\ndemo", - "test\ndemo", - "test\ndemo", - "test\ndemo", - "test\ndemo", - "test\ndemo", - "test\ndemo", - "test\ndemo", - "test\ndemo" - ] + # get_clients return None by CalledProcessError + mock_client.return_value = None po = PrimeOffloader() - self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 0), + self.assertEqual(po.check_offload(cmd, "card_id", "card_name", 1), None) - self.assertTrue(po.check_result) - mock_check.assert_called_with(["cat", - "/sys/kernel/debug/dri/card_id/clients" - ], - universal_newlines=True) + self.assertEqual(po.check_result, True) class CheckNvOffloadEnvTests(unittest.TestCase): @@ -314,30 +272,12 @@ class CheckNvOffloadEnvTests(unittest.TestCase): Only on-demand mode is supported for NV driver. """ - @patch("subprocess.check_output") - def test_no_prime_select_check(self, mock_check): - po = PrimeOffloader() - # subprocess failed - mock_check.side_effect = subprocess.CalledProcessError(-1, "which") - self.assertEqual(None, po.check_nv_offload_env()) - mock_check.assert_called_with(["which", - "prime-select" - ], - universal_newlines=True) - - mock_check.side_effect = ["xxxxx"] - self.assertEqual(None, po.check_nv_offload_env()) - mock_check.assert_called_with(["which", - "prime-select" - ], - universal_newlines=True) - @patch("subprocess.check_output") def test_on_demand_check(self, mock_check): po = PrimeOffloader() # with nv driver, not on-demand mode mock_check.return_value = "prime-select" - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.check_nv_offload_env() mock_check.assert_called_with(["prime-select", "query"], @@ -348,7 +288,7 @@ def test_nvlink_check(self, mock_check): po = PrimeOffloader() # with nv driver, on-demand mode. This might be NVLINK environment mock_check.return_value = "prime-select on-demand" - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.check_nv_offload_env() mock_check.assert_called_with(["nvidia-smi", "nvlink", @@ -356,8 +296,8 @@ def test_nvlink_check(self, mock_check): universal_newlines=True) # with nv driver, on-demand mode, nv driver error - mock_check.side_effect = ["prime-select", "on-demand", "error"] - with self.assertRaises(RuntimeError): + mock_check.side_effect = ["on-demand", "error"] + with self.assertRaises(SystemExit): po.check_nv_offload_env() mock_check.assert_called_with(["nvidia-smi", "nvlink", @@ -365,15 +305,17 @@ def test_nvlink_check(self, mock_check): universal_newlines=True) # with nv driver, on-demand mode, no nv driver error - mock_check.side_effect = ["prime-select", - "on-demand", - ""] + mock_check.side_effect = ["on-demand", ""] self.assertEqual(None, po.check_nv_offload_env()) mock_check.assert_called_with(["nvidia-smi", "nvlink", "-s"], universal_newlines=True) + # No prime-select + mock_check.side_effect = FileNotFoundError + self.assertEqual(po.check_nv_offload_env(), None) + class RunOffloadCmdTests(unittest.TestCase): """ @@ -384,20 +326,20 @@ class RunOffloadCmdTests(unittest.TestCase): def test_condition_check(self): po = PrimeOffloader() # no card id - po.find_card_id = MagicMock(side_effect=RuntimeError) - with self.assertRaises(RuntimeError): + po.find_card_id = MagicMock(side_effect=SystemExit) + with self.assertRaises(SystemExit): po.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) # no card name po.find_card_id = MagicMock(return_value="0") - po.find_card_name = MagicMock(side_effect=RuntimeError) - with self.assertRaises(RuntimeError): + po.find_card_name = MagicMock(side_effect=SystemExit) + with self.assertRaises(SystemExit): po.run_offload_cmd("echo", "0000:00:00.0", "driver", 0) # timeout in command po.find_card_id = MagicMock(return_value="0") po.find_card_name = MagicMock(return_value="Card") - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.run_offload_cmd("timeout 10 echo", "0000:00:00.0", "driver", @@ -406,8 +348,8 @@ def test_condition_check(self): # check_nv_offload_env failed po.find_card_id = MagicMock(return_value="0") po.find_card_name = MagicMock(return_value="Card") - po.check_nv_offload_env = MagicMock(side_effect=RuntimeError) - with self.assertRaises(RuntimeError): + po.check_nv_offload_env = MagicMock(side_effect=SystemExit) + with self.assertRaises(SystemExit): po.run_offload_cmd("echo", "0000:00:00.0", "driver", @@ -434,7 +376,7 @@ def test_offload_cmd_check(self, mock_open, mock_sleep): stdout=subprocess.PIPE, universal_newlines=True) # check check_offload function get correct args - po.check_offload.assert_called_with(["echo"], '0', 'Intel', 0) + po.check_offload.assert_called_with(["echo"], '0', 'Intel', 20) # non NV driver with timeout setting po.find_card_id = MagicMock(return_value="0") @@ -473,7 +415,7 @@ def test_offload_cmd_check(self, mock_open, mock_sleep): po.check_offload = MagicMock(return_value="") os.environ.copy = MagicMock(return_value={}) mock_open.side_effect = subprocess.CalledProcessError(-1, "test") - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 1) # check run_offload_cmd executing correct command mock_open.assert_called_with(["timeout", "1", "echo"], @@ -491,7 +433,7 @@ def test_offload_cmd_check(self, mock_open, mock_sleep): os.environ.copy = MagicMock(return_value={}) po.check_result = True mock_open.side_effect = None - with self.assertRaises(RuntimeError): + with self.assertRaises(SystemExit): po.run_offload_cmd("echo", "0000:00:00.0", "nvidia", 1) # check run_offload_cmd executing correct command mock_open.assert_called_with(["timeout", "1", "echo"], @@ -511,7 +453,7 @@ def test_success(self): self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "i915") - self.assertEqual(rv.timeout, 0) + self.assertEqual(rv.timeout, 20) # change command args = ["-c", "glxgears -fullscreen"] @@ -519,7 +461,7 @@ def test_success(self): self.assertEqual(rv.command, "glxgears -fullscreen") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "i915") - self.assertEqual(rv.timeout, 0) + self.assertEqual(rv.timeout, 20) # change pci args = ["-p", "0000:00:01.0"] @@ -527,7 +469,7 @@ def test_success(self): self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:01.0") self.assertEqual(rv.driver, "i915") - self.assertEqual(rv.timeout, 0) + self.assertEqual(rv.timeout, 20) # change driver args = ["-d", "nvidia"] @@ -535,7 +477,7 @@ def test_success(self): self.assertEqual(rv.command, "glxgears") self.assertEqual(rv.pci, "0000:00:02.0") self.assertEqual(rv.driver, "nvidia") - self.assertEqual(rv.timeout, 0) + self.assertEqual(rv.timeout, 20) # change timeout args = ["-t", "5"] @@ -561,19 +503,16 @@ class MainTests(unittest.TestCase): @patch("prime_offload_tester.PrimeOffloader.parse_args") @patch("prime_offload_tester.PrimeOffloader.run_offload_cmd") def test_run_offload_cmd_succ(self, mock_run_offload, mock_parse_args): - po = PrimeOffloader() - with self.assertRaises(SystemExit) as cm: - po.main() - self.assertEqual(cm.exception.code, 0) + self.assertEqual(PrimeOffloader().main(), None) @patch("prime_offload_tester.PrimeOffloader.parse_args") @patch("prime_offload_tester.PrimeOffloader.run_offload_cmd") def test_run_offload_cmd_fail(self, mock_run_offload, mock_parse_args): po = PrimeOffloader() - mock_run_offload.side_effect = RuntimeError + mock_run_offload.side_effect = SystemExit with self.assertRaises(SystemExit) as cm: po.main() - self.assertEqual(cm.exception.code, 1) + self.assertNotEqual(cm.exception.code, 0) if __name__ == '__main__':