os_util 工具类和方法的实现
一、前置说明
- 总体目录:《从 0-1 搭建企业级 APP 自动化测试框架》
- 上节回顾:在 init_appium_and_devices 的实现思路分析 小节中,分析了实现
init_appium_and_devices
的思路,梳理出了必要的工具类和方法。 - 本节目标:完成
os_util
模块工具类和方法的编写,为具体实现做准备。 - 其它说明:
- 工具类和方法,是实现目的的手段,不是框架的重点;
- 可以使用
ChatGPT
工具辅助实现工具类和方法,验证即可; - 对工具类和方法,进行分类整理,使用途更明确。
二、代码实现
utils/os_util.py
import os
import platform
import logging
import shutil
import subprocess
import timeimport psutil
import win32gui
import win32processlogger = logging.getLogger(__name__)class RunCMDError(Exception):...class OSName:@propertydef os_name(self):return platform.system()def is_windows(self):return self.os_name.lower() == 'windows'def is_linux(self):return self.os_name.lower() == 'linux'def is_mac(self):return self.os_name.lower() == 'darwin'osname = OSName()class CMDRunner:@staticmethoddef run_command(command, timeout=5, retry_interval=0.5, expected_text=''):"""执行命令,并等待返回结果。无论是执行成功还是执行失败,都会返回结果。:param command: 待执行的命令:param timeout: 超时时间该参数的作用说明:- 在连续执行多条命令时,会遇到:因为执行速度过快,上一条命令未执行完成就执行了下一条命令,导致下一条命令执行失败的问题;- 因此需要增加失败重跑的机制:只要未执行成功,就进入失败重跑,直到到达指定的超时时间为止。:param retry_interval: 失败重试的间隔等待时间:param expected_text: 期望字符串,如果期望字符串为真,则以"字符串是否在输出结果中"来判断是否执行成功;该参数的作用举例说明:- 如果你在命令行中输入:adb connect 127.0.0.1:7555- 返回的结果是:* daemon not running; starting now at tcp:5037* daemon started successfullycannot connect to 127.0.0.1:7555: 由于目标计算机积极拒绝,无法连接。 (10061)- 虽然命令已执行成功,但是并没有达到 "连接成功" 的预期;- 此时,你可以使用 run_command('adb connect 127.0.0.1:62001', timeout=60, expected_text='already connected to 127.0.0.1:7555'):return: (output, status), 返回输出结果和状态,无论是成功还是失败,都会返回输出结果和状态"""# 定义失败重跑的截止时间end_time = time.time() + timeoutattempts = 0while True:try:# subprocess.run() 方法用于执行命令并等待其完成,然后返回一个 CompletedProcess 对象,该对象包含执行结果的属性,# 它适用于需要等待命令完成并获取结果的情况。result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)if result.returncode == 0:# 如果 returncode==0,表示执行成功output = result.stdout.strip()# 如果期望字符串为真,则通过"字符串是否在输出结果中"来判断是否执行成功status = expected_text in output if expected_text else True# 通常情况下,执行成功时,命令行不会返回任何结果,此时result为'',因此添加这个逻辑output = output or 'successful'else:# 否则,从result.stderr.strip() 获取 outputoutput = result.stderr.strip()status = expected_text in output if expected_text else False# 如果 status 为真,则返回 (output, status), 否则进入 while 循环,进行失败重试if status:logger.debug(f"Execute adb command successfully: {command}"f"\nOutput is: "f"\n=========================="f"\n{output}"f"\n==========================")return output, statusexcept subprocess.TimeoutExpired as e:logger.warning(f"Command timed out: {e}")output, status = '', Falseexcept Exception as e:logger.warning(f"Unexpected error while executing command: {e}")output, status = '', False# 添加等待时间,等待上一条命令执行完成time.sleep(retry_interval)attempts += 1logger.debug(f'Execute adb command failure: {command}'f'\nRetrying... Attempt {attempts}')# 如果超过截止时间,则跳出循环if time.time() > end_time:breaklogger.warning(f"Execute adb command failure: {command}"f"\nOutput is: "f"\n=========================="f"\n{output}"f"\n==========================")return output, statusdef run_command_strict(self, command, timeout=5, retry_interval=0.5, expected_text=''):"""严格执行命令,如果执行失败,则抛出RunCMDError异常;如果执行成功,则返回输出结果.:param command: 待执行的命令:param timeout: 超时时间:param retry_interval: 失败重试的间隔等待时间:param expected_text: 期望字符串:return: output, 返回命令执行成功之后的输出结果"""output, status = self.run_command(command, timeout, retry_interval, expected_text)if not status:raise RunCMDError(output)return outputcmd_runner = CMDRunner()class PortManager:@staticmethoddef is_port_in_use(port) -> bool:"""判断指定端口是否被占用"""try:# 获取当前系统中所有正在运行的网络连接connections = psutil.net_connections()# 检查是否有连接占用指定端口for conn in connections:if conn.laddr.port == int(port):logger.debug(f"Port {port} is in use.")return Truelogger.debug(f"Port {port} is not in use.")return Falseexcept Exception as e:# 处理异常,例如权限不足等情况logger.error(f"Error checking port {port}: {e}")return Falsedef generate_available_ports(self, start_port, count):"""生成可用的端口号:param start_port: 起始端口号:param count: 生成端口的个数:return: 列表"""available_ports = []port = start_portwhile len(available_ports) < count:if not self.is_port_in_use(port):available_ports.append(port)port += 1return available_portsport_manager = PortManager()class ProcessManager:@staticmethoddef find_pid_by_window_name(window_name):"""根据窗口名称查找对应的进程ID列表"""pids = [] # 存储找到的进程ID列表system = platform.system() # 获取操作系统类型if system == "Windows":# 使用 win32gui 库查找窗口def callback(hwnd, hwnd_list):try:if win32gui.IsWindowVisible(hwnd): # 检查窗口是否可见window_text = win32gui.GetWindowText(hwnd) # 获取窗口标题文本if window_name.lower() in window_text.lower(): # 如果窗口标题包含指定的窗口名称(不区分大小写)_, pid = win32process.GetWindowThreadProcessId(hwnd) # 获取窗口对应的进程IDhwnd_list.append(pid) # 将进程ID添加到列表中except:passreturn Truewin32gui.EnumWindows(callback, pids) # 枚举所有窗口,并调用回调函数进行查找else:# 在 macOS 和 Linux 上使用 psutil 库查找进程 PIDfor proc in psutil.process_iter(['pid', 'name']): # 遍历所有进程,并获取进程的PID和名称try:process_name = proc.info['name'].lower() # 获取进程名称(转换为小写)if window_name.lower() in process_name: # 如果进程名称包含指定的窗口名称(不区分大小写)pids.append(proc.info['pid']) # 将进程ID添加到列表中except (psutil.NoSuchProcess, psutil.AccessDenied):passreturn pids@staticmethoddef find_pid_by_port(port):"""根据端口号查找对应的进程ID列表"""pids = []for conn in psutil.net_connections():try:if conn.laddr.port == port:pids.append(conn.pid)except (psutil.NoSuchProcess, psutil.AccessDenied):passreturn pids@staticmethoddef find_pid_by_process_name(process_name):"""根据进程名称查找对应的进程ID列表"""pids = []for proc in psutil.process_iter(['pid', 'name']):try:if process_name.lower() in proc.info['name'].lower():pids.append(proc.info['pid'])except (psutil.NoSuchProcess, psutil.AccessDenied):passreturn pids@staticmethoddef kill_process_by_pid(pid):"""根据进程PID查杀进程。此方法是 kill_process_by_port、kill_process_by_name、kill_process_by_window_name 的底层方法。"""try:process = psutil.Process(pid)process.kill()logger.debug(f"Killed process with PID {process.pid}")# 杀死进程时,可能会遇到权限等问题except Exception as e:logger.debug(f"Failed to kill process: {e}")def kill_process_by_port(self, port):"""根据端口号,查杀进程"""pids = self.find_pid_by_port(int(port))for pid in pids:self.kill_process_by_pid(pid)def kill_process_by_name(self, process_name):"""根据进程名,查杀进程"""pids = self.find_pid_by_process_name(process_name)for pid in pids:self.kill_process_by_pid(pid)def kill_process_by_window_name(self, window_name):"""根据窗口名称,查杀进程"""pids = self.find_pid_by_window_name(window_name)for pid in pids:self.kill_process_by_pid(pid)process_manager = ProcessManager()class ApplicationManager:@staticmethoddef check_app_installed(app_name):"""使用 shutil.which() 方法来检查应用是否已安装,可适用于所有平台。只会从Path环境变量中判断,如果已安装但未设置为环境变量,会返回False."""if shutil.which(app_name) is not None:return Trueelse:return False@staticmethoddef set_environment_variable(key, value):"""设置系统变量,比如设置: JAVA_HOME、ANDROID_HOME"""os.environ[key] = value@staticmethoddef set_path_env_variable(path):"""设置Path环境变量warning: 这种方式设置环境变量,在windows平台会遇到1024截断的问题, todo"""if 'PATH' in os.environ:os.environ['PATH'] = path + os.pathsep + os.environ['PATH']else:os.environ['PATH'] = path@staticmethoddef start_application(executable_path, is_run_in_command=False):"""启动应用程序,但不必等待其启动完成。:param executable_path: 可执行文件的路径,比如: .exe文件:param is_run_in_command: 是否要在命令行中启动,比如:在命令行中输入 `appium` 启动 appium server"""try:if osname.is_windows():if is_run_in_command:# 如果程序需要在命令行中启动,则需要需要使用 start 启动命令行窗口command = f"start {executable_path}"else:command = executable_path# subprocess.Popen() 方法用于启动命令,但不必等待其完成,# 这对于需要启动长时间运行的程序或不需要等待程序完成的情况非常有用, 例如等待命令完成、发送信号等。subprocess.Popen(command, shell=True)logger.debug(f"Started program '{executable_path}'")else:if is_run_in_command:# 类Unix系统使用nohup命令启动程序command = f"nohup {executable_path} &"else:command = executable_pathsubprocess.Popen(command, shell=True, preexec_fn=os.setsid)logger.debug(f"Started program '{executable_path}'")except Exception as e:logger.warning(f"Error starting program '{executable_path}': {str(e)}")application_manager = ApplicationManager()if __name__ == '__main__':logging.basicConfig(level=logging.DEBUG)# 按窗口名称查杀 uiautomatorviewerprocess_manager.kill_process_by_window_name('automator')# 按端口号杀进 appiumprocess_manager.kill_process_by_port(4723)# 按进程名称查杀夜神模拟器process_manager.kill_process_by_name('nox.exe')
三、要点小结
请注意:
- 工具类和方法,是实现目的的手段,不是框架的重点内容;
- 可以使用
ChatGPT
工具辅助实现工具类和方法,验证即可; - 对工具类和方法,进行分类整理,使用途更明确。
- 以本节为例,以上所有方法都与系统操作相关,因此将模块文件命名为 os_util.py
- 再将方法进行分类:CMDRunner、PortManager、ProcessManager、ApplicationManager
点击返回主目录
相关文章:
os_util 工具类和方法的实现
一、前置说明 总体目录:《从 0-1 搭建企业级 APP 自动化测试框架》上节回顾:在 init_appium_and_devices 的实现思路分析 小节中,分析了实现 init_appium_and_devices 的思路,梳理出了必要的工具类和方法。本节目标:完…...
uview表单校验带星号
uView表单校验带星号可以通过设置required属性来实现。在uView中,可以使用组件来实现表单校验,具体步骤如下: 1、在需要校验的表单元素上添加required属性,例如: <u-form :model"detailInfo" ref"d…...

vue+element实现动态表格:根据后台返回的属性名和字段动态生成可变表格
现有一个胡萝卜厂生产不同品种的胡萝卜,为了便于客户了解产品,现需在官网展示胡萝卜信息。现有的萝卜信息:编号(id)、名称(name)、保质期(age)、特点(remark&…...

云渲染UE4像素流送搭建(winows、ubuntu单实例与多实例像素流送)
windows/ubuntu20.4下UE4.27.2像素流送 像素流送技术可以将服务器端打包的虚幻引擎应用程序在客户端的浏览器上运行,用户可以通过浏览器操作虚幻引擎应用程序,客户端无需下载虚幻引擎,本文实现两台机器通过物理介质网线实现虚幻引擎应用程序…...

Unity VR Pico apk安装失败:INSTALL_FAILED_UPDATE_INCOMPATIBLE
我的报错: PICO4企业版。安装apk,报错“安装失败。(所属的Unity项目打包的apk,被我在同一台pico4安装了20次) 调试方法: PIco4发布使用UNITY开发的Vr应用,格式为apk,安装的时候发生…...
Prompt 提示工程学习笔记
一、Prompt设计的四个关键要素: 任务描述、输入数据、上下文信息、提示风格 (1)任务描述:描述想要让LLM遵循的指令。描述应详细清晰,可进一步使用关键词突出特殊设置,从而更好地指导LLM工作。 ࿰…...

STM32实现三个小灯亮
led.c #include"led.h"void Led_Init(void) {GPIO_InitTypeDef GPIO_VALUE; //???RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//???GPIO_VALUE.GPIO_ModeGPIO_Mode_Out_PP;//???? ????GPIO_VALUE.GPIO_PinGPIO_Pin_1|GPIO_Pin_2|GPIO_P…...

1861_什么是H桥
Grey 全部学习内容汇总: GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1861_什么是H桥 H桥电路可以…...

【计算机四级(网络工程师)笔记】操作系统运行机制
目录 一、中央处理器(CPU) 1.1CPU的状态 1.2指令分类 二、寄存器 2.1寄存器分类 2.2程序状态字(PSW) 三、系统调用 3.1系统调用与一般过程调用的区别 3.2系统调用的分类 四、中断与异常 4.1中断 4.2异常 🌈嗨ÿ…...

Swagger快速入门
1、Swagger快速入门 1.1 swagger介绍 官网:https://swagger.io/ Swagger 是一个规范和完整的Web API框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。 功能主要包含以下几点: A. 使得前后端分离开发更加方便,有利于团队协作…...

数据结构之<堆>的介绍
1.简介 堆是一种特殊的数据结构,通常用于实现优先队列。堆是一个可以被看作近似完全二叉树的结构,并且具有一些特殊的性质,根据这些性质,堆被分为最大堆(或者大根堆,大顶堆)和最小堆两种。 2.…...

使用Ubuntu22+Minikube快速搭建K8S开发环境
安装Vmware 这一步,可以参考我的如下课程。 安装Ubuntu22 下载ISO镜像 这里我推荐从清华镜像源下载,速度会快非常多。 下载地址:https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04.3/ 如果你报名了我的这门视频课程…...

【中小型企业网络实战案例 二】配置网络互连互通
【中小型企业网络实战案例 一】规划、需求和基本配置-CSDN博客 热门IT技术视频教程:https://xmws-it.blog.csdn.net/article/details/134398330?spm1001.2014.3001.5502 配置接入层交换机 1.以接入交换机ACC1为例,创建ACC1的业务VLAN 10和20。 <…...

Azure Machine Learning - Azure OpenAI GPT 3.5 Turbo 微调教程
本教程将引导你在Azure平台完成对 gpt-35-turbo-0613 模型的微调。 关注TechLead,分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师&…...

运维大模型探索之 Text2PromQL 问答机器人
作者:陈昆仪(图杨) 大家下午好,我是来自阿里云可观测团队的算法工程师陈昆仪。今天分享的主题是“和我交谈并获得您想要的PromQL”。今天我跟大家分享在将AIGC技术运用到可观测领域的探索。 今天分享主要包括5个部分:…...
虚拟机VMware:变动ip修改固定ip
1、配置ip地址 vi /etc/sysconfig/network-scripts/ifcfg-ens33修改为: 修改如下:TYPE"Ethernet" # 网络类型为以太网 BOOTPROTO"static" # 手动分配ip NAME"ens33" # 网卡…...

Docker部署Nexus Maven私服并实现远程访问Nexus界面
🎬 鸽芷咕:个人主页 🔥 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 1. Docker安装Nexus2. 本地访问Nexus3. Linux安装Cpolar4. 配置Nexus界面公网地址5. 远程访问 Nexus界面6. 固定N…...

苏州科技大学计算机817程序设计(java) 学习笔记
之前备考苏州科技大学计算机(专业课:817程序设计(java))。 学习Java和算法相关内容,现将笔记及资料统一整理归纳移至这里。 部分内容不太完善,欢迎提议。 目录 考情分析 考卷题型 刷题攻略…...

虚幻学习笔记22—C++同步和异步加载
一、前言 之前提到的静态和动态加载都是同步的加载,同时其中的引用基本都是硬引用。如果资源比较大的话会出现卡顿的现象,下面将介绍一种异步加载的方式。同时,还将介绍一种区别与之前的Load的方法。 在说明同步和异步加载之前需要先讲一下虚…...

华清远见嵌入式学习——ARM——作业3
作业要求: 代码效果图: 代码: led.h #ifndef __LED_H__ #define __LED_H__#define RCC_GPIO (*(unsigned int *)0x50000a28) #define GPIOE_MODER (*(unsigned int *)0x50006000) #define GPIOF_MODER (*(unsigned int *)0x50007000) #defi…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果发送和接收都必须准备好࿰…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...