1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import annotations 6 7import functools 8import os 9from typing import Any, Dict, Optional, Tuple 10 11from crossbench import path as pth 12from crossbench.plt.base import SubprocessError 13from crossbench.plt.posix import PosixPlatform 14from crossbench.plt.remote import RemotePlatformMixin 15 16 17class LinuxPlatform(PosixPlatform): 18 SEARCH_PATHS: Tuple[pth.AnyPath, ...] = ( 19 pth.AnyPosixPath("."), 20 pth.AnyPosixPath("/usr/local/sbin"), 21 pth.AnyPosixPath("/usr/local/bin"), 22 pth.AnyPosixPath("/usr/sbin"), 23 pth.AnyPosixPath("/usr/bin"), 24 pth.AnyPosixPath("/sbin"), 25 pth.AnyPosixPath("/bin"), 26 pth.AnyPosixPath("/opt/google"), 27 ) 28 29 @property 30 def is_linux(self) -> bool: 31 return True 32 33 @property 34 def name(self) -> str: 35 return "linux" 36 37 def check_system_monitoring(self, disable: bool = False) -> bool: 38 return True 39 40 @functools.cached_property 41 def device(self) -> str: #pylint: disable=invalid-overridden-method 42 try: 43 id_dir = self.path("/sys/devices/virtual/dmi/id") 44 vendor = self.cat(id_dir / "sys_vendor").strip() 45 product = self.cat(id_dir / "product_name").strip() 46 return f"{vendor} {product}" 47 except (FileNotFoundError, SubprocessError): 48 return "UNKNOWN" 49 50 @functools.cached_property 51 def cpu(self) -> str: #pylint: disable=invalid-overridden-method 52 cpu_str = "UNKNOWN" 53 for line in self.cat(self.path("/proc/cpuinfo")).splitlines(): 54 if line.startswith("model name"): 55 _, cpu_str = line.split(":", maxsplit=2) 56 break 57 if cores_info := self._get_cpu_cores_info(): 58 cpu_str = f"{cpu_str} {cores_info}" 59 return cpu_str 60 61 @property 62 def has_display(self) -> bool: 63 return "DISPLAY" in os.environ 64 65 @property 66 def is_battery_powered(self) -> bool: 67 if self.is_local: 68 return super().is_battery_powered 69 if self.which("on_ac_power"): 70 return self.sh("on_ac_power", check=False).returncode == 1 71 return False 72 73 def system_details(self) -> Dict[str, Any]: 74 details = super().system_details() 75 for info_bin in ("lscpu", "inxi"): 76 if self.which(info_bin): 77 details[info_bin] = self.sh_stdout(info_bin) 78 return details 79 80 def search_binary(self, app_or_bin: pth.AnyPathLike) -> Optional[pth.AnyPath]: 81 app_or_bin_path: pth.AnyPath = self.path(app_or_bin) 82 if not app_or_bin_path.parts: 83 raise ValueError("Got empty path") 84 if result_path := self.which(app_or_bin_path): 85 if not self.exists(result_path): 86 raise RuntimeError(f"{result_path} does not exist.") 87 return result_path 88 for path in self.SEARCH_PATHS: 89 # Recreate Path object for easier pyfakefs testing 90 result_path = self.path(path) / app_or_bin_path 91 if self.exists(result_path): 92 return result_path 93 return None 94 95 def screenshot(self, result_path: pth.AnyPath) -> None: 96 # TODO: maybe use imagemagick's 'import' as more portable alternative 97 self.sh("gnome-screenshot", "--file", result_path) 98 99 100class RemoteLinuxPlatform(RemotePlatformMixin, LinuxPlatform): 101 pass 102