diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md deleted file mode 100755 index f09d98dde9597de75ffcdb237c2b580b8fffa3f9..0000000000000000000000000000000000000000 --- a/.gitee/ISSUE_TEMPLATE.zh-CN.md +++ /dev/null @@ -1,13 +0,0 @@ -### 该问题是怎么引起的? - - - -### 重现步骤 - - - -### 报错信息 - - - - diff --git a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md deleted file mode 100755 index 33948fdcb51264545ce5ae797f5310a1c06f871d..0000000000000000000000000000000000000000 --- a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md +++ /dev/null @@ -1,15 +0,0 @@ -### 相关的Issue - - -### 原因(目的、解决的问题等) - - -### 描述(做了什么,变更了什么) - - -### 测试用例(新增、改动、可能影响的功能) - - - - - diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..278e115bdfad6f62ebf15e676e1dc04ff99103a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +/extension/ \ No newline at end of file diff --git a/BUILD.gn b/BUILD.gn old mode 100755 new mode 100644 index 2533f502eb1a9f545d90e251d581b1ef3653680c..dc9998e58e936929ea55250d9a88e37b6c668a48 --- a/BUILD.gn +++ b/BUILD.gn @@ -1,18 +1,17 @@ - # Copyright (c) 2020 Huawei Device Co., Ltd. - # Licensed under the Apache License, Version 2.0 (the "License"); - # you may not use this file except in compliance with the License. - # You may obtain a copy of the License at - # - # http://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, - # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - # See the License for the specific language governing permissions and - # limitations under the License. -import("//test/xts/tools/build/suite_lite.gni") - -deploy_suite("xdevice"){ - suite_name = "acts" - -} +# Copyright (c) 2020 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//test/xts/tools/lite/build/suite_lite.gni") + +deploy_suite("xdevice") { + suite_name = "acts,hits,ssts" +} diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.md b/README.md new file mode 100755 index 0000000000000000000000000000000000000000..85e1de82f1b0370b0f5ccd3297832a8e6cee9863 --- /dev/null +++ b/README.md @@ -0,0 +1,227 @@ +# XDevice + +- [Introduction](#section15701932113019) +- [Directory Structure](#section1791423143211) +- [Constraints](#section118067583303) +- [Usage](#section2036431583) +- [Repositories Involved](#section260848241) + +## Introduction + +XDevice, a core module of the OpenHarmony test framework, provides services on which test case execution depends. + +XDevice consists of the following sub-modules: + +- **command**: enables command-based interactions between users and the test platform. It parses and processes user commands. +- **config**: sets test framework configurations and provides different configuration options for the serial port connection and USB connection modes. +- **driver**: functions as a test case executor, which defines main test steps, such as test case distribution, execution, and result collection. +- **report**: parses test results and generates test reports. +- **scheduler**: schedules various test case executors in the test framework. +- **environment**: configures the test framework environment, enabling device discovery and device management. +- **testkit**: provides test tools to implement JSON parsing, network file mounting, etc. +- **resource**: provides the device connection configuration file and report template definitions. +- **adapter**: adapts the test framework to open-source software. + +## Directory Structure + +``` +xdevice +├── config # XDevice configuration +│ ├── user_config.xml # XDevice environment configuration +├── resource # XDevice resources +│ ├── tools # Burning tools +├── src # Source code +│ ├── xdevice +├── extension # XDevice extension +│ ├── src # Source code of the extension +│ └── setup.py # Installation script of the extension +``` + +## Constraints + +The environment requirements for using this module are as follows: + +- Python version: 3.7.5 or later +- pySerial version: 3.3 or later +- Paramiko version: 2.7.1 or later +- RSA version: 4.0 or later + +## Usage + +- **Installing XDevice** + 1. Go to the installation directory of XDevice. + 2. Open the console window and run the following command: + + ``` + python setup.py install + ``` + + +- **Installing the extension** + 1. Go to the installation directory of the XDevice extension. + 2. Open the console and run the following command: + + ``` + python setup.py install + ``` + + +- **Modifying the user\_config.xml file** + + Configure information about your environment in the **user\_config.xml** file. + + **1. Configure the environment.** + + - For devices that support hdc connection, refer to the following note to configure the environment. + + >![](figures/icon-note.gif) **NOTE:** + >**ip/port**: IP address and port of a remote device. By default, the parameter is left blank, indicating that the local device \(IP address: 127.0.0.1; port: the one used for hdc startup\) is used as the test device. + >**sn**: SN of the test devices specified for command execution. If this parameter is set to **SN1**, only device SN1 can execute the subsequent **run** commands. In this case, other devices are set as **Ignored** and not involved in the command execution. You can run the **list devices** command and check the value of **Allocation** to view the **sn** values. You can set multiple SNs and separate each two of them with a semicolon \(;\). + + - For devices that support serial port connection, refer to the following note to configure the environment. + + >![](figures/icon-note.gif) **NOTE:** + >**type**: device connection mode. The **com** mode indicates that the device is connected through the serial port. + >**label**: device type, for example, **wifiiot** + >**serial**: serial port + >- **serial/com**: serial port for local connection, for example, **COM20** + >- **serial/type**: serial port type. The value can be **cmd** \(serial port for test case execution\) or **deploy** \(serial port for system upgrade\). + > For the open-source project, the **cmd** and **deploy** serial ports are the same, and their **com** values are the same too. + >**serial/baud\_rate, data\_bits, stop\_bits** and **timeout**: serial port parameters. You can use the default values. + + + **2. Set the test case directory.** + + **dir**: test case directory + + **3. Mount the NFS.** + + >![](figures/icon-note.gif) **NOTE:** + >**server**: NFS mounting configuration. Set the value to **NfsServer**. + >**server/ip**: IP address of the mounting environment + >**server/port**: port number of the mounting environment + >**server/username**: user name for logging in to the server + >**server/password**: password for logging in to the server + >**server/dir**: external mount path + >**server/remote**: whether the NFS server and the XDevice executor are deployed on different devices. If yes, set this parameter to **true**. Otherwise, set it to **false**. + +- **Specify the task type.** +- **Start the test framework.** +- **Execute test commands.** + + Test framework commands can be classified into three groups: **help**, **list**, and **run**. Among them, **run** commands are most commonly used in the instruction sequence. + + **help** + + Queries help information about test framework commands. + + ``` + help: + Use help to get information. + usage: + run: Display a list of supported run commands. + list: Display a list of supported devices and task records. + Examples: + help run + help list + ``` + + >![](figures/icon-note.gif) **NOTE:** + >**help run**: displays the description of **run** commands. + >**help list**: displays the description of **list** commands. + + **list** + + Displays device information and related task information. + + ``` + list: + Display device list and task records. + usage: + list + list history + list + Introduction: + list: Display the device list. + list history: Display historical records of a series of tasks. + list : Display historical records of tasks with the specified IDs. + Examples: + list + list history + list 6e****90 + ``` + + >![](figures/icon-note.gif) **NOTE:** + >**list**: displays device information. + >**list history**: displays historical task information. + >**list **: displays historical information about tasks with specified IDs. + + **run** + + Executes test tasks. + + ``` + run: + Execute the selected test cases. + The command execution process includes use case compilation, execution, and result collection. + usage: run [-l TESTLIST [TESTLIST ...] | -tf TESTFILE + [TESTFILE ...]] [-tc TESTCASE] [-c CONFIG] [-sn DEVICE_SN] + [-rp REPORT_PATH [REPORT_PATH ...]] + [-respath RESOURCE_PATH [RESOURCE_PATH ...]] + [-tcpath TESTCASES_PATH [TESTCASES_PATH ...]] + [-ta TESTARGS [TESTARGS ...]] [-pt] + [-env TEST_ENVIRONMENT [TEST_ENVIRONMENT ...]] + [-e EXECTYPE] [-t [TESTTYPE [TESTTYPE ...]]] + [-td TESTDRIVER] [-tl TESTLEVEL] [-bv BUILD_VARIANT] + [-cov COVERAGE] [--retry RETRY] [--session SESSION] + [--dryrun] [--reboot-per-module] [--check-device] + [--repeat REPEAT] + action task + Specify tests to run. + positional arguments: + action Specify the action to do. + task Specify the task name, such as ssts, acts, and hits. + ``` + + >![](figures/icon-note.gif) **NOTE:** + >The structure of a basic **run** command is as follows: + >``` + >run [task name] -l module1;moudle2 + >``` + >**task name**: task type. This parameter is optional. Generally, the value is **ssts**, **acts**, or **hits**. + >**-l**: test cases to execute. Use semicolons \(;\) to separate each two test cases. + >**module**: module to test. Generally, there is a **.json** file of the module in the **testcases** directory. + >In addition, other parameters can be attached to this command as constraints. Common parameters are as follows: + >**-sn**: specifies the devices for test case execution. If this parameter is set to **SN1**, only device SN1 executes the test cases. + >**-c**: specifies a new **user\_config.xml** file. + >**-rp**: indicates the path where the report is generated. The default directory is **xxx/xdevice/reports**. Priority of a specified directory is higher than that of the default one. + >**-tcpath**: indicates the environment directory, which is **xxx/xdevice/testcases** by default. Priority of a specified directory is higher than that of the default one. + >**-respath**: indicates the test suite directory, which is **xxx/xdevice/resource** by default. Priority of a specified directory is higher than that of the default one. + >**--reboot-per-module**: restarts the device before test case execution. + +- **View the execution result.** + + After executing the **run** commands, the test framework displays the corresponding logs on the console, and generates the execution report in the directory specified by the **-rp** parameter. If the parameter is not set, the report will be generated in the default directory. + + ``` + Structure of the report directory (the default or the specified one) + ├── result # Test case execution results of the module + │ ├── module name.xml + │ ├── ... + │ + ├── log # Running logs of devices and tasks + │ ├── device 1.log + │ ├── ... + │ ├── task.log + ├── summary_report.html # Visual report + ├── summary_report.html # Statistical report + └── ... + ``` + + +## Repositories Involved + +test\_xdevice + +test\_xdevice\_extension + diff --git a/README_zh.md b/README_zh.md new file mode 100755 index 0000000000000000000000000000000000000000..2a5b1615996a3fd5f201446eef208137a6aa2638 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,225 @@ +# xdevice组件 + +- [简介](#section15701932113019) +- [目录](#section1791423143211) +- [约束](#section118067583303) +- [使用](#section2036431583) +- [相关仓](#section260848241) + +## 简介 + +xdevice是OpenHarmony中为测试框架的核心组件,提供用例执行所依赖的相关服务。 + +xdevice主要包括以下几个主要模块: + +- command,用户与测试平台命令行交互模块,提供用户输入命令解析,命令处理。 +- config,测试框架配置模块,提供测试平台串口连接方式和USB连接方式的不同配置选项。 +- driver,测试用例执行器,提供测试用例分发,执行,结果收集等主要测试步骤定义。 +- report,测试报告模块,提供测试结果解析和测试报告生成。 +- scheduler,测试框架调度模块,提供不同类型的测试执行器调度的调度功能。 +- environment,测试框架的环境配置模块,提供设备发现,设备管理的功能。 +- testkit,测试框架工具模块,提供json解析,网络文件挂载等操作。 +- resource,测试框架资源模块,提供设备连接配置文件和报告模板定义。 +- adapter,测试框架适配开源软件的模块。 + +## 目录 + +``` +xdevice +├── config # xdevice组件配置 +│ ├── user_config.xml # xdevice环境配置 +├── resource # xdevice组件资源 +│ ├── tools # 版本烧录工具 +├── src # 组件源码目录 +│ ├── xdevice +├── extension # xdevice扩展模块 +│ ├── src # 扩展模块源码 +│ └── setup.py # xdevice扩展模块安装脚本 +``` + +## 约束 + +运行环境要求: + +- python版本\>=3.7.5 +- pyserial\>=3.3 +- paramiko\>=2.7.1 +- rsa\>=4.0 + +## 使用 + +- **安装xdevice** + 1. 打开xdevice安装目录。 + 2. 打开控制台,执行如下命令: + + ``` + python setup.py install + ``` + + +- **安装extension** + 1. 打开extension安装目录。 + 2. 打开控制台,执行如下命令: + + ``` + python setup.py install + ``` + + +- **修改user\_config.xml** + + user\_config.xml是框架提供的用户配置文件,用户可以根据自身环境信息配置相关内容,具体介绍如下: + + **1、environment环境相关配置:** + + - 设备类型一 + + >![](figures/icon-note.gif) **说明:** + >ip/port: 表示远程设备地址,默认情况为空,表示使用本地设备,ip地址为127.0.0.1,port为本机hdc启动端口号; + >​sn: 过滤执行测试设备,若设置为SN1,则表示只有设备SN1能够支持后续run命令执行,其他设备分配状态设置为Ignored,不参与命令执行,可通过list devices命令中Allocation字段来查看sn设置,可配置多个sn,中间以;隔开; + + - 设备类型二 + + >![](figures/icon-note.gif) **说明:** + >type: 设备连接方式,com表示连接方式是串口 + >label: 表示设备种类,如wifiiot + >serial: 表示一个串口定义 + >serial/com 表示本地连接的串口,如COM20 serial/type 表示串口类型,cmd是命令串口,deploy是刷机串口,社区版本cmd和deploy使用同一个串口,com值相同 + >serial/baud\_rate、data\_bits、stop\_bits、timeout: 为串口波特率等串口参数 ,一般采用默认值即可。 + + + **2、测试用例目录设置** + + dir: 指定测试用例目录。 + + **3、nfs挂载** + + >![](figures/icon-note.gif) **说明:** + >server: nfs挂载配置,label取值为NfsServer。 + >server/ip: 挂载环境IP地址。 + >server/port: 挂载环境端口。 + >server/username: 登录用户名。 + >server/password: 登录用户密码。 + >server/dir: 对应挂载的外部路径。 + >server/remote: nfs服务器与xDevice执行机不在同一台机器时,remote配置为true,否则为false。 + +- **选定任务类型** +- **启动框架** +- **执行指令** + + 框架指令可以分为三组:help、list、run。在指令序列中,以run为最常用的执行指令。 + + **1、help** + + 输入help指令可以查询框架指令帮助信息。 + + ``` + help: + use help to get information. + usage: + run: Display a list of supported run command. + list: Display a list of supported device and task record. + Examples: + help run + help list + ``` + + >![](figures/icon-note.gif) **说明:** + >help run:展示run指令相关说明 + >help list:展示 list指令相关说明 + + **2、list** + + list指令用来展示设备和相关的任务信息 + + ``` + list: + This command is used to display device list and task record. + usage: + list + list history + list + Introduction: + list: display device list + list history: display history record of a serial of tasks + list : display history record about task what contains specific id + Examples: + list + list history + list 6e****90 + ``` + + >![](figures/icon-note.gif) **说明:** + >list: 展示设备信息 + >list history: 展示任务历史信息 + >list : 展示特定id的任务其历史信息 + + **3、run** + + run指令主要用于执行测试任务 + + ``` + run: + This command is used to execute the selected testcases. + It includes a series of processes such as use case compilation, execution, and result collection. + usage: run [-l TESTLIST [TESTLIST ...] | -tf TESTFILE + [TESTFILE ...]] [-tc TESTCASE] [-c CONFIG] [-sn DEVICE_SN] + [-rp REPORT_PATH [REPORT_PATH ...]] + [-respath RESOURCE_PATH [RESOURCE_PATH ...]] + [-tcpath TESTCASES_PATH [TESTCASES_PATH ...]] + [-ta TESTARGS [TESTARGS ...]] [-pt] + [-env TEST_ENVIRONMENT [TEST_ENVIRONMENT ...]] + [-e EXECTYPE] [-t [TESTTYPE [TESTTYPE ...]]] + [-td TESTDRIVER] [-tl TESTLEVEL] [-bv BUILD_VARIANT] + [-cov COVERAGE] [--retry RETRY] [--session SESSION] + [--dryrun] [--reboot-per-module] [--check-device] + [--repeat REPEAT] + action task + Specify tests to run. + positional arguments: + action Specify action + task Specify task name,such as "ssts", "acts", "hits" + ``` + + >![](figures/icon-note.gif) **说明:** + >一个基本的run指令结构如下: + >``` + >run [task name] -l module1;moudle2 + >``` + >task name:任务类型。一般为ssts、acts、hits。非必选项 + >-l :指定执行测试用例,多个测试用例,中间用;隔开 + >module:被测试的模块。一般在testcases目录下存在对应的\\.json文件 + >此外,其他参数可以作为约束条件,附加到这个基本指令之上使用。常用的如: + >-sn: 过滤执行测试设备,若设置为SN1,则表示只有设备SN1执行用例 + >-c: 重新指定user\_config.xml。 + >-rp: 报告生成路径。默认为xxx/xdevice/reports目录。指定目录后,优先级:指定目录\>xxx/xdevice/reports目录。 + >-tcpath:环境目录,默认为xxx/xdevice/testcases目录。指定目录后,优先级:指定目录\>xxx/xdevice/testcases目录 + >-respath:测试套目录,默认为xxx/xdevice/resource目录。指定目录后,优先级:指定目录\>xxx/xdevice/resource目录 + >--reboot-per-module: 执行前先重启设备 + +- **查看执行结果** + + 框架执行run指令,控制台会输出对应的log打印,还会生成对应的执行结果报告。如果使用了-rp参数指定报告路径,那么报告就会生成在指定的路径下。否则报告会存放在默认目录。 + + ``` + 当前报告目录(默认目录/指定目录) + ├── result(模块执行结果存放目录) + │ ├── <模块名>.xml + │ ├── ... ... + │ + ├── log (设备和任务运行log存放目录) + │ ├── <设备1>.log + │ ├── ... ... + │ ├── <任务>.log + ├── summary_report.html(测试任务可视化报告) + ├── summary_report.html(测试任务数据化报告) + └── ... ... + ``` + + +## 相关仓 + +test\_xdevice + +test\_xdevice\_extension + diff --git a/adapter/LICENSE b/adapter/LICENSE new file mode 100755 index 0000000000000000000000000000000000000000..1b49cb7af0e9a66df0cb92ba59066227c2bee861 --- /dev/null +++ b/adapter/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2021 Huawei Device Co., Ltd. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*************************************************************** +Copyright (C) 2011 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/adapter/xdevice_adapter/__init__.py b/adapter/xdevice_adapter/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..d8daf3c4b297c8f25d9018f3245ab388ed07ebc1 --- /dev/null +++ b/adapter/xdevice_adapter/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# coding=utf-8 + +# +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/adapter/xdevice_adapter/constants.py b/adapter/xdevice_adapter/constants.py new file mode 100755 index 0000000000000000000000000000000000000000..3a917c6b49903093deb9e77dfa086e51a4d38e90 --- /dev/null +++ b/adapter/xdevice_adapter/constants.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# coding=utf-8 + +# +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Copyright (c) 2013 The Android Open Source Project +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from dataclasses import dataclass + +__all__ = ["UsbConst", "AppConst"] + + +@dataclass +class UsbConst: + connector = "adb" + connector_type = "usb-adb" + push = "adb push" + pull = "adb pull" + shell = "adb shell" + server_port = "ANDROID_ADB_SERVER_PORT" + kill_server = "adb kill-server" + start_server = "adb start-server" + reboot = "adb reboot" + + +@dataclass +class AppConst: + entry_app = "Entry.apk" + app_ext = ".apk" + app_name = "APK" + wifi_app = "xDeviceService-wifi.apk" diff --git a/config/acts.json b/config/acts.json old mode 100755 new mode 100644 index b774b2f8624ff49f2d7e11cbc966d27138ce8078..62e67cf7cd82ff75d87d7db9d1dc4e0e125071cd --- a/config/acts.json +++ b/config/acts.json @@ -1,16 +1,22 @@ -{ - "description": "Config for acts test suites", - "kits": [ - { - "type": "QueryKit", - "server": "NfsServer", - "mount": [ - { - "source": "resource/tools/query.bin", - "target": "/test_root/tools" - } - ], - "query" : "/test_root/tools/query.bin" - } - ] -} +{ + "description": "Config for acts test suites", + "kits": [ + { + "type": "QueryKit", + "server": "NfsServer", + "mount": [ + { + "source": "resource/tools/query.bin", + "target": "/test_root/tools" + } + ], + "query" : "/test_root/tools/query.bin" + }, + { + "type": "RootFsKit", + "command": "./bin/checksum /bin", + "hash_file_name": "checksum.hash", + "device_label": "ipcamera" + } + ] +} diff --git a/config/hits.json b/config/hits.json new file mode 100644 index 0000000000000000000000000000000000000000..243cc6fbb4fa8977dc161c370f4861a69eaf6615 --- /dev/null +++ b/config/hits.json @@ -0,0 +1,16 @@ +{ + "description": "Config for hits test suites", + "kits": [ + { + "type": "QueryKit", + "server": "NfsServer", + "mount": [ + { + "source": "resource/tools/query.bin", + "target": "/test_root/tools" + } + ], + "query" : "/test_root/tools/query.bin" + } + ] +} diff --git a/config/ssts.json b/config/ssts.json new file mode 100644 index 0000000000000000000000000000000000000000..c0e39511071055f583f0e6f34b7fa92837474b6a --- /dev/null +++ b/config/ssts.json @@ -0,0 +1,20 @@ +{ + "description": "Runs a STS plan from a pre-existing STS installation", + "kits": [ + { + "type": "QueryKit", + "server": "NfsServer", + "mount": [ + { + "source": "resource/tools/query.bin", + "target": "/test_root/tools" + } + ], + "query" : "/test_root/tools/query.bin", + "properties": { + "version": "", + "spt": "" + } + } + ] +} \ No newline at end of file diff --git a/config/user_config.xml b/config/user_config.xml index c576199e86316846dca24a446cb7aaf5dfea1d12..5439585176ed811b6236df8389b9aa7185fee04d 100755 --- a/config/user_config.xml +++ b/config/user_config.xml @@ -1,65 +1,66 @@ - - - - - - true - - - - - cmd - 115200 - 8 - 1 - 20 - - - - deploy - 115200 - - - - - - cmd - 115200 - 8 - 1 - 1 - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + cmd + 115200 + 8 + 1 + 20 + + + + deploy + 115200 + + + + + + cmd + 115200 + 8 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + INFO + diff --git a/figures/icon-caution.gif b/figures/icon-caution.gif new file mode 100755 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and b/figures/icon-caution.gif differ diff --git a/figures/icon-danger.gif b/figures/icon-danger.gif new file mode 100755 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and b/figures/icon-danger.gif differ diff --git a/figures/icon-note.gif b/figures/icon-note.gif new file mode 100755 index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda Binary files /dev/null and b/figures/icon-note.gif differ diff --git a/figures/icon-notice.gif b/figures/icon-notice.gif new file mode 100755 index 0000000000000000000000000000000000000000..86024f61b691400bea99e5b1f506d9d9aef36e27 Binary files /dev/null and b/figures/icon-notice.gif differ diff --git a/figures/icon-tip.gif b/figures/icon-tip.gif new file mode 100755 index 0000000000000000000000000000000000000000..93aa72053b510e456b149f36a0972703ea9999b7 Binary files /dev/null and b/figures/icon-tip.gif differ diff --git a/figures/icon-warning.gif b/figures/icon-warning.gif new file mode 100755 index 0000000000000000000000000000000000000000..6e90d7cfc2193e39e10bb58c38d01a23f045d571 Binary files /dev/null and b/figures/icon-warning.gif differ diff --git a/lite/BUILD.gn b/lite/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..ac478a35375a864e3eae85cb884fcd0c17bfe0df --- /dev/null +++ b/lite/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright (c) 2020 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//test/xts/tools/lite/build/suite_lite.gni") + +deploy_suite("xdevice") { + suite_name = "acts" +} diff --git a/readme.md b/readme.md deleted file mode 100755 index 7eaf179dcef27044cc16fda887e8629ae9124ddd..0000000000000000000000000000000000000000 --- a/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -详见:https://gitee.com/openharmony/docs/blob/master/readme/测试子系统README.md - -see: https://gitee.com/openharmony/docs/blob/master/docs-en/readme/testing-subsystem.md diff --git a/run.bat b/run.bat index b2c0e7ee93802428cb1f6d67f502370e60b7df93..14265e80506a599b2879b716b53c2c64e7d67f6a 100755 --- a/run.bat +++ b/run.bat @@ -1,3 +1,16 @@ +@rem Copyright (c) 2020-2021 Huawei Device Co., Ltd. +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. + @echo off set BASE_DIR=%~dp0 set PYTHON=python @@ -6,12 +19,14 @@ cd /d %BASE_DIR% (where %PYTHON% | findstr %PYTHON%) >nul 2>&1 || ( @echo "Python3.7 or higher version required!" + pause goto:eof ) python -c "import sys; exit(1) if sys.version_info.major < 3 or sys.version_info.minor < 7 else exit(0)" @if errorlevel 1 ( @echo "Python3.7 or higher version required!" + pause goto:eof ) @@ -32,4 +47,10 @@ for %%a in (%TOOLS%/*.egg) do ( @echo "Error occurs to install %%a!" ) ) +for %%a in (%TOOLS%/*.tar.gz) do ( + python -m easy_install --user %TOOLS%/%%a + @if errorlevel 1 ( + @echo "Error occurs to install %%a!" + ) +) python -m xdevice %* diff --git a/run.sh b/run.sh index 5c7d065e22b5f591e24d61754852b3c7393bbdad..ddcf9557007e6b229de85bcdb3579544887996a2 100755 --- a/run.sh +++ b/run.sh @@ -1,5 +1,18 @@ #!/usr/bin/env sh -# Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. +# +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# error() { @@ -31,4 +44,12 @@ do $PYTHON -m easy_install --user "$f" || echo "Error occurs to install $f!" done +for f in "$TOOLS"/*.tar.gz +do + if [ ! -e "$f" ]; then + error "Can not find xdevice package!" + fi + $PYTHON -m easy_install --user "$f" || echo "Error occurs to install $f!" +done + $PYTHON -m xdevice "$@" diff --git a/setup.py b/setup.py index 98cfc120d3a0710895147b9e43c40ab5709ae752..42c7d8822446f72b15a87f69bed0030e110ebc97 100755 --- a/setup.py +++ b/setup.py @@ -18,12 +18,14 @@ from setuptools import setup +INSTALL_REQUIRES = [] + def main(): setup(name='xdevice', description='xdevice test framework', url='', - package_dir={'': 'src'}, + package_dir={'': 'src', 'adapter': 'adapter'}, packages=['xdevice', 'xdevice._core', 'xdevice._core.build', @@ -33,7 +35,8 @@ def main(): 'xdevice._core.environment', 'xdevice._core.executor', 'xdevice._core.report', - 'xdevice._core.testkit' + 'xdevice._core.testkit', + 'adapter.xdevice_adapter', ], package_data={ 'xdevice._core': [ @@ -49,6 +52,7 @@ def main(): ] }, zip_safe=False, + install_requires=INSTALL_REQUIRES, ) diff --git a/src/xdevice/__init__.py b/src/xdevice/__init__.py index 2560be9ead22062aac37871913294eb1b50d0503..eff8f4ff5ed18ee4477e4d1ed5971f579af688a5 100755 --- a/src/xdevice/__init__.py +++ b/src/xdevice/__init__.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -30,6 +30,7 @@ from _core.interface import LifeCycle from _core.interface import IShellReceiver from _core.interface import ITestKit from _core.interface import IListener +from _core.interface import IReporter from _core.exception import ParamError from _core.exception import DeviceError from _core.exception import LiteDeviceError @@ -40,7 +41,9 @@ from _core.constants import DeviceLabelType from _core.constants import ManagerType from _core.constants import DeviceOsType from _core.constants import ProductForm +from _core.constants import TestType from _core.constants import CKit +from _core.constants import ConfigConst from _core.config.config_manager import UserConfigManager from _core.config.resource_manager import ResourceManager from _core.executor.listener import CaseResult @@ -57,11 +60,17 @@ from _core.utils import get_device_log_file from _core.utils import get_kit_instances from _core.utils import get_config_value from _core.utils import exec_cmd +from _core.utils import check_device_name +from _core.utils import do_module_kit_setup +from _core.utils import do_module_kit_teardown from _core.environment.manager_env import DeviceSelectionOption from _core.environment.manager_env import EnvironmentManager from _core.executor.scheduler import Scheduler from _core.report.suite_reporter import SuiteReporter from _core.report.suite_reporter import ResultCode +from _core.report.reporter_helper import ExecInfo +from _core.report.result_reporter import ResultReporter +from _core.report.__main__ import main_report from _core.command.console import Console __all__ = [ @@ -78,6 +87,7 @@ __all__ = [ "IShellReceiver", "ITestKit", "IListener", + "IReporter", "ParamError", "DeviceError", "LiteDeviceError", @@ -88,7 +98,9 @@ __all__ = [ "ManagerType", "DeviceOsType", "ProductForm", + "TestType", "CKit", + "ConfigConst", "UserConfigManager", "ResourceManager", "CaseResult", @@ -109,13 +121,20 @@ __all__ = [ "get_device_log_file", "get_kit_instances", "get_config_value", - "exec_cmd" + "exec_cmd", + "check_device_name", + "do_module_kit_setup", + "do_module_kit_teardown", + "ExecInfo", + "ResultReporter", + "main_report" ] def _load_external_plugins(): plugins = [Plugin.SCHEDULER, Plugin.DRIVER, Plugin.DEVICE, Plugin.LOG, - Plugin.PARSER, Plugin.LISTENER, Plugin.TEST_KIT, Plugin.MANAGER] + Plugin.PARSER, Plugin.LISTENER, Plugin.TEST_KIT, Plugin.MANAGER, + Plugin.REPORTER] for plugin_group in plugins: for entry_point in pkg_resources.iter_entry_points(group=plugin_group): entry_point.load() diff --git a/src/xdevice/__main__.py b/src/xdevice/__main__.py index 080dc63c1f2221e5890bf121ae9af8ffb967048e..0ea77cb130629cb1b62d7adcb81e5d8a8eb435f3 100755 --- a/src/xdevice/__main__.py +++ b/src/xdevice/__main__.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/src/xdevice/_core/__init__.py b/src/xdevice/_core/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/build/__init__.py b/src/xdevice/_core/build/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/command/__init__.py b/src/xdevice/_core/command/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/command/console.py b/src/xdevice/_core/command/console.py index ac22002f623ba2c8eeddfe1fc8b94709873073d8..9e448ba8d7e3f92ec35925774d4f7f09341dc521 100755 --- a/src/xdevice/_core/command/console.py +++ b/src/xdevice/_core/command/console.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -25,6 +25,8 @@ import threading from _core.config.config_manager import UserConfigManager from _core.constants import SchedulerType +from _core.constants import ConfigConst +from _core.constants import ModeType from _core.constants import ToolCommandType from _core.environment.manager_env import EnvironmentManager from _core.exception import ParamError @@ -36,6 +38,7 @@ from _core.plugin import Plugin from _core.plugin import get_plugin from _core.utils import SplicingAction from _core.utils import get_instance_name +from _core.report.result_reporter import ResultReporter __all__ = ["Console"] @@ -43,7 +46,7 @@ LOG = platform_logger("Console") try: if platform.system() != 'Windows': import readline -except ModuleNotFoundError: +except (ModuleNotFoundError, ImportError): LOG.warning("readline module is not exist.") @@ -111,7 +114,7 @@ class Console(object): except SystemExit: LOG.info("Program exit normally!") - return + break except ExecuteTerminate: LOG.info("execution terminated") except (IOError, EOFError, KeyboardInterrupt) as error: @@ -138,84 +141,109 @@ class Console(object): type=str, default=None, help="Specify task name") - parser.add_argument("-sn", "--device_sn", - action="store", - type=str, - dest="device_sn", - default="", - help="Specify device serial number" - ) + group.add_argument("-l", "--testlist", + action=SplicingAction, + type=str, + nargs='+', + dest=ConfigConst.testlist, + default="", + help="Specify test list" + ) group.add_argument("-tf", "--testfile", action=SplicingAction, type=str, nargs='+', - dest="testfile", + dest=ConfigConst.testfile, default="", help="Specify test list file" ) - parser.add_argument("-c", "--config", + parser.add_argument("-tc", "--testcase", action="store", type=str, - dest="config", + dest=ConfigConst.testcase, default="", - help="Specify test config file" + help="Specify test case" ) - parser.add_argument("-rp", "--reportpath", + parser.add_argument("-c", "--config", action=SplicingAction, type=str, nargs='+', - dest="reportpath", + dest=ConfigConst.configfile, default="", - help="Specify test report path" + help="Specify config file path" ) - parser.add_argument("-e", "--exectype", + parser.add_argument("-sn", "--device_sn", action="store", type=str, - dest="exectype", - default="device", - help="Specify test execute type" + dest=ConfigConst.device_sn, + default="", + help="Specify device serial number" ) - parser.add_argument("-p", "--productform", - action="store", + parser.add_argument("-rp", "--reportpath", + action=SplicingAction, type=str, - dest="productform", - default="phone", - help="Specified product form" + nargs='+', + dest=ConfigConst.report_path, + default="", + help="Specify test report path" ) - parser.add_argument("-t", "--testtype", - nargs='*', - dest="testtype", - default=["UT"], - help="Specify test type" + - "(UT,MST,ST,PERF,SEC,RELI,DST,ALL)" + parser.add_argument("-respath", "--resourcepath", + action=SplicingAction, + type=str, + nargs='+', + dest=ConfigConst.resource_path, + default="", + help="Specify test resource path" ) - parser.add_argument("-ss", "--subsystem", - action="store", + parser.add_argument("-tcpath", "--testcasespath", + action=SplicingAction, type=str, - dest="subsystem", + nargs='+', + dest=ConfigConst.testcases_path, default="", - help="Specify test subsystem" + help="Specify testcases path" ) - parser.add_argument("-tm", "--testmodule", - action="store", + parser.add_argument("-ta", "--testargs", + action=SplicingAction, + type=str, + nargs='+', + dest=ConfigConst.testargs, + default={}, + help="Specify test arguments" + ) + parser.add_argument("-pt", "--passthrough", + action="store_true", + dest=ConfigConst.pass_through, + help="Pass through test arguments" + ) + parser.add_argument("-env", "--environment", + action=SplicingAction, type=str, - dest="testmodule", + nargs='+', + dest=ConfigConst.test_environment, default="", - help="Specified test module" + help="Specify test environment" ) - parser.add_argument("-ts", "--testsuit", + parser.add_argument("-e", "--exectype", action="store", type=str, - dest="testsuit", - default="", - help="Specify test suit" + dest=ConfigConst.exectype, + default="device", + help="Specify test execute type" ) - parser.add_argument("-tc", "--testcase", + parser.add_argument("-t", "--testtype", + nargs='*', + dest=ConfigConst.testtype, + default=[], + help="Specify test type" + + "(UT,MST,ST,PERF,SEC,RELI,DST,ALL)" + ) + parser.add_argument("-td", "--testdriver", action="store", type=str, - dest="testcase", + dest=ConfigConst.testdriver, default="", - help="Specify test case" + help="Specify test driver id" ) parser.add_argument("-tl", "--testlevel", action="store", @@ -224,13 +252,6 @@ class Console(object): default="", help="Specify test level" ) - parser.add_argument("-td", "--testdriver", - action="store", - type=str, - dest="testdriver", - default="", - help="Specify test driver id" - ) parser.add_argument("-bv", "--build_variant", action="store", type=str, @@ -238,13 +259,6 @@ class Console(object): default="release", help="Specify build variant(release,debug)" ) - parser.add_argument("-b", "--build", - nargs='*', - dest="build", - default=[], - help="Specify build values" - "(version,testcase,example)" - ) parser.add_argument("-cov", "--coverage", action="store", type=str, @@ -252,58 +266,37 @@ class Console(object): default="", help="Specify coverage" ) - parser.add_argument("-respath", "--resourcepath", - action=SplicingAction, - type=str, - nargs='+', - dest="resource_path", - default="", - help="Specify test resource path" - ) - parser.add_argument("-tcpath", "--testcasespath", - action=SplicingAction, - type=str, - nargs='+', - dest="testcases_path", - default="", - help="Specify testcases path" - ) - parser.add_argument("-ta", "--testargs", - action=SplicingAction, - type=str, - nargs='+', - dest="testargs", - default={}, - help="Specify test arguments" - ) - group.add_argument("-l", "--testlist", - action=SplicingAction, - type=str, - nargs='+', - dest="testlist", - default="", - help="Specify test list" - ) - parser.add_argument("-env", "--environment", - action=SplicingAction, - type=str, - nargs='+', - dest="test_environment", - default="", - help="Specify test environment" - ) parser.add_argument("--retry", action="store", type=str, - dest="retry", + dest=ConfigConst.retry, default="", help="Specify retry command" ) - parser.add_argument("--pass_through", + parser.add_argument("--session", + action="store", + dest=ConfigConst.session, + help="retry task by session id") + parser.add_argument("--dryrun", action="store_true", - dest="pass_through", - help="Pass through test arguments" - ) + dest=ConfigConst.dry_run, + help="show retry test case list") + parser.add_argument("--reboot-per-module", + action="store_true", + dest=ConfigConst.reboot_per_module, + help="reboot devices before executing each " + "module") + parser.add_argument("--check-device", + action="store_true", + dest=ConfigConst.check_device, + help="check the test device meets the " + "requirements") + parser.add_argument("--repeat", + type=int, + default=0, + dest=ConfigConst.repeat, + help="number of times that a task is executed" + " repeatedly") self._params_pre_processing(para_list) (options, unparsed) = parser.parse_known_args(para_list) if unparsed: @@ -333,18 +326,24 @@ class Console(object): def _params_post_processing(self, options): # params post-processing if options.task == Task.EMPTY_TASK: - setattr(options, "task", "") - if options.testargs and not options.pass_through: - test_args = self._parse_combination_param(options.testargs) - setattr(options, "testargs", test_args) + setattr(options, ConfigConst.task, "") + if options.testargs: + if not options.pass_through: + test_args = self._parse_combination_param(options.testargs) + setattr(options, ConfigConst.testargs, test_args) + else: + setattr(options, ConfigConst.testargs, { + ConfigConst.pass_through: options.testargs}) if not options.resource_path: - resource_path = UserConfigManager(env=options.test_environment).\ + resource_path = UserConfigManager( + config_file=options.config, env=options.test_environment).\ get_resource_path() - setattr(options, "resource_path", resource_path) + setattr(options, ConfigConst.resource_path, resource_path) if not options.testcases_path: - testcases_path = UserConfigManager(env=options.test_environment). \ + testcases_path = UserConfigManager( + config_file=options.config, env=options.test_environment).\ get_testcases_dir() - setattr(options, "testcases_path", testcases_path) + setattr(options, ConfigConst.testcases_path, testcases_path) def command_parser(self, args): try: @@ -359,6 +358,11 @@ class Console(object): if options.action == ToolCommandType.toolcmd_key_run and \ options.retry: options = self._get_retry_options(options) + if options.dry_run: + history_report_path = getattr(options, + "history_report_path", "") + self._list_retry_case(history_report_path) + return else: from xdevice import SuiteReporter SuiteReporter.clear_failed_case_list() @@ -372,8 +376,9 @@ class Console(object): self._process_command(command, options, para_list, parser) except (ParamError, ValueError, TypeError, SyntaxError, AttributeError) as exception: + error_no = getattr(exception, "error_no", "00000") LOG.exception("%s: %s" % (get_instance_name(exception), exception), - exc_info=False) + exc_info=False, error_no=error_no) if Scheduler.upload_address: Scheduler.upload_unavailable_result(str(exception.args)) Scheduler.upload_report_end() @@ -393,65 +398,71 @@ class Console(object): elif command.startswith(ToolCommandType.toolcmd_key_list): self._process_command_list(command, para_list) else: - LOG.error("unsupported command action: %s" % command) + LOG.error("unsupported command action", error_no="00100", + action=command) def _get_retry_options(self, options): - input_report_path = options.reportpath - - # get history_command, history_report_path - if options.retry == "retry_previous_command": - if len(Scheduler.command_queue) < 2: - raise ParamError("no previous command executed") - _, history_command, history_report_path = \ - Scheduler.command_queue[-2] - else: - history_command, history_report_path = "", "" - for command_tuple in Scheduler.command_queue[:-1]: - if command_tuple[0] != options.retry: - continue - history_command, history_report_path = \ - command_tuple[1], command_tuple[2] - break - if not history_command: - raise ParamError("wrong task id input: %s" % options.retry) - + # get history command, history report path + history_command, history_report_path = self._parse_retry_option( + options) + input_report_path = options.report_path LOG.info("History command: %s", history_command) - # parse history_command, set history_report_path if not os.path.exists(history_report_path) and \ - Scheduler.mode != "decc": + Scheduler.mode != ModeType.decc: raise ParamError( "history report path %s not exists" % history_report_path) - (options, _, _, _) = self.argument_parser( - history_command.split()) + + # parse history command, set history report path + is_dry_run = True if options.dry_run else False + + # clear the content about repeat count in history command + if "--repeat" in history_command: + split_list = list(history_command.split()) + if "--repeat" in split_list: + pos = split_list.index("--repeat") + split_list = split_list[:pos] + split_list[pos+2:] + history_command = " ".join(split_list) + + (options, _, _, _) = self.argument_parser(history_command.split()) + options.dry_run = is_dry_run setattr(options, "history_report_path", history_report_path) # modify history_command -rp param - if options.reportpath: + history_command = self._parse_rp_option( + history_command, input_report_path, options) + + # add history command to Scheduler.command_queue + LOG.info("Retry command: %s", history_command) + Scheduler.command_queue[-1] = history_command + return options + + @classmethod + def _parse_rp_option(cls, history_command, input_report_path, + options): + if options.report_path: if input_report_path: history_command = history_command.replace( - options.reportpath, input_report_path) - setattr(options, "reportpath", input_report_path) + options.report_path, input_report_path) + setattr(options, "report_path", input_report_path) else: history_command = history_command.replace( - options.reportpath, "").replace("-rp", "").replace( + options.report_path, "").replace("-rp", "").replace( "--reportpath", "") - setattr(options, "reportpath", "") + setattr(options, "report_path", "") else: if input_report_path: history_command = "{}{}".format(history_command, " -rp %s" % input_report_path) - setattr(options, "reportpath", input_report_path) - history_command = history_command.strip() - - # add history command to Scheduler.command_queue - LOG.info("Retry command: %s", history_command) - Scheduler.command_queue[-1] = history_command - return options + setattr(options, "report_path", input_report_path) + return history_command.strip() @classmethod def _process_command_help(cls, parser, para_list): if para_list[0] == ToolCommandType.toolcmd_key_help: - parser.print_help() + if len(para_list) == 2: + cls.display_help_command_info(para_list[1]) + else: + parser.print_help() else: LOG.error("Wrong help command. Use 'help' to print help") return @@ -545,3 +556,256 @@ class Console(object): print("{0:<16}{1:<100}".format("TaskId:", task_id)) print("{0:<16}{1:<100}".format("Command:", command)) print("{0:<16}{1:<100}".format("ReportPath:", report_path)) + + @classmethod + def _list_retry_case(cls, history_path): + params = ResultReporter.get_task_info_params(history_path) + if not params: + raise ParamError("no retry case exists") + session_id, command, report_path, failed_list = \ + params[0], params[1], params[2], \ + [(module, failed) for module, case_list in params[3].items() + for failed in case_list] + if Scheduler.mode == ModeType.decc: + from xdevice import SuiteReporter + SuiteReporter.failed_case_list = failed_list + return + + # draw tables in console + left, middle, right = 23, 49, 49 + two_segments = "{0:-<%s}{1:-<%s}+" % (left, middle + right) + two_rows = "|{0:^%s}|{1:^%s}|" % (left - 1, middle + right - 1) + + three_segments = "{0:-<%s}{1:-<%s}{2:-<%s}+" % (left, middle, right) + three_rows = "|{0:^%s}|{1:^%s}|{2:^%s}|" % \ + (left - 1, middle - 1, right - 1) + if len(session_id) > middle + right - 1: + session_id = "%s..." % session_id[:middle + right - 4] + if len(command) > middle + right - 1: + command = "%s..." % command[:middle + right - 4] + if len(report_path) > middle + right - 1: + report_path = "%s..." % report_path[:middle + right - 4] + + print(two_segments.format("+", '+')) + print(two_rows.format("SessionId", session_id)) + print(two_rows.format("Command", command)) + print(two_rows.format("ReportPath", report_path)) + + print(three_segments.format("+", '+', '+')) + print(three_rows.format("Module", "Testsuite", "Testcase")) + print(three_segments.format("+", '+', '+')) + for module, failed in failed_list: + # all module is failed + if "#" not in failed: + class_name = "-" + test = "-" + # others, get failed cases info + else: + pos = failed.rfind("#") + class_name = failed[:pos] + test = failed[pos + 1:] + if len(module) > left - 1: + module = "%s..." % module[:left - 4] + if len(class_name) > middle - 1: + class_name = "%s..." % class_name[:middle - 4] + if len(test) > right - 1: + test = "%s..." % test[:right - 4] + print(three_rows.format(module, class_name, test)) + print(three_segments.format("+", '+', '+')) + + @classmethod + def _find_history_path(cls, session): + from xdevice import Variables + if os.path.isdir(session): + return session + + target_path = os.path.join( + Variables.exec_dir, Variables.report_vars.report_dir, session) + if not os.path.isdir(target_path): + raise ParamError("session '%s' is invalid!" % session) + + return target_path + + def _parse_retry_option(self, options): + if Scheduler.mode == ModeType.decc: + if len(Scheduler.command_queue) < 2: + raise ParamError("no previous command executed") + _, history_command, history_report_path = \ + Scheduler.command_queue[-2] + return history_command, history_report_path + + # get history_command, history_report_path + if options.retry == "retry_previous_command": + from xdevice import Variables + history_path = os.path.join( + Variables.exec_dir, Variables.report_vars.report_dir, "latest") + if options.session: + history_path = self._find_history_path(options.session) + + params = ResultReporter.get_task_info_params(history_path) + if not params: + error_msg = "no previous command executed" if not \ + options.session else "'%s' has no command executed" % \ + options.session + raise ParamError(error_msg) + history_command, history_report_path = params[1], params[2] + else: + history_command, history_report_path = "", "" + for command_tuple in Scheduler.command_queue[:-1]: + if command_tuple[0] != options.retry: + continue + history_command, history_report_path = \ + command_tuple[1], command_tuple[2] + break + if not history_command: + raise ParamError("wrong task id input: %s" % options.retry) + return history_command, history_report_path + + @classmethod + def display_help_command_info(cls, command): + if command == ToolCommandType.toolcmd_key_run: + print(RUN_INFORMATION) + elif command == ToolCommandType.toolcmd_key_list: + print(LIST_INFORMATION) + elif command == "empty": + print(GUIDE_INFORMATION) + else: + print("'%s' command no help information." % command) + + +RUN_INFORMATION = """run: + This command is used to execute the selected testcases. + It includes a series of processes such as use case compilation, \ +execution, and result collection. + +usage: run [-l TESTLIST [TESTLIST ...] | -tf TESTFILE + [TESTFILE ...]] [-tc TESTCASE] [-c CONFIG] [-sn DEVICE_SN] + [-rp REPORT_PATH [REPORT_PATH ...]] + [-respath RESOURCE_PATH [RESOURCE_PATH ...]] + [-tcpath TESTCASES_PATH [TESTCASES_PATH ...]] + [-ta TESTARGS [TESTARGS ...]] [-pt] + [-env TEST_ENVIRONMENT [TEST_ENVIRONMENT ...]] + [-e EXECTYPE] [-t [TESTTYPE [TESTTYPE ...]]] + [-td TESTDRIVER] [-tl TESTLEVEL] [-bv BUILD_VARIANT] + [-cov COVERAGE] [--retry RETRY] [--session SESSION] + [--dryrun] [--reboot-per-module] [--check-device] + [--repeat REPEAT] + action task + +Specify tests to run. + +positional arguments: + action Specify action + task Specify task name,such as "ssts", "acts", "hits" + +optional arguments: + -h, --help show this help message and exit + -l TESTLIST [TESTLIST ...], --testlist TESTLIST [TESTLIST ...] + Specify test list + -tf TESTFILE [TESTFILE ...], --testfile TESTFILE [TESTFILE ...] + Specify test list file + -tc TESTCASE, --testcase TESTCASE + Specify test case + -c CONFIG, --config CONFIG + Specify config file path + -sn DEVICE_SN, --device_sn DEVICE_SN + Specify device serial number + -rp REPORT_PATH [REPORT_PATH ...], --reportpath REPORT_PATH [REPORT_PATH \ +...] + Specify test report path + -respath RESOURCE_PATH [RESOURCE_PATH ...], --resourcepath RESOURCE_PATH \ +[RESOURCE_PATH ...] + Specify test resource path + -tcpath TESTCASES_PATH [TESTCASES_PATH ...], --testcasespath \ +TESTCASES_PATH [TESTCASES_PATH ...] + Specify testcases path + -ta TESTARGS [TESTARGS ...], --testargs TESTARGS [TESTARGS ...] + Specify test arguments + -pt, --passthrough Pass through test arguments + -env TEST_ENVIRONMENT [TEST_ENVIRONMENT ...], --environment \ +TEST_ENVIRONMENT [TEST_ENVIRONMENT ...] + Specify test environment + -e EXECTYPE, --exectype EXECTYPE + Specify test execute type + -t [TESTTYPE [TESTTYPE ...]], --testtype [TESTTYPE [TESTTYPE ...]] + Specify test type(UT,MST,ST,PERF,SEC,RELI,DST,ALL) + -td TESTDRIVER, --testdriver TESTDRIVER + Specify test driver id + -tl TESTLEVEL, --testlevel TESTLEVEL + Specify test level + -bv BUILD_VARIANT, --build_variant BUILD_VARIANT + Specify build variant(release,debug) + -cov COVERAGE, --coverage COVERAGE + Specify coverage + --retry RETRY Specify retry command + --session SESSION retry task by session id + --dryrun show retry test case list + --reboot-per-module reboot devices before executing each module + --check-device check the test device meets the requirements + --repeat REPEAT number of times that a task is executed repeatedly + +Examples: + run -l ; + run -tf test/resource/.txt + + run –l -sn ; + run –l -respath + run –l -ta size:large + run –l –ta class:## + run –l -ta size:large -pt + run –l –env + run –l –e device + run –l –t ALL + run –l –td CppTest + run –l -tcpath resource/testcases + + run ssts + run ssts –tc ; + run ssts -sn ; + run ssts -respath + ... ... + + run acts + run acts –tc ; + run acts -sn ; + run acts -respath + ... ... + + run hits + ... ... + + run --retry + run --retry --session + run --retry --dryrun +""" + +LIST_INFORMATION = "list:" + """ + This command is used to display device list and task record.\n +usage: + list + list history + list + +Introduction: + list: display device list + list history: display history record of a serial of tasks + list : display history record about task what contains specific id + +Examples: + list + list history + list 6e****90 +""" + + +GUIDE_INFORMATION = """help: + use help to get information. + +usage: + run: Display a list of supported run command. + list: Display a list of supported device and task record. + +Examples: + help run + help list +""" diff --git a/src/xdevice/_core/common.py b/src/xdevice/_core/common.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/config/__init__.py b/src/xdevice/_core/config/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/config/config_manager.py b/src/xdevice/_core/config/config_manager.py index e148edb280fcf3499da513b18f79949ff80cd456..e2b4fab202f8e3f3aa053ca8ae61d172f28be434 100755 --- a/src/xdevice/_core/config/config_manager.py +++ b/src/xdevice/_core/config/config_manager.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,6 +22,9 @@ from dataclasses import dataclass from _core.exception import ParamError from _core.logger import platform_logger +from _core.utils import get_local_ip + +from xdevice_adapter.constants import UsbConst __all__ = ["UserConfigManager"] LOG = platform_logger("ConfigManager") @@ -35,37 +38,42 @@ class ConfigFileConst(object): class UserConfigManager(object): def __init__(self, config_file="", env=""): from xdevice import Variables - if config_file: - pass try: if env: self.config_content = ET.fromstring(env) else: - user_path = os.path.join(Variables.exec_dir, "config") - top_user_path = os.path.join(Variables.top_dir, "config") - config_path = os.path.join(Variables.res_dir, "config") - paths = [user_path, top_user_path, config_path] - - for path in paths: - if os.path.exists(os.path.abspath(os.path.join( - path, ConfigFileConst.userconfig_filepath))): - self.file_path = os.path.abspath(os.path.join( - path, ConfigFileConst.userconfig_filepath)) - break - + if config_file: + self.file_path = config_file + else: + user_path = os.path.join(Variables.exec_dir, "config") + top_user_path = os.path.join(Variables.top_dir, "config") + config_path = os.path.join(Variables.res_dir, "config") + paths = [user_path, top_user_path, config_path] + + for path in paths: + if os.path.exists(os.path.abspath(os.path.join( + path, ConfigFileConst.userconfig_filepath))): + self.file_path = os.path.abspath(os.path.join( + path, ConfigFileConst.userconfig_filepath)) + break + + LOG.debug("user config path: %s" % self.file_path) if os.path.exists(self.file_path): tree = ET.parse(self.file_path) self.config_content = tree.getroot() else: - raise ParamError("config file not found") + raise ParamError("%s not found" % self.file_path, + error_no="00115") except SyntaxError as error: if env: raise ParamError( - "Parse environment parameter fail! Error: %s" % error.args) + "Parse environment parameter fail! Error: %s" % error.args, + error_no="00115") else: raise ParamError( - "Parse %s fail! Error: %s" % (self.file_path, error.args)) + "Parse %s fail! Error: %s" % (self.file_path, error.args), + error_no="00115") def get_user_config_list(self, tag_name): data_dic = {} @@ -94,18 +102,10 @@ class UserConfigManager(object): return [] return config_list - def get_sn_list(self): + def get_sn_list(self, input_string): sn_select_list = [] - data_dic = {} - for node in self.config_content.findall("environment/device"): - if node.attrib["type"] != "usb-hdc": - continue - for sub in node: - data_dic[sub.tag] = sub.text if sub.text else "" - sn_config = data_dic.get("sn", "") - if sn_config: - sn_select_list = self._handle_str(sn_config) - break + if input_string: + sn_select_list = self._handle_str(input_string) return sn_select_list def get_remote_config(self): @@ -114,17 +114,18 @@ class UserConfigManager(object): if "ip" in data_dic.keys() and "port" in data_dic.keys(): remote_ip = data_dic.get("ip", "") - remote_adb_port = data_dic.get("port", "") + remote_port = data_dic.get("port", "") else: remote_ip = "" - remote_adb_port = "" + remote_port = "" - if (not remote_ip) or (not remote_adb_port): + if (not remote_ip) or (not remote_port): remote_ip = "" - remote_adb_port = "" - + remote_port = "" + if remote_ip == get_local_ip(): + remote_ip = "127.0.0.1" remote_dic["ip"] = remote_ip - remote_dic["port"] = remote_adb_port + remote_dic["port"] = remote_port return remote_dic def get_testcases_dir_config(self): @@ -161,7 +162,7 @@ class UserConfigManager(object): devices = [] for node in self.config_content.findall(target_name): - if node.attrib["type"] != "com": + if node.attrib["type"] != "com" and node.attrib["type"] != "agent": continue device = [node.attrib] @@ -172,6 +173,8 @@ class UserConfigManager(object): if sub.text is not None and sub.tag != "serial": data_dic[sub.tag] = sub.text if data_dic: + if data_dic.get("ip", "") == get_local_ip(): + data_dic["ip"] = "127.0.0.1" device.append(data_dic) devices.append(device) continue @@ -189,18 +192,21 @@ class UserConfigManager(object): return devices def get_device(self, target_name): - data_dic = {} - for node in self.config_content.findall(target_name): - if node.attrib["type"] != "usb-hdc": + data_dic = {} + if node.attrib["type"] != "usb-hdc" and \ + node.attrib["type"] != UsbConst.connector_type: continue + data_dic["usb_type"] = node.attrib["type"] for sub in node: if sub.text is None: data_dic[sub.tag] = "" else: data_dic[sub.tag] = sub.text - break - return data_dic + if data_dic.get("ip", "") == get_local_ip(): + data_dic["ip"] = "127.0.0.1" + return data_dic + return None def get_testcases_dir(self): from xdevice import Variables @@ -226,3 +232,15 @@ class UserConfigManager(object): return os.path.abspath( os.path.join(Variables.exec_dir, "resource")) + + def get_log_level(self): + data_dic = {} + node = self.config_content.find("loglevel") + if node is not None: + if node.find("console") is None and node.find("file") is None: + # neither loglevel/console nor loglevel/file exists + data_dic.update({"console": str(node.text).strip()}) + else: + for child in node: + data_dic.update({child.tag: str(child.text).strip()}) + return data_dic diff --git a/src/xdevice/_core/config/resource_manager.py b/src/xdevice/_core/config/resource_manager.py index a8860dcd45cb4a50e8dd00740d77590d24abe7e9..7a1039719b4b404aab17a54e1c0acf9fd2587ce1 100755 --- a/src/xdevice/_core/config/resource_manager.py +++ b/src/xdevice/_core/config/resource_manager.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -110,9 +110,7 @@ class ResourceManager(object): src = os.path.join(resource_dir, push_value[0:pos].strip()) dst = push_value[pos + len(find_key):len(push_value)].strip() - LOG.info("create_dir: dst = %s" % (dst)) device.execute_shell_command("mkdir -p %s" % dst) - LOG.info("push_file: src = %s, dst = %s" % (src, dst)) device.push_file(src, dst) elif item["name"] == "pull": push_value = item["value"] @@ -122,16 +120,13 @@ class ResourceManager(object): src = os.path.join(resource_dir, push_value[0:pos].strip()) dst = push_value[pos + len(find_key):len(push_value)].strip() - LOG.info("pull_file: src = %s, dst = %s" % (src, dst)) device.pull_file(src, dst) elif item["name"] == "shell": command = item["value"].strip() - LOG.info("shell = %s", command) device.execute_shell_command(command) else: command = "".join((item["name"], " ", item["value"])) command = command.strip() - LOG.info("others = %s", command) device.execute_command(command) @staticmethod diff --git a/src/xdevice/_core/constants.py b/src/xdevice/_core/constants.py index bc8b78ec7a8bf97e026b94e2ce4a1f15006ccb84..c69178e70144c2f1f15663e2829e819813962752 100755 --- a/src/xdevice/_core/constants.py +++ b/src/xdevice/_core/constants.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,7 +22,8 @@ __all__ = ["DeviceOsType", "ProductForm", "TestType", "TestExecType", "DeviceTestType", "HostTestType", "HostDrivenTestType", "SchedulerType", "ListenerType", "ToolCommandType", "TEST_DRIVER_SET", "LogType", "ParserType", "CKit", "ComType", - "DeviceLabelType", "DeviceLiteKernel", "GTestConst", "ManagerType"] + "DeviceLabelType", "DeviceLiteKernel", "GTestConst", "ManagerType", + "ModeType", "ConfigConst"] @dataclass @@ -40,9 +41,10 @@ class ProductForm(object): ProductForm enumeration """ phone = "phone" - car = "car" + car = "ivi" television = "tv" watch = "watch" + tablet = 'tablet' @dataclass @@ -57,6 +59,7 @@ class TestType(object): sec = "security" reli = "reliability" dst = "distributedtest" + benchmark = "benchmark" all = "ALL" @@ -76,8 +79,9 @@ class DeviceLabelType(object): """ wifiiot = "wifiiot" ipcamera = "ipcamera" - watch = "watch" + watch_gt = "watchGT" phone = "phone" + watch = "watch" @dataclass @@ -124,10 +128,12 @@ class DeviceTestType(object): hap_test = "HapTest" junit_test = "JUnitTest" jsunit_test = "JSUnitTest" + jsunit_test_lite = "JSUnitTestLite" ctest_lite = "CTestLite" cpp_test_lite = "CppTestLite" lite_cpp_test = "LiteUnitTest" open_source_test = "OpenSourceTest" + build_only_test = "BuildOnlyTestLite" @dataclass @@ -190,6 +196,8 @@ class ParserType: cpp_test_lite = "CppTestLite" cpp_test_list_lite = "CppTestListLite" open_source_test = "OpenSourceTest" + build_only_test = "BuildOnlyTestLite" + jsuit_test_lite = "JSUnitTestLite" @dataclass @@ -210,7 +218,7 @@ class ToolCommandType(object): @dataclass class CKit: push = "PushKit" - install = "ApkInstallKit" + liteinstall = "LiteAppInstallKit" command = "CommandKit" config = "ConfigKit" wifi = "WIFIKit" @@ -222,9 +230,55 @@ class CKit: liteuikit = 'LiteUiKit' rootfs = "RootFsKit" query = "QueryKit" + liteshell = "LiteShellKit" + app_install = "AppInstallKit" @dataclass class GTestConst(object): exec_para_filter = "--gtest_filter" exec_para_level = "--gtest_testsize" + + +@dataclass +class ModeType(object): + decc = "decc" + factory = "factory" + developer = "developer" + + +@dataclass +class ConfigConst(object): + action = "action" + task = "task" + testlist = "testlist" + testfile = "testfile" + testcase = "testcase" + device_sn = "device_sn" + report_path = "report_path" + resource_path = "resource_path" + testcases_path = "testcases_path" + testargs = "testargs" + pass_through = "pass_through" + test_environment = "test_environment" + exectype = "exectype" + testtype = "testtype" + testdriver = "testdriver" + retry = "retry" + session = "session" + dry_run = "dry_run" + reboot_per_module = "reboot_per_module" + check_device = "check_device" + configfile = "config" + repeat = "repeat" + + # Runtime Constant + history_report_path = "history_report_path" + product_info = "product_info" + task_state = "task_state" + recover_state = "recover_state" + need_kit_setup = "need_kit_setup" + task_kits = "task_kits" + module_kits = "module_kits" + spt = "spt" + version = "version" diff --git a/src/xdevice/_core/driver/__init__.py b/src/xdevice/_core/driver/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/driver/device_test.py b/src/xdevice/_core/driver/device_test.py index 3a55bde24cfa9859bc0bd6d006e685900cc23f97..f19475b735fcc15dba7773deeeb6bd4d4d8afbce 100755 --- a/src/xdevice/_core/driver/device_test.py +++ b/src/xdevice/_core/driver/device_test.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,9 +21,12 @@ import os import sys from _core.constants import HostDrivenTestType +from _core.constants import TestExecType +from _core.constants import ModeType from _core.constants import DeviceLabelType from _core.driver.drivers_lite import init_remote_server from _core.exception import DeviceError +from _core.exception import LiteDeviceError from _core.exception import ParamError from _core.exception import ReportException from _core.exception import ExecuteTerminate @@ -32,64 +35,18 @@ from _core.logger import platform_logger from _core.plugin import Plugin from _core.testkit.json_parser import JsonParser from _core.utils import get_config_value +from _core.utils import do_module_kit_setup +from _core.utils import do_module_kit_teardown +from _core.utils import get_filename_extension +from _core.utils import get_file_absolute_path from _core.utils import get_kit_instances from _core.utils import check_result_report +from _core.utils import check_mode from _core.report.suite_reporter import SuiteReporter LOG = platform_logger("DeviceTest") - - -def start_task(test_file, configs, device, logger): - from xdevice import Variables - # insert& devicetest path for loading devicetest module - devicetest_module = os.path.join(Variables.modules_dir, "_devicetest") - if os.path.exists(devicetest_module): - sys.path.insert(1, devicetest_module) - - if configs["testcases_path"]: - sys.path.insert(1, configs["testcases_path"]) - - from _devicetest.devicetest.main import DeviceTest - device_test = DeviceTest(test_list=test_file, configs=configs, - devices=[device], log=logger) - device_test.run() - - -def set_up_env(request, source): - if not source.endswith(".json"): - source = "%s.json" % source - json_config = JsonParser(source) - test_args = get_config_value('xml-output', json_config.get_driver()) - - kits = get_kit_instances(json_config, request.config.resource_path, - request.config.testcases_path) - kits_copy = copy.deepcopy(kits) - from xdevice import Scheduler - for kit in kits: - if not Scheduler.is_execute: - raise ExecuteTerminate() - kit.__setup__(request.config.device, request=request) - return test_args, kits, kits_copy - - -def get_file_list(module_path): - file_list = [] - - if not file_list: - for test_file in os.listdir(module_path): - if test_file.endswith(".py") or test_file.endswith(".pyd"): - file_list.append(os.path.join( - module_path, test_file)) - return file_list - else: - filter_file_list = [] - for test_file in file_list: - if (test_file.endswith(".pyd") or test_file.endswith(".py")) and \ - os.path.exists(os.path.join(module_path, test_file)): - filter_file_list.append(os.path.join( - module_path, test_file)) - - return filter_file_list +PY_SUFFIX = ".py" +PYD_SUFFIX = ".pyd" @Plugin(type=Plugin.DRIVER, id=HostDrivenTestType.device_test) @@ -111,125 +68,197 @@ class DeviceTestDriver(IDriver): pass def __check_config__(self, config=None): - del config - self.py_file = "" + pass def __init_nfs_server__(self, request=None): return init_remote_server(self, request) def __execute__(self, request): - kits, source = self._parse_request(request) + try: + # set self.config + self.config = request.config + self.config.tmp_id = str(request.uuid) + self.config.tmp_folder = os.path.join(self.config.report_path, + "temp") + self.config.devices = request.get_devices() + if request.get("exectype") == TestExecType.device_test and \ + not self.config.devices: + LOG.error("no device", error_no="00104") + raise ParamError("Load Error[00104]", error_no="00104") + + # get source, json config and kits + if request.get_config_file(): + source = request.get_config_file() + LOG.debug("Test config file path: %s" % source) + else: + source = request.get_source_string() + LOG.debug("Test String: %s" % source) + + if not source: + LOG.error("no config file found for '%s'" % + request.get_source_file(), error_no="00102") + raise ParamError("Load Error(00102)", error_no="00102") + + json_config = JsonParser(source) + kits = get_kit_instances(json_config, request.config.resource_path, + request.config.testcases_path) + + # create tmp folder + test_name = request.get_module_name() + tmp_sub_folder = self._create_tmp_folder(request) + self.result = "%s.xml" % os.path.join(tmp_sub_folder, test_name) + + # set configs keys + configs = self._set_configs(json_config, kits, request, + tmp_sub_folder) + + # get test list + test_list = self._get_test_list(json_config, request, source) + if not test_list: + raise ParamError("no test list to run") + + self._run_devicetest(configs, test_list) + except (ReportException, ModuleNotFoundError, ExecuteTerminate, + SyntaxError, ValueError, AttributeError, TypeError, + KeyboardInterrupt, ParamError, DeviceError, LiteDeviceError) \ + as exception: + error_no = getattr(exception, "error_no", "00000") + LOG.exception(exception, exc_info=False, error_no=error_no) + self.error_message = exception + + finally: + self._handle_finally(request) + + def _get_test_list(self, json_config, request, source): + test_list = get_config_value('py_file', json_config.get_driver(), + is_list=True) + if str(request.root.source.source_file).endswith(PYD_SUFFIX) or \ + str(request.root.source.source_file).endswith(PY_SUFFIX): + test_list = [request.root.source.source_file] + + if not test_list and os.path.exists(source): + test_list = _get_dict_test_list(os.path.dirname(source)) + + # check test list + testcase = request.get("testcase") + testcase_list = [] + if testcase: + testcase_list = str(testcase).split(";") + + checked_test_list = [] + for index, test in enumerate(test_list): + if not os.path.exists(test): + try: + absolute_file = get_file_absolute_path(test, [ + self.config.resource_path, self.config.testcases_path]) + except ParamError as error: + LOG.error(error, error_no=error.error_no) + continue + else: + absolute_file = test + + file_name = get_filename_extension(absolute_file)[0] + if not testcase_list or file_name in testcase_list: + checked_test_list.append(absolute_file) + else: + LOG.info("test '%s' is ignored", absolute_file) + if checked_test_list: + LOG.info("test list: {}".format(checked_test_list)) + else: + LOG.error("no test list found", error_no="00109") + raise ParamError("Load Error(00109)", error_no="00109") + return checked_test_list + + def _set_configs(self, json_config, kits, request, tmp_sub_folder): configs = dict() - configs["testargs"] = self.config.testargs or "" + configs["testargs"] = self.config.testargs or {} configs["testcases_path"] = self.config.testcases_path or "" configs["request"] = request - - try: - if self.config.device and self.config.device.label == \ - DeviceLabelType.ipcamera: + configs["test_name"] = request.get_module_name() + configs["report_path"] = tmp_sub_folder + configs["execute"] = get_config_value( + 'execute', json_config.get_driver(), False) + + for device in self.config.devices: + do_module_kit_setup(request, kits) + if device.label == DeviceLabelType.ipcamera: + # add extra keys to configs for ipcamera device self.__init_nfs_server__(request=request) - _, kits, kits_copy = set_up_env(request, source) configs["linux_host"] = self.linux_host configs["linux_directory"] = self.linux_directory - configs["kits"] = kits_copy - self.run_module(request, configs, source) - elif self.config.device and self.config.device.label == \ - DeviceLabelType.watch: - self.run_module(request, configs, source) - elif self.config.device and self.config.device.label == \ - DeviceLabelType.phone: - self.run_module(request, configs, source) - except (ReportException, ModuleNotFoundError, ExecuteTerminate, - SyntaxError, ValueError, AttributeError, TypeError, - KeyboardInterrupt, ParamError) as exception: - LOG.exception(exception) - self.error_message = exception + configs["kits"] = kits - finally: - self._handle_finally(kits, request) + return configs - def _handle_finally(self, kits, request): + def _handle_finally(self, request): from xdevice import Scheduler - for kit in kits: - kit.__teardown__(request.config.device) - if self.config.device.label == \ - DeviceLabelType.ipcamera or self.config.device.label == \ - DeviceLabelType.watch: - self.config.device.close() + + # do kit teardown + do_module_kit_teardown(request) + + # close device connect + for device in self.config.devices: + if device.label == DeviceLabelType.ipcamera or device.label == \ + DeviceLabelType.watch_gt: + device.close() + if device.label == DeviceLabelType.phone: + device.close() + + # check result report report_name = request.root.source.test_name if \ not request.root.source.test_name.startswith("{") \ else "report" - if Scheduler.mode != "decc": + module_name = request.get_module_name() + if Scheduler.mode != ModeType.decc: self.result = check_result_report( request.config.report_path, self.result, self.error_message, - report_name) + report_name, module_name) else: tmp_list = copy.copy(SuiteReporter.get_report_result()) - if os.path.dirname(self.result) not in \ - [report_path for report_path, _ in tmp_list]: + if self.result not in [report_path for report_path, _ in tmp_list]: if not self.error_message: - self.error_message = "An unknown exception occurred " \ - "during the execution case" + self.error_message = "Case not execute[01205]" self.result = check_result_report( request.config.report_path, self.result, - self.error_message, report_name) + self.error_message, report_name, module_name) - def _parse_request(self, request): - from xdevice import Variables - kits = [] - self.config = request.config - self.config.device = request.config.environment.devices[0] - if not self.config.device: - raise DeviceError("no device..........................") - current_dir = request.config.resource_path if \ - request.config.resource_path else Variables.exec_dir - if request.root.source.source_file.strip(): - source = os.path.join(current_dir, - request.root.source.source_file.strip()) - LOG.debug("Testfile FilePath: %s" % source) - else: - source = request.root.source.source_string.strip() - self.config.tmp_folder = os.path.join(self.config.report_path, - "temp") - self.config.tmp_id = str(request.uuid) - return kits, source - - def run_module(self, request, configs, source): - json_config = JsonParser(source) + def _create_tmp_folder(self, request): if request.root.source.source_file.strip(): folder_name = "task_%s_%s" % (self.config.tmp_id, request.root.source.test_name) - - tmp_sub_folder = os.path.join(self.config.tmp_folder, folder_name) - os.makedirs(tmp_sub_folder, exist_ok=True) - - configs["report_path"] = tmp_sub_folder - - self.result = "%s.xml" % os.path.join(tmp_sub_folder, "report") - - module_path = os.path.dirname(source) - file_list = get_config_value('py_file', json_config.get_driver(), - is_list=True) - if not file_list: - file_list = get_file_list(module_path) else: folder_name = "task_%s_report" % self.config.tmp_id - tmp_sub_folder = os.path.join(self.config.tmp_folder, - folder_name) - self.result = "%s.xml" % os.path.join(tmp_sub_folder, "report") - - json_config = JsonParser(source) - file_list = get_config_value('py_file', json_config.get_driver(), - is_list=True) - configs["test_name"] = request.root.source.test_name - configs["execute"] = get_config_value('execute', - json_config.get_driver(), False) + tmp_sub_folder = os.path.join(self.config.tmp_folder, folder_name) os.makedirs(tmp_sub_folder, exist_ok=True) - self._run_devicetest(configs, file_list) + return tmp_sub_folder + + def _run_devicetest(self, configs, test_list): + from xdevice import Variables - def _run_devicetest(self, configs, test_file): - start_task(test_file, configs, self.config.device, LOG) + # insert paths for loading _devicetest module and testcases + devicetest_module = os.path.join(Variables.modules_dir, "_devicetest") + if os.path.exists(devicetest_module): + sys.path.insert(1, devicetest_module) + if configs["testcases_path"]: + sys.path.insert(1, configs["testcases_path"]) + + # run devicetest + from _devicetest.devicetest.main import DeviceTest + device_test = DeviceTest(test_list=test_list, configs=configs, + devices=self.config.devices, log=LOG) + device_test.run() def __result__(self): + if check_mode(ModeType.decc): + return self.result return self.result if os.path.exists(self.result) else "" + + +def _get_dict_test_list(module_path): + test_list = [] + for root, _, files in os.walk(module_path): + for _file in files: + if _file.endswith(".py") or _file.endswith(".pyd"): + test_list.append(os.path.join(root, _file)) + return test_list diff --git a/src/xdevice/_core/driver/drivers_lite.py b/src/xdevice/_core/driver/drivers_lite.py index 510052479c25f0e70b44f381be7324c9cdbf43d9..5395f0ee8b33ff40f6eff3fa9968251106c9dac4 100755 --- a/src/xdevice/_core/driver/drivers_lite.py +++ b/src/xdevice/_core/driver/drivers_lite.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -19,20 +19,24 @@ import os import shutil import glob +import time +import stat from _core.config.config_manager import UserConfigManager +from _core.constants import ConfigConst from _core.constants import DeviceTestType from _core.constants import GTestConst from _core.constants import DeviceLabelType from _core.constants import ComType from _core.constants import ParserType +from _core.constants import CKit +from _core.constants import DeviceLiteKernel from _core.driver.parser_lite import ShellHandler from _core.environment.dmlib_lite import generate_report from _core.exception import ExecuteTerminate -from _core.exception import ShellCommandUnresponsiveException -from _core.exception import DeviceError +from _core.exception import LiteDeviceError from _core.exception import LiteDeviceExecuteCommandError -from _core.exception import LiteDeviceReadOutputError +from _core.exception import ParamError from _core.executor.listener import CollectingLiteGTestListener from _core.executor.listener import TestDescription from _core.interface import IDriver @@ -49,6 +53,7 @@ from _core.utils import get_device_log_file from _core.utils import get_filename_extension from _core.utils import get_file_absolute_path from _core.utils import get_test_component_version +from _core.report.suite_reporter import SuiteReporter __all__ = ["CppTestDriver", "CTestDriver", "init_remote_server"] LOG = platform_logger("DriversLite") @@ -56,22 +61,24 @@ FAILED_RUN_TEST_ATTEMPTS = 2 def get_nfs_server(request): - config_manager = UserConfigManager(env=request.config.test_environment) + config_manager = UserConfigManager( + config_file=request.get(ConfigConst.configfile, ""), + env=request.get(ConfigConst.test_environment, "")) remote_info = config_manager.get_user_config("testcases/server", filter_name="NfsServer") if not remote_info: err_msg = "The name of remote nfs server does not match" - LOG.error(err_msg) - raise TypeError(err_msg) + LOG.error(err_msg, error_no="00403") + raise ParamError(err_msg, error_no="00403") return remote_info def init_remote_server(lite_instance, request=None): - config_manager = UserConfigManager(env=request.config.test_environment) + config_manager = UserConfigManager( + config_file=request.get(ConfigConst.configfile, ""), + env=request.get(ConfigConst.test_environment, "")) linux_dict = config_manager.get_user_config("testcases/server") - # linux ip and replace the ip in commands list on top, which is only - # the default ip if linux_dict: setattr(lite_instance, "linux_host", linux_dict.get("ip")) setattr(lite_instance, "linux_port", linux_dict.get("port")) @@ -80,23 +87,7 @@ def init_remote_server(lite_instance, request=None): else: error_message = "nfs server does not exist, please " \ "check user_config.xml" - LOG.error(error_message) - raise DeviceError(error_message) - - -def get_execute_command(bin_file, xml_output, device_directory=None): - if bin_file.startswith("/"): - bin_file = ".%s" % bin_file - else: - bin_file = "./%s" % bin_file - - if xml_output: - report_path = "/%s/%s/" % ("reports", bin_file.split(".")[0]) - command = "".join((bin_file, " ", "--gtest_output=xml:", - device_directory, report_path)) - return command, report_path, True - else: - return bin_file, "", False + raise ParamError(error_message, error_no="00108") def get_testcases(testcases_list): @@ -105,7 +96,7 @@ def get_testcases(testcases_list): test_item = test.split("#") if len(test_item) == 1: cases_list.append(test) - elif len(test_item) == 3: + elif len(test_item) == 2: cases_list.append(test_item[-1]) return cases_list @@ -122,12 +113,10 @@ class CppTestDriver(IDriver): config = None result = "" error_message = "" - has_param = False def __init__(self): self.rerun = True self.file_name = "" - self.need_download = False def __check_environment__(self, device_options): if len(device_options) != 1 or \ @@ -139,23 +128,16 @@ class CppTestDriver(IDriver): def __check_config__(self, config=None): pass - def __init_nfs_server__(self, request=None): - return init_remote_server(self, request) - - def is_download(self, testcases_dir): - from _core.utils import get_local_ip - if (str(get_local_ip()) == self.linux_host) and ( - self.linux_directory == ("/data%s" % testcases_dir)): - return False - return True - def __execute__(self, request): kits = [] + device_log_file = get_device_log_file( + request.config.report_path, + request.get_devices()[0].__get_serial__()) try: self.config = request.config self.init_cpp_config() self.config.device = request.config.environment.devices[0] - self.__init_nfs_server__(request=request) + init_remote_server(self, request=request) config_file = request.root.source.config_file json_config = JsonParser(config_file) self._get_driver_config(json_config) @@ -168,23 +150,15 @@ class CppTestDriver(IDriver): from xdevice import Scheduler for kit in kits: if not Scheduler.is_execute: - raise ExecuteTerminate() + raise ExecuteTerminate("ExecuteTerminate", + error_no="00300") kit.__setup__(request.config.device, request=request) - execute_dir = "/".join(bin_file.split("/")[0:-1]) - execute_bin = bin_file.split("/")[-1] - - self.config.device.execute_command_with_timeout( - command="cd {}".format(execute_dir), timeout=1) + command = self._get_execute_command(bin_file) - command, report_path, self.has_param = get_execute_command( - execute_bin, self.config.xml_output, execute_dir) - self.config.device_xml_path = (self.linux_directory + - report_path).replace("//", "/") self.set_file_name(request, command) - self.need_download = self.is_download( - request.config.testcases_path) - if self.need_download and self.has_param: + + if self.config.xml_output: self.delete_device_xml(request, self.config.device_xml_path) if os.path.exists(self.result): os.remove(self.result) @@ -194,30 +168,53 @@ class CppTestDriver(IDriver): self.dry_run(command, request.listeners) else: self.run_cpp_test(command, request) - self.generate_device_xml(request, execute_bin) - device_log_file = get_device_log_file( - request.config.report_path, - request.config.device.__get_serial__()) - with open(device_log_file, "a", encoding="UTF-8") as file_name: - file_name.write(self.config.command_result) + self.generate_device_xml(request, self.execute_bin) - except (LiteDeviceExecuteCommandError, Exception) as exception: - LOG.exception(exception) + except (LiteDeviceError, Exception) as exception: + LOG.exception(exception, exc_info=False) self.error_message = exception finally: + device_log_file_open = os.open(device_log_file, os.O_WRONLY | + os.O_CREAT | os.O_APPEND, 0o755) + with os.fdopen(device_log_file_open, "a") as file_name: + file_name.write(self.config.command_result) + file_name.flush() LOG.info("-------------finally-----------------") self._after_command(kits, request) + def _get_execute_command(self, bin_file): + if self.config.device.get("device_kernel") == \ + DeviceLiteKernel.linux_kernel: + execute_dir = "/storage" + "/".join(bin_file.split("/")[0:-1]) + else: + execute_dir = "/".join(bin_file.split("/")[0:-1]) + self.execute_bin = bin_file.split("/")[-1] + + self.config.device.execute_command_with_timeout( + command="cd {}".format(execute_dir), timeout=1) + + if self.execute_bin.startswith("/"): + command = ".%s" % self.execute_bin + else: + command = "./%s" % self.execute_bin + + report_path = "/%s/%s/" % ("reports", self.execute_bin.split(".")[0]) + self.config.device_xml_path = (self.linux_directory + report_path).\ + replace("//", "/") + self.config.device_report_path = execute_dir + report_path + + return command + def _get_driver_config(self, json_config): xml_output = get_config_value('xml-output', json_config.get_driver(), False) if isinstance(xml_output, bool): self.config.xml_output = xml_output - elif str(xml_output).lower() == "true": - self.config.xml_output = True - else: + elif str(xml_output).lower() == "false": self.config.xml_output = False + else: + self.config.xml_output = True rerun = get_config_value('rerun', json_config.get_driver(), False) if isinstance(rerun, bool): @@ -227,14 +224,24 @@ class CppTestDriver(IDriver): else: self.rerun = True + timeout_config = get_config_value('timeout', + json_config.get_driver(), False) + if timeout_config: + self.config.timeout = int(timeout_config) + else: + self.config.timeout = 900 + def _after_command(self, kits, request): - self.config.device.execute_command_with_timeout( - command="cd /", timeout=1) + if self.config.device.get("device_kernel") == \ + DeviceLiteKernel.linux_kernel: + self.config.device.execute_command_with_timeout( + command="cd /storage", timeout=1) + else: + self.config.device.execute_command_with_timeout( + command="cd /", timeout=1) for kit in kits: kit.__teardown__(request.config.device) - close_error = self.config.device.close() - self.error_message = close_error if close_error else \ - "case results are abnormal" + self.config.device.close() self.delete_device_xml(request, self.linux_directory) report_name = "report" if request.root.source. \ @@ -246,43 +253,51 @@ class CppTestDriver(IDriver): report_name) def generate_device_xml(self, request, execute_bin): - if self.has_param: - if self.need_download: - self.download_nfs_xml(request, self.config.device_xml_path) - xml_count = 0 - for file_name in os.listdir(os.path.dirname(self.result)): - if file_name.startswith(execute_bin): - xml_count += 1 - if xml_count > 1: - self.merge_xml(execute_bin) - elif xml_count == 1: - self.copy_result(execute_bin) - - def dry_run(self, command, listener=None): - parsers = get_plugin(Plugin.PARSER, ParserType.cpp_test_list_lite) - parser_instances = [] - for parser in parsers: - parser_instance = parser.__class__() - parser_instance.suites_name = os.path.basename(self.result) - if listener: - parser_instance.listeners = listener - parser_instances.append(parser_instance) - handler = ShellHandler(parser_instances) + if self.config.xml_output: + self.download_nfs_xml(request, self.config.device_xml_path) + self.merge_xml(execute_bin) + + def dry_run(self, request, command, listener=None): + if self.config.xml_output: + collect_test_command = "%s --gtest_output=xml:%s " \ + "--gtest_list_tests" % \ + (command, self.config.device_report_path) + result, _, _ = self.config.device.execute_command_with_timeout( + command=collect_test_command, + case_type=DeviceTestType.cpp_test_lite, + timeout=15, receiver=None) + + tests = self.read_nfs_xml(request, self.config.device_xml_path) + self.delete_device_xml(request, self.config.device_xml_path) + return tests - collect_test_command = "%s --gtest_list_tests" % command.split(" ")[0] - result, _, _ = self.config.device.execute_command_with_timeout( - command=collect_test_command, - case_type=DeviceTestType.cpp_test_lite, - timeout=5, receiver=handler) - self.config.command_result = "{}{}".format( - self.config.command_result, result) - from xdevice import SuiteReporter - if parser_instances[0].tests and len(parser_instances[0].tests) > 0: - SuiteReporter.set_suite_list([item.test_name for item in - parser_instances[0].tests]) else: - SuiteReporter.set_suite_list([]) + parsers = get_plugin(Plugin.PARSER, ParserType.cpp_test_list_lite) + parser_instances = [] + for parser in parsers: + parser_instance = parser.__class__() + parser_instance.suites_name = os.path.basename(self.result) + if listener: + parser_instance.listeners = listener + parser_instances.append(parser_instance) + handler = ShellHandler(parser_instances) + collect_test_command = "%s --gtest_list_tests" % command + result, _, _ = self.config.device.execute_command_with_timeout( + command=collect_test_command, + case_type=DeviceTestType.cpp_test_lite, + timeout=15, receiver=handler) + self.config.command_result = "{}{}".format( + self.config.command_result, result) + if parser_instances[0].tests and \ + len(parser_instances[0].tests) > 0: + SuiteReporter.set_suite_list([item.test_name for item in + parser_instances[0].tests]) + else: + SuiteReporter.set_suite_list([]) + tests = parser_instances[0].tests + if not tests: + LOG.error("collect test failed!", error_no="00402") return parser_instances[0].tests def run_cpp_test(self, command, request): @@ -290,30 +305,34 @@ class CppTestDriver(IDriver): testcases_list = get_testcases( request.config.testargs.get("test")) for test in testcases_list: - if not self.has_param: - command = "{} {}=*{}".format( - command, GTestConst.exec_para_filter, test) + command_case = "{} --gtest_filter=*{}".format( + command, test) - self.run(command, - request.listeners, - timeout=15) + if not self.config.xml_output: + self.run(command_case, request.listeners, timeout=15) else: - self.run(command, - None, - timeout=15) + command_case = "{} --gtest_output=xml:{}".format( + command_case, self.config.device_report_path) + self.run(command_case, None, timeout=15) else: self._do_test_run(command, request) def init_cpp_config(self): setattr(self.config, "command_result", "") - setattr(self.config, "xml_path", "") setattr(self.config, "device_xml_path", "") setattr(self.config, "dry_run", False) def merge_xml(self, execute_bin): - DataHelper.get_summary_result(os.path.join( - self.config.report_path, "result"), self.result, - key=sort_by_length) + report_path = os.path.join(self.config.report_path, "result") + summary_result = DataHelper.get_summary_result( + report_path, self.result, key=sort_by_length, + file_prefix=execute_bin) + if summary_result: + SuiteReporter.append_report_result(( + os.path.join(report_path, "%s.xml" % execute_bin), + DataHelper.to_string(summary_result))) + else: + self.error_message = "The test case did not generate XML" for xml_file in os.listdir(os.path.split(self.result)[0]): if not xml_file.startswith(execute_bin): continue @@ -321,33 +340,14 @@ class CppTestDriver(IDriver): os.remove(os.path.join(os.path.split( self.result)[0], xml_file)) - def copy_result(self, execute_bin): - path, _ = os.path.split(self.result) - for xml_file in os.listdir(path): - if not xml_file.startswith(execute_bin): - continue - device_file = open(os.path.join(path, xml_file), 'r') - if os.path.exists(self.result): - os.remove(self.result) - result_file = open(self.result, "w") - for line in device_file: - result_file.write(line) - device_file.close() - result_file.close() - os.remove(os.path.join(path, xml_file)) - def set_file_name(self, request, command): - if not self.has_param or not self.is_download( - request.config.testcases_path): - self.file_name = command.split(" ")[0].split("/")[-1].split(".")[0] - else: - self.file_name = command.split(" ")[0].split("/")[-1].split(".")[0] - self.config.xml_path = "{}/reports/{}.xml".format( - self.linux_directory, self.file_name).replace("//", "/") + self.file_name = command.split(" ")[0].split("/")[-1].split(".")[0] self.result = "%s.xml" % os.path.join(request.config.report_path, "result", self.file_name) - def run(self, command=None, listener=None, timeout=900): + def run(self, command=None, listener=None, timeout=None): + if not timeout: + timeout = self.config.timeout if listener: parsers = get_plugin(Plugin.PARSER, ParserType.cpp_test_lite) parser_instances = [] @@ -366,33 +366,30 @@ class CppTestDriver(IDriver): return error, result, handler def _do_test_run(self, command, request): - listeners = request.listeners - test_to_run = self._collect_test_to_run(command) - if self.has_param and self.need_download: - listeners = None - if not test_to_run: - error, _, _ = self.run(command, listeners, timeout=900) - if error: - raise LiteDeviceExecuteCommandError( - "execute %s failed" % command) - else: - self._run_with_rerun(command, request, test_to_run) + test_to_run = self._collect_test_to_run(request, command) + self._run_with_rerun(command, request, test_to_run) def _run_with_rerun(self, command, request, expected_tests): - test_tracker = CollectingLiteGTestListener() - - if self.has_param and self.need_download: - listener = [] + if self.config.xml_output: + self.run("{} --gtest_output=xml:{}".format( + command, self.config.device_report_path)) + time.sleep(5) + test_run = self.read_nfs_xml(request, self.config.device_xml_path) + if len(test_run) < len(expected_tests): + expected_tests = TestDescription.remove_test(expected_tests, + test_run) + self._rerun_tests(command, expected_tests, None) else: + test_tracker = CollectingLiteGTestListener() listener = request.listeners - listener_copy = listener.copy() - listener_copy.append(test_tracker) - self.run(command, listener_copy, timeout=900) - test_run = test_tracker.get_current_run_results() - if len(test_run) != len(expected_tests): - expected_tests = TestDescription.remove_test(expected_tests, - test_run) - self._rerun_tests(command, expected_tests, listener) + listener_copy = listener.copy() + listener_copy.append(test_tracker) + self.run(command, listener_copy) + test_run = test_tracker.get_current_run_results() + if len(test_run) != len(expected_tests): + expected_tests = TestDescription.remove_test(expected_tests, + test_run) + self._rerun_tests(command, expected_tests, listener) def _rerun_tests(self, command, expected_tests, listener): if not expected_tests: @@ -401,34 +398,39 @@ class CppTestDriver(IDriver): self._re_run(command, test, listener) def _re_run(self, command, test, listener): - handler = None - for _ in range(FAILED_RUN_TEST_ATTEMPTS): - try: - listener_copy = listener.copy() - test_tracker = CollectingLiteGTestListener() - listener_copy.append(test_tracker) - _, _, handler = self.run("{} {}=*{}".format( - command, GTestConst.exec_para_filter, test.test_name), - listener_copy, timeout=15) - if len(test_tracker.get_current_run_results()): - return - except ShellCommandUnresponsiveException: - LOG.debug("Exception: ShellCommandUnresponsiveException") - handler.parsers[0].mark_test_as_failed(test) - - def _collect_test_to_run(self, command): + if self.config.xml_output: + _, _, handler = self.run("{} {}=*{} --gtest_output=xml:{}".format( + command, GTestConst.exec_para_filter, test.test_name, + self.config.device_report_path), + listener, timeout=15) + else: + handler = None + for _ in range(FAILED_RUN_TEST_ATTEMPTS): + try: + listener_copy = listener.copy() + test_tracker = CollectingLiteGTestListener() + listener_copy.append(test_tracker) + _, _, handler = self.run("{} {}=*{}".format( + command, GTestConst.exec_para_filter, test.test_name), + listener_copy, timeout=15) + if len(test_tracker.get_current_run_results()): + return + except LiteDeviceError: + LOG.debug("Exception: ShellCommandUnresponsiveException") + handler.parsers[0].mark_test_as_failed(test) + + def _collect_test_to_run(self, request, command): if self.rerun: - collect_test_command = command.split(" ")[0] - tests = self.dry_run(collect_test_command) + tests = self.dry_run(request, command) return tests - return None + return [] def download_nfs_xml(self, request, report_path): remote_nfs = get_nfs_server(request) if not remote_nfs: err_msg = "The name of remote device {} does not match". \ format(self.remote) - LOG.error(err_msg) + LOG.error(err_msg, error_no="00403") raise TypeError(err_msg) LOG.info("Trying to pull remote server: {}:{} report files to local " "in dir {}".format @@ -454,7 +456,7 @@ class CppTestDriver(IDriver): localpath=os.path.join(os.path.split( self.result)[0], report_xml)) except IOError as error: - LOG.error(error) + LOG.error(error, error_no="00404") client.close() else: if os.path.isdir(report_path): @@ -465,14 +467,85 @@ class CppTestDriver(IDriver): os.path.join(os.path.split( self.result)[0], report_xml)) except (FileNotFoundError, IOError) as error: - LOG.error("download xml failed %s" % error) + LOG.error("download xml failed %s" % error, error_no="00403") + + def check_xml_exist(self, xml_file, timeout=60): + ls_command = "ls %s" % self.config.device_report_path + start_time = time.time() + while time.time() - start_time < timeout: + result, _, _ = self.config.device.execute_command_with_timeout( + command=ls_command, case_type=DeviceTestType.cpp_test_lite, + timeout=5, receiver=None) + if xml_file in result: + return True + time.sleep(5) + return False + + def read_nfs_xml(self, request, report_path): + remote_nfs = get_nfs_server(request) + if not remote_nfs: + err_msg = "The name of remote device {} does not match". \ + format(self.remote) + LOG.error(err_msg, error_no="00403") + raise TypeError(err_msg) + tests = [] + file_path = os.path.join(report_path, + self.execute_bin + ".xml") + if not self.check_xml_exist(self.execute_bin + ".xml"): + return tests + + from xml.etree import ElementTree + try: + if remote_nfs["remote"] == "true": + import paramiko + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(hostname=remote_nfs.get("ip"), + port=int(remote_nfs.get("port")), + username=remote_nfs.get("username"), + password=remote_nfs.get("password")) + sftp_client = client.open_sftp() + remote_file = sftp_client.open(file_path) + try: + result = remote_file.read().decode() + suites_element = ElementTree.fromstring(result) + for suite_element in suites_element: + suite_name = suite_element.get("name", "") + for case in suite_element: + case_name = case.get("name") + test = TestDescription(suite_name, case_name) + if test not in tests: + tests.append(test) + finally: + remote_file.close() + client.close() + else: + if os.path.isdir(report_path): + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(file_path, flags, modes), + "r") as test_file: + result = test_file.read() + suites_element = ElementTree.fromstring(result) + for suite_element in suites_element: + suite_name = suite_element.get("name", "") + for case in suite_element: + case_name = case.get("name") + test = TestDescription(suite_name, case_name) + if test not in tests: + tests.append(test) + except (FileNotFoundError, IOError) as error: + LOG.error("download xml failed %s" % error, error_no="00403") + except SyntaxError as error: + LOG.error("parse xml failed %s" % error, error_no="00404") + return tests def delete_device_xml(self, request, report_path): remote_nfs = get_nfs_server(request) if not remote_nfs: err_msg = "The name of remote device {} does not match". \ format(self.remote) - LOG.error(err_msg) + LOG.error(err_msg, error_no="00403") raise TypeError(err_msg) LOG.info("delete xml directory {} from remote server: {}" "".format @@ -492,6 +565,7 @@ class CppTestDriver(IDriver): filepath = "{}{}".format(report_path, report_xml) try: sftp.remove(filepath) + time.sleep(0.5) except IOError: pass except FileNotFoundError: @@ -547,7 +621,8 @@ class CTestDriver(IDriver): self._run_ctest(source=source, request=request) except (LiteDeviceExecuteCommandError, Exception) as exception: - LOG.error(exception) + LOG.error(exception, error_no=getattr(exception, "error_no", + "00000")) self.error_message = exception finally: report_name = "report" if request.root.source. \ @@ -560,54 +635,44 @@ class CTestDriver(IDriver): def _run_ctest(self, source=None, request=None): parser_instances = [] parsers = get_plugin(Plugin.PARSER, ParserType.ctest_lite) - result = "Execute command error" try: if not source: - LOG.error("Error: source don't exist %s." % source) - return - if not self.config.device.local_device: - LOG.error( - "CTest must have a local device, please check " - "your config.") + LOG.error("Error: source don't exist %s." % source, + error_no="00101") return + version = get_test_component_version(self.config) + + for parser in parsers: + parser_instance = parser.__class__() + parser_instance.suites_name = self.file_name + parser_instance.product_info.setdefault("Version", version) + parser_instance.listeners = request.listeners + parser_instances.append(parser_instance) + handler = ShellHandler(parser_instances) + reset_cmd = self._reset_device(request, source) self.result = "%s.xml" % os.path.join( request.config.report_path, "result", self.file_name) - self.config.device.local_device.com_dict.get( + self.config.device.device.com_dict.get( ComType.deploy_com).connect() - result, _, error = self.config.device.local_device. \ + result, _, error = self.config.device.device. \ execute_command_with_timeout( command=reset_cmd, case_type=DeviceTestType.ctest_lite, - key=ComType.deploy_com, - timeout=90) + key=ComType.deploy_com, timeout=90, receiver=handler) device_log_file = get_device_log_file(request.config.report_path, request.config.device. __get_serial__()) - with open(device_log_file, "a", encoding="UTF-8") as file_name: + device_log_file_open = \ + os.open(device_log_file, os.O_WRONLY | os.O_CREAT | + os.O_APPEND, 0o755) + with os.fdopen(device_log_file_open, "a") as file_name: file_name.write("{}{}".format( "\n".join(result.split("\n")[0:-1]), "\n")) - if error: - raise LiteDeviceReadOutputError(error) - except (LiteDeviceExecuteCommandError, Exception) as exception: - LOG.error(exception) - self.error_message = exception + file_name.flush() finally: - close_error = self.config.device.local_device.com_dict.get( + self.config.device.device.com_dict.get( ComType.deploy_com).close() - if close_error: - self.error_message = close_error - - version = get_test_component_version(self.config) - - for parser in parsers: - parser_instance = parser.__class__() - parser_instance.suites_name = self.file_name - parser_instance.product_params.setdefault("Version", version) - parser_instance.listeners = request.listeners - parser_instances.append(parser_instance) - handler = ShellHandler(parser_instances) - generate_report(handler, result) def _reset_device(self, request, source): json_config = JsonParser(source) @@ -625,11 +690,10 @@ class CTestDriver(IDriver): 'burn_file', kit_info)[0].split("\\")[-1].split(".")[0] reset_cmd = kit_instance.burn_command if not Scheduler.is_execute: - raise ExecuteTerminate() - setup_result = kit_instance.__setup__( - self.config.device.local_device) - if not setup_result: - raise DeviceError("set_up wifiiot failed") + raise ExecuteTerminate("ExecuteTerminate", + error_no="00300") + kit_instance.__setup__( + self.config.device) reset_cmd = [int(item, 16) for item in reset_cmd] return reset_cmd @@ -663,16 +727,13 @@ class OpenSourceTestDriver(IDriver): def __check_config__(self, config=None): pass - def __init_nfs_server__(self, request=None): - return init_remote_server(self, request) - def __execute__(self, request): kits = [] try: self.config = request.config setattr(self.config, "command_result", "") self.config.device = request.config.environment.devices[0] - self.__init_nfs_server__(request=request) + init_remote_server(self, request) config_file = request.root.source.config_file json_config = JsonParser(config_file) pre_cmd = get_config_value('pre_cmd', json_config.get_driver(), @@ -685,38 +746,45 @@ class OpenSourceTestDriver(IDriver): from xdevice import Scheduler for kit in kits: if not Scheduler.is_execute: - raise ExecuteTerminate() + raise ExecuteTerminate("ExecuteTerminate", + error_no="00300") copy_list = kit.__setup__(request.config.device, request=request) self.file_name = request.root.source.test_name self.set_file_name(request, request.root.source.test_name) self.config.device.execute_command_with_timeout( - command=pre_cmd, timeout=0.5) + command=pre_cmd, timeout=1) self.config.device.execute_command_with_timeout( command="cd {}".format(execute_dir), timeout=1) - for test_bin in copy_list: - if not test_bin.endswith(".run-test"): - continue - command, _, _ = get_execute_command(test_bin, False) - self._do_test_run(command, request) device_log_file = get_device_log_file( request.config.report_path, request.config.device.__get_serial__()) - with open(device_log_file, "a", encoding="UTF-8") as file_name: - file_name.write(self.config.command_result) + device_log_file_open = \ + os.open(device_log_file, os.O_WRONLY | os.O_CREAT | + os.O_APPEND, 0o755) + with os.fdopen(device_log_file_open, "a") as file_name: + for test_bin in copy_list: + if not test_bin.endswith(".run-test"): + continue + if test_bin.startswith("/"): + command = ".%s" % test_bin + else: + command = "./%s" % test_bin + self._do_test_run(command, request) + file_name.write(self.config.command_result) + file_name.flush() except (LiteDeviceExecuteCommandError, Exception) as exception: - LOG.error(exception) + LOG.error(exception, error_no=getattr(exception, "error_no", + "00000")) self.error_message = exception finally: LOG.info("-------------finally-----------------") # umount the dirs already mount for kit in kits: kit.__teardown__(request.config.device) - close_error = self.config.device.close() - self.error_message = close_error if close_error else\ - "case results are abnormal" + self.config.device.close() report_name = "report" if request.root.source. \ test_name.startswith("{") else get_filename_extension( request.root.source.test_name)[0] @@ -743,9 +811,8 @@ class OpenSourceTestDriver(IDriver): result, _, error = self.config.device.execute_command_with_timeout( command=command, case_type=DeviceTestType.open_source_test, timeout=timeout, receiver=self.handler) - self.config.command_result = "{}{}".format( - self.config.command_result, result) - if "test pass" in result.lower() or "tests pass" in result.lower(): + self.config.command_result = result + if "pass" in result.lower(): break return error, result, self.handler @@ -753,10 +820,201 @@ class OpenSourceTestDriver(IDriver): listeners = request.listeners for listener in listeners: listener.device_sn = self.config.device.device_sn - error, _, _ = self.run(command, listeners, timeout=20) + error, _, _ = self.run(command, listeners, timeout=60) if error: LOG.error( - "execute %s failed" % command) + "execute %s failed" % command, error_no="00402") + + def __result__(self): + return self.result if os.path.exists(self.result) else "" + + +@Plugin(type=Plugin.DRIVER, id=DeviceTestType.build_only_test) +class BuildOnlyTestDriver(IDriver): + """ + BuildOnlyTest is a test that runs a native test package on given + device lite device. + """ + config = None + result = "" + error_message = "" + + def __check_environment__(self, device_options): + pass + + def __check_config__(self, config): + pass + + def __execute__(self, request): + self.config = request.config + self.config.device = request.config.environment.devices[0] + self.file_name = request.root.source.test_name + self.config_file = request.root.source.config_file + file_path = self._get_log_file() + result_list = self._get_result_list(file_path) + if len(result_list) == 0: + LOG.error( + "Error: source don't exist %s." % request.root.source. + source_file, error_no="00101") + return + total_result = '' + for result in result_list: + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(result, flags, modes), "r", + encoding="utf-8") as file_content: + result = file_content.read() + if not result.endswith('\n'): + result = '%s\n' % result + total_result = '{}{}'.format(total_result, result) + parsers = get_plugin(Plugin.PARSER, ParserType.build_only_test) + parser_instances = [] + for parser in parsers: + parser_instance = parser.__class__() + parser_instance.suite_name = self.file_name + parser_instance.listeners = request.listeners + parser_instances.append(parser_instance) + handler = ShellHandler(parser_instances) + generate_report(handler, total_result) + + @classmethod + def _get_result_list(cls, file_path): + result_list = list() + for root_path, _, file_names in os.walk(file_path): + for file_name in file_names: + if file_name == "logfile": + result_list.append(os.path.join(root_path, file_name)) + return result_list + + def _get_log_file(self): + json_config = JsonParser(self.config_file) + log_path = get_config_value('log_path', json_config.get_driver(), + False) + file_path = get_file_absolute_path(log_path) + return file_path + + def __result__(self): + return self.result if os.path.exists(self.result) else "" + + +@Plugin(type=Plugin.DRIVER, id=DeviceTestType.jsunit_test_lite) +class JSUnitTestLiteDriver(IDriver): + """ + JSUnitTestDriver is a Test that runs a native test package on given device. + """ + + def __init__(self): + self.result = "" + self.error_message = "" + self.kits = [] + self.config = None + + def __check_environment__(self, device_options): + pass + + def __check_config__(self, config): + pass + + def _get_driver_config(self, json_config): + bundle_name = get_config_value('bundle-name', + json_config.get_driver(), False) + if not bundle_name: + raise ParamError("Can't find bundle-name in config file.", + error_no="00108") + else: + self.config.bundle_name = bundle_name + + ability = get_config_value('ability', + json_config.get_driver(), False) + if not ability: + self.config.ability = "default" + else: + self.config.ability = ability + + def __execute__(self, request): + try: + LOG.debug("Start execute xdevice extension JSUnit Test") + + self.config = request.config + self.config.device = request.config.environment.devices[0] + + config_file = request.root.source.config_file + suite_file = request.root.source.source_file + + if not suite_file: + raise ParamError( + "test source '%s' not exists" % + request.root.source.source_string, error_no="00101") + + if not os.path.exists(config_file): + LOG.error("Error: Test cases don't exist %s." % config_file, + error_no="00101") + raise ParamError( + "Error: Test cases don't exist %s." % config_file, + error_no="00101") + + self.file_name = os.path.basename( + request.root.source.source_file.strip()).split(".")[0] + + self.result = "%s.xml" % os.path.join( + request.config.report_path, "result", self.file_name) + + json_config = JsonParser(config_file) + self.kits = get_kit_instances(json_config, + self.config.resource_path, + self.config.testcases_path) + + self._get_driver_config(json_config) + from xdevice import Scheduler + for kit in self.kits: + if not Scheduler.is_execute: + raise ExecuteTerminate("ExecuteTerminate", + error_no="00300") + if kit.__class__.__name__ == CKit.liteinstall: + kit.bundle_name = self.config.bundle_name + kit.__setup__(self.config.device, request=request) + + self._run_jsunit(request) + + except Exception as exception: + self.error_message = exception + finally: + report_name = "report" if request.root.source. \ + test_name.startswith("{") else get_filename_extension( + request.root.source.test_name)[0] + + self.result = check_result_report( + request.config.report_path, self.result, self.error_message, + report_name) + + for kit in self.kits: + kit.__teardown__(self.config.device) + self.config.device.close() + + def _run_jsunit(self, request): + parser_instances = [] + parsers = get_plugin(Plugin.PARSER, ParserType.jsuit_test_lite) + for parser in parsers: + parser_instance = parser.__class__() + parser_instance.suites_name = self.file_name + parser_instance.listeners = request.listeners + parser_instances.append(parser_instance) + handler = ShellHandler(parser_instances) + + command = "./bin/aa start -p %s -n %s" % \ + (self.config.bundle_name, self.config.ability) + result, _, error = self.config.device.execute_command_with_timeout( + command=command, timeout=300, receiver=handler) + device_log_file = get_device_log_file(request.config.report_path, + request.config.device. + __get_serial__()) + device_log_file_open =\ + os.open(device_log_file, os.O_WRONLY | os.O_CREAT | os.O_APPEND, + 0o755) + with os.fdopen(device_log_file_open, "a") as file_name: + file_name.write("{}{}".format( + "\n".join(result.split("\n")[0:-1]), "\n")) + file_name.flush() def __result__(self): return self.result if os.path.exists(self.result) else "" diff --git a/src/xdevice/_core/driver/parser_lite.py b/src/xdevice/_core/driver/parser_lite.py index 3d2e87a9d98211f63dbfd2f0e3f8ceb0ab30b934..566b7dd56846b903d01901c2387f21b10e1ab2de 100755 --- a/src/xdevice/_core/driver/parser_lite.py +++ b/src/xdevice/_core/driver/parser_lite.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -19,6 +19,7 @@ import copy import re import time +import datetime from queue import Queue from _core.interface import IParser @@ -52,8 +53,7 @@ _CTEST_SUITE_TIME_RUN_TAG = "Run test suite " _CTEST_SETUP_TAG = "setup" _CTEST_RUN_TAG = "-----------------------" -_TEST_PASSED_LOWER = "test pass" -_TESTS_PASSED_LOWER = "tests pass" +_TEST_PASSED_LOWER = "pass" _COMPILE_PASSED = "compile PASSED" _COMPILE_PARA = r"(.* compile .*)" @@ -62,6 +62,14 @@ _PRODUCT_PARA = r"(.*The .* is .*)" _PRODUCT_PARA_START = r"To Obtain Product Params Start" _PRODUCT_PARA_END = r"To Obtain Product Params End" +_START_JSUNIT_RUN_MARKER = "[start] start run suites" +_START_JSUNIT_SUITE_RUN_MARKER = "[suite start]" +_START_JSUNIT_SUITE_END_MARKER = "[suite end]" +_END_JSUNIT_RUN_MARKER = "[end] run suites end" +_PASS_JSUNIT_MARKER = "[%s]" % "pass" +_FAIL_JSUNIT_MARKER = "[fail]" +_ACE_LOG_MARKER = "[Console Info]" + LOG = platform_logger("ParserLite") @@ -71,7 +79,7 @@ class CppTestParserLite(IParser): self.state_machine = StateRecorder() self.suite_name = "" self.listeners = [] - self.product_params = {} + self.product_info = {} self.is_params = False def get_suite_name(self): @@ -103,24 +111,24 @@ class CppTestParserLite(IParser): suites = copy.copy(suites_result) listener.__ended__(LifeCycle.TestSuites, test_result=suites, suites_name=suites.suites_name, - product_params=suites.product_params) + product_info=suites.product_info) self.state_machine.current_suites = None @staticmethod def _is_test_run(line): - return True if line.startswith(_TEST_RUN_TAG) else False + return True if _TEST_RUN_TAG in line else False @staticmethod def _is_test_start_run(line): - return True if line.startswith(_TEST_START_RUN_TAG) else False + return True if _TEST_START_RUN_TAG in line else False @staticmethod def _is_informational_start(line): - return True if line.startswith(_INFORMATIONAL_START) else False + return True if _INFORMATIONAL_START in line else False @staticmethod def _is_test_start(line): - return True if line.startswith(_TEST_START_TAG) else False + return True if _TEST_START_TAG in line else False def _process_informational_line(self, line): pattern = r"(.*) (\(\d+ ms total\))" @@ -142,18 +150,18 @@ class CppTestParserLite(IParser): elif _PRODUCT_PARA_END in line: self.is_params = False if re.match(_PRODUCT_PARA, line) and self.is_params: - handle_product_params(line, self.product_params) + handle_product_info(line, self.product_info) if self.state_machine.suites_is_started() or self._is_test_run(line): if self._is_test_start_run(line): - message = line[len(_TEST_RUN_TAG):].strip() - self.handle_suites_started_tag(message) + self.handle_suites_started_tag(line) elif self._is_informational_start(line): self._process_informational_line(line) elif self._is_test_run(line): self._process_test_run_line(line) elif self._is_test_start(line): - message = line[len(_TEST_START_TAG):].strip() + message = line[line.index(_TEST_START_TAG) + + len(_TEST_START_TAG):].strip() self.handle_test_started_tag(message) else: self.process_test(line) @@ -165,7 +173,7 @@ class CppTestParserLite(IParser): if not self.state_machine.test_is_running(): LOG.error( "Found {} without {} before, wrong GTest log format". - format(line, _TEST_START_TAG)) + format(line, _TEST_START_TAG), error_no="00405") return self.handle_test_ended_tag(message, ResultCode.SKIPPED) elif _TEST_OK_TAG in line: @@ -174,7 +182,7 @@ class CppTestParserLite(IParser): if not self.state_machine.test_is_running(): LOG.error( "Found {} without {} before, wrong GTest log format". - format(line, _TEST_START_TAG)) + format(line, _TEST_START_TAG), error_no="00405") return self.handle_test_ended_tag(message, ResultCode.PASSED) elif _ALT_OK_TAG in line: @@ -210,13 +218,13 @@ class CppTestParserLite(IParser): @classmethod def parse_test_description(cls, message): run_time = 0 - matcher = re.match(r'(.*) \((\d+) ms\)', message) + matcher = re.match(r'(.*) \((\d+) ms\)(.*)', message) if matcher: test_class, test_name = matcher.group(1).rsplit(".", 1) run_time = int(matcher.group(2)) else: test_class, test_name = message.rsplit(".", 1) - return test_class, test_name, run_time + return test_class.split(" ")[-1], test_name.split(" ")[0], run_time def handle_test_ended_tag(self, message, test_status): test_class, test_name, run_time = self.parse_test_description( @@ -226,18 +234,21 @@ class CppTestParserLite(IParser): test_result.code = test_status.value if not test_result.is_running(): LOG.error( - "Test has no start tag when trying to end test: %s", message) + "Test has no start tag when trying to end test: %s", message, + error_no="00405") return found_unexpected_test = False if test_result.test_class != test_class: LOG.error( "Expected class: {} but got:{} ".format(test_result.test_class, - test_class)) + test_class), + error_no="00405") found_unexpected_test = True if test_result.test_name != test_name: LOG.error( "Expected test: {} but got: {}".format(test_result.test_name, - test_name)) + test_name), + error_no="00405") found_unexpected_test = True test_result.current = self.state_machine.running_test_index + 1 self.state_machine.test().is_completed = True @@ -255,13 +266,13 @@ class CppTestParserLite(IParser): def handle_suites_started_tag(self, message): self.state_machine.get_suites(reset=True) - matcher = re.match(r'Running (\d+) test[s]? from .*', message) + matcher = re.match(r'.* Running (\d+) test[s]? from .*', message) expected_test_num = int(matcher.group(1)) if matcher else -1 if expected_test_num >= 0: test_suites = self.state_machine.get_suites() test_suites.suites_name = self.get_suite_name() test_suites.test_num = expected_test_num - test_suites.product_params = self.product_params + test_suites.product_info = self.product_info for listener in self.get_listeners(): suite_report = copy.copy(test_suites) listener.__started__(LifeCycle.TestSuites, suite_report) @@ -299,7 +310,7 @@ class CppTestParserLite(IParser): copy_suites = copy.copy(suites) listener.__ended__(LifeCycle.TestSuites, test_result=copy_suites, suites_name=suites.suites_name, - product_params=suites.product_params) + product_info=suites.product_info) def append_test_output(self, message): if self.state_machine.test().stacktrace: @@ -409,7 +420,7 @@ class CppTestListParserLite(IParser): if not self.last_test_class_name: LOG.error( "parsed new test case name %s but no test class" - " name has been set" % line) + " name has been set" % line, error_no="00405") else: test = TestDescription(self.last_test_class_name, self.method_result.group(1)) @@ -521,7 +532,7 @@ class CTestParser(IParser): self.state_machine = StateRecorder() self.suites_name = "" self.listeners = [] - self.product_params = {} + self.product_info = {} self.is_params = False def get_suite_name(self): @@ -543,7 +554,7 @@ class CTestParser(IParser): for listener in self.get_listeners(): listener.__ended__(LifeCycle.TestSuites, test_result=suites, suites_name=suites.suites_name, - product_params=suites.product_params) + product_info=suites.product_info) self.state_machine.current_suites = None @staticmethod @@ -556,7 +567,7 @@ class CTestParser(IParser): @staticmethod def _is_ctest_run(line): - return True if line.endswith(_CTEST_RUN_TAG) else False + return re.match(r".*(\d+ Tests \d+ Failures \d+ Ignored).*", line) def _is_ctest_suite_test_run(self, line): return re.match("{}{}".format(self.pattern, _CTEST_SUITE_TEST_RUN_TAG), @@ -578,13 +589,13 @@ class CTestParser(IParser): r"(.*" + "\\.c:" + "\\d+:.*:(PASS|FAIL|OK|IGNORE"")\\.*)", line.strip()) + @staticmethod + def _is_result_line(line): + return line.find("PASS") != -1 or line.find("FAIL") != -1 or line.find( + "IGNORE") != -1 + def parse(self, line): - if _PRODUCT_PARA_START in line: - self.is_params = True - elif _PRODUCT_PARA_END in line: - self.is_params = False - if self.is_params and re.match(_PRODUCT_PARA, line): - handle_product_params(line, self.product_params) + self._parse_product_info(line) if self.state_machine.suites_is_started() or \ self._is_ctest_start_test_run(line): @@ -594,20 +605,56 @@ class CTestParser(IParser): elif self._is_ctest_end_test_run(line): self.process_suites_ended_tag() elif self._is_ctest_run(line): - self.handle_suite_ended_tag() + self.handle_suite_ended_tag(line) elif self._is_ctest_suite_test_run(line) and \ not self.state_machine.suite_is_running(): self._process_ctest_suite_test_run_line(line) elif self.is_ctest_suite_time_run(line) and \ not self.state_machine.suite_is_running(): self.handle_suite_started_tag(line) - elif line.find(":") != -1 and line.count(":") >= 3: - if self._is_execute_result_line(line): - self.handle_one_test_tag(line.strip()) + elif self._is_result_line(line) and \ + self.state_machine.suite_is_running(): + if line.find(":") != -1 and line.count( + ":") >= 3 and self._is_execute_result_line(line): + self.handle_one_test_tag(line.strip(), False) + else: + self.handle_one_test_tag(line.strip(), True) except AttributeError: - LOG.error("parsing log: %s failed" % (line.strip())) + LOG.error("parsing log: %s failed" % (line.strip()), + error_no="00405") self.last_line = line + def _parse_product_info(self, line): + if _PRODUCT_PARA_START in line: + self.is_params = True + elif _PRODUCT_PARA_END in line: + self.is_params = False + if self.is_params and re.match(_PRODUCT_PARA, line): + handle_product_info(line, self.product_info) + + def parse_error_test_description(self, message): + end_time = re.match(self.pattern, message).group().strip() + start_time = re.match(self.pattern, + self.last_line.strip()).group().strip() + start_timestamp = int(time.mktime( + time.strptime(start_time, "%Y-%m-%d %H:%M:%S.%f"))) * 1000 + int( + start_time.split(".")[-1]) + end_timestamp = int(time.mktime( + time.strptime(end_time, "%Y-%m-%d %H:%M:%S.%f"))) * 1000 + int( + end_time.split(".")[-1]) + run_time = end_timestamp - start_timestamp + status_dict = {"PASS": ResultCode.PASSED, "FAIL": ResultCode.FAILED, + "IGNORE": ResultCode.SKIPPED} + status = "" + if message.find("PASS") != -1: + status = "PASS" + elif message.find("FAIL") != -1: + status = "FAIL" + elif message.find("IGNORE") != -1: + status = "IGNORE" + status = status_dict.get(status) + return "", "", status, run_time + def parse_test_description(self, message): test_class = message.split(".c:")[0].split(" ")[-1].split("/")[-1] @@ -629,9 +676,13 @@ class CTestParser(IParser): status = status_dict.get(status) return test_class, test_name, status, run_time - def handle_one_test_tag(self, message): - test_class, test_name, status, run_time = \ - self.parse_test_description(message) + def handle_one_test_tag(self, message, is_error): + if is_error: + test_class, test_name, status, run_time = \ + self.parse_error_test_description(message) + else: + test_class, test_name, status, run_time = \ + self.parse_test_description(message) test_result = self.state_machine.test(reset=True) test_result.test_class = test_class test_result.test_name = test_name @@ -660,7 +711,7 @@ class CTestParser(IParser): elif ResultCode.SKIPPED == status: for listener in self.get_listeners(): result = copy.copy(test_result) - listener.__skipped__(LifeCycle.TestCase, result) + listener.__failed__(LifeCycle.TestCase, result) self.state_machine.test().is_completed = True test_suite.test_num += 1 @@ -674,7 +725,7 @@ class CTestParser(IParser): self.state_machine.get_suites(reset=True) test_suites = self.state_machine.get_suites() test_suites.suites_name = self.suites_name - test_suites.product_params = self.product_params + test_suites.product_info = self.product_info test_suites.test_num = 0 for listener in self.get_listeners(): suite_report = copy.copy(test_suites) @@ -692,7 +743,7 @@ class CTestParser(IParser): suite_report = copy.copy(test_suite) listener.__started__(LifeCycle.TestSuite, suite_report) - def handle_suite_ended_tag(self): + def handle_suite_ended_tag(self, line): suite_result = self.state_machine.suite() suites = self.state_machine.get_suites() suite_result.run_time = suite_result.run_time @@ -711,7 +762,7 @@ class CTestParser(IParser): for listener in self.get_listeners(): listener.__ended__(LifeCycle.TestSuites, test_result=suites, suites_name=suites.suites_name, - product_params=suites.product_params) + product_info=suites.product_info) def append_test_output(self, message): if self.state_machine.test().stacktrace: @@ -731,6 +782,7 @@ class OpenSourceParser(IParser): self.listeners = [] self.output = "" self.lines = [] + self.start_time = None def get_suite_name(self): return self.suite_name @@ -739,6 +791,8 @@ class OpenSourceParser(IParser): return self.listeners def __process__(self, lines): + if not self.start_time: + self.start_time = datetime.datetime.now() self.lines.extend(lines) def __done__(self): @@ -758,15 +812,23 @@ class OpenSourceParser(IParser): listener.__started__(LifeCycle.TestCase, result) for line in self.lines: self.output = "{}{}".format(self.output, line) - if _TEST_PASSED_LOWER in line.lower() or \ - _TESTS_PASSED_LOWER in line.lower(): + if _TEST_PASSED_LOWER in line.lower(): test_result.code = ResultCode.PASSED.value + if self.start_time: + end_time = datetime.datetime.now() + run_time = (end_time - self.start_time).total_seconds() + test_result.run_time = int(run_time * 1000) for listener in self.get_listeners(): result = copy.copy(test_result) listener.__ended__(LifeCycle.TestCase, result) break else: test_result.code = ResultCode.FAILED.value + test_result.stacktrace = "\\n".join(self.lines) + if self.start_time: + end_time = datetime.datetime.now() + run_time = (end_time - self.start_time).total_seconds() + test_result.run_time = int(run_time * 1000) for listener in self.get_listeners(): result = copy.copy(test_result) listener.__ended__(LifeCycle.TestCase, result) @@ -791,6 +853,221 @@ class OpenSourceParser(IParser): suite_report=True) +@Plugin(type=Plugin.PARSER, id=ParserType.build_only_test) +class BuildOnlyParser(IParser): + def __init__(self): + self.state_machine = StateRecorder() + self.suite_name = "" + self.test_name = "" + self.test_num = 0 + self.listeners = [] + self.output = "" + + def get_suite_name(self): + return self.suite_name + + def get_listeners(self): + return self.listeners + + def __process__(self, lines): + if not self.state_machine.suites_is_started(): + self.state_machine.trace_logs.extend(lines) + self.handle_suite_started_tag(self.test_num) + + self.state_machine.running_test_index = \ + self.state_machine.running_test_index + 1 + + for line in lines: + if re.match(_COMPILE_PARA, line): + self.test_name = str(line).split('compile')[0].strip() + test_result = self.state_machine.test(reset=True) + test_result.run_time = 0 + test_result.test_class = self.suite_name + test_result.test_name = self.test_name + for listener in self.get_listeners(): + result = copy.copy(test_result) + listener.__started__(LifeCycle.TestCase, result) + if _COMPILE_PASSED in line: + test_result.code = ResultCode.PASSED.value + for listener in self.get_listeners(): + result = copy.copy(test_result) + listener.__ended__(LifeCycle.TestCase, result) + else: + test_result.code = ResultCode.FAILED.value + for listener in self.get_listeners(): + result = copy.copy(test_result) + listener.__failed__(LifeCycle.TestCase, result) + self.state_machine.test().is_completed = True + + def __done__(self): + self.handle_suite_ended_tag() + + def handle_suite_started_tag(self, test_num): + test_suite = self.state_machine.suite() + if test_num >= 0: + test_suite.suite_name = self.suite_name + test_suite.test_num = test_num + for listener in self.get_listeners(): + suite_report = copy.copy(test_suite) + listener.__started__(LifeCycle.TestSuite, suite_report) + + def handle_suite_ended_tag(self): + suite_result = self.state_machine.suite() + for listener in self.get_listeners(): + suite = copy.copy(suite_result) + listener.__ended__(LifeCycle.TestSuite, suite, + suite_report=True) + + +@Plugin(type=Plugin.PARSER, id=ParserType.jsuit_test_lite) +class JSUnitParserLite(IParser): + last_line = "" + pattern = r"(\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}\.\d{3}) " + + def __init__(self): + self.state_machine = StateRecorder() + self.suites_name = "" + self.listeners = [] + + def get_listeners(self): + return self.listeners + + def __process__(self, lines): + if not self.state_machine.suites_is_started(): + self.state_machine.trace_logs.extend(lines) + for line in lines: + self.parse(line) + + def __done__(self): + pass + + def parse(self, line): + if (self.state_machine.suites_is_started() or + line.find(_START_JSUNIT_RUN_MARKER) != -1) and \ + line.find(_ACE_LOG_MARKER) != -1: + if line.find(_START_JSUNIT_RUN_MARKER) != -1: + self.handle_suites_started_tag() + elif line.endswith(_END_JSUNIT_RUN_MARKER): + self.handle_suites_ended_tag() + elif line.find(_START_JSUNIT_SUITE_RUN_MARKER) != -1: + self.handle_suite_started_tag(line.strip()) + elif line.endswith(_START_JSUNIT_SUITE_END_MARKER): + self.handle_suite_ended_tag() + elif _PASS_JSUNIT_MARKER in line or _FAIL_JSUNIT_MARKER \ + in line: + self.handle_one_test_tag(line.strip()) + self.last_line = line + + def parse_test_description(self, message): + pattern = r"\[(pass|fail)\]" + year = time.strftime("%Y") + filter_message = message.split("[Console Info]")[1].strip() + end_time = "%s-%s" % \ + (year, re.match(self.pattern, message).group().strip()) + start_time = "%s-%s" % \ + (year, re.match(self.pattern, + self.last_line.strip()).group().strip()) + start_timestamp = int(time.mktime( + time.strptime(start_time, "%Y-%m-%d %H:%M:%S.%f"))) * 1000 + int( + start_time.split(".")[-1]) + end_timestamp = int(time.mktime( + time.strptime(end_time, "%Y-%m-%d %H:%M:%S.%f"))) * 1000 + int( + end_time.split(".")[-1]) + run_time = end_timestamp - start_timestamp + _, status_end_index = re.match(pattern, filter_message).span() + status = filter_message[:status_end_index] + test_name = filter_message[status_end_index:] + status_dict = {"pass": ResultCode.PASSED, "fail": ResultCode.FAILED, + "ignore": ResultCode.SKIPPED} + status = status_dict.get(status[1:-1]) + return test_name, status, run_time + + def handle_suites_started_tag(self): + self.state_machine.get_suites(reset=True) + test_suites = self.state_machine.get_suites() + test_suites.suites_name = self.suites_name + test_suites.test_num = 0 + for listener in self.get_listeners(): + suite_report = copy.copy(test_suites) + listener.__started__(LifeCycle.TestSuites, suite_report) + + def handle_suites_ended_tag(self): + suites = self.state_machine.get_suites() + suites.is_completed = True + + for listener in self.get_listeners(): + listener.__ended__(LifeCycle.TestSuites, test_result=suites, + suites_name=suites.suites_name) + + def handle_one_test_tag(self, message): + test_name, status, run_time = \ + self.parse_test_description(message) + test_result = self.state_machine.test(reset=True) + test_suite = self.state_machine.suite() + test_result.test_class = test_suite.suite_name + test_result.test_name = test_name + test_result.run_time = run_time + test_result.code = status.value + test_result.current = self.state_machine.running_test_index + 1 + self.state_machine.suite().run_time += run_time + for listener in self.get_listeners(): + test_result = copy.copy(test_result) + listener.__started__(LifeCycle.TestCase, test_result) + + test_suites = self.state_machine.get_suites() + found_unexpected_test = False + + if found_unexpected_test or ResultCode.FAILED == status: + for listener in self.get_listeners(): + result = copy.copy(test_result) + listener.__failed__(LifeCycle.TestCase, result) + elif ResultCode.SKIPPED == status: + for listener in self.get_listeners(): + result = copy.copy(test_result) + listener.__skipped__(LifeCycle.TestCase, result) + + self.state_machine.test().is_completed = True + test_suite.test_num += 1 + test_suites.test_num += 1 + for listener in self.get_listeners(): + result = copy.copy(test_result) + listener.__ended__(LifeCycle.TestCase, result) + self.state_machine.running_test_index += 1 + + def fake_run_marker(self, message): + fake_marker = re.compile(" +").split(message) + self.processTestStartedTag(fake_marker) + + def handle_suite_started_tag(self, message): + self.state_machine.suite(reset=True) + test_suite = self.state_machine.suite() + if re.match(r".*\[suite start\].*", message): + _, index = re.match(r".*\[suite start\]", message).span() + test_suite.suite_name = message[index:] + test_suite.test_num = 0 + for listener in self.get_listeners(): + suite_report = copy.copy(test_suite) + listener.__started__(LifeCycle.TestSuite, suite_report) + + def handle_suite_ended_tag(self): + suite_result = self.state_machine.suite() + suites = self.state_machine.get_suites() + suite_result.run_time = suite_result.run_time + suites.run_time += suite_result.run_time + suite_result.is_completed = True + + for listener in self.get_listeners(): + suite = copy.copy(suite_result) + listener.__ended__(LifeCycle.TestSuite, suite, is_clear=True) + + def append_test_output(self, message): + if self.state_machine.test().stacktrace: + self.state_machine.test().stacktrace = \ + "%s\r\n" % self.state_machine.test().stacktrace + self.state_machine.test().stacktrace = \ + ''.join((self.state_machine.test().stacktrace, message)) + + class ShellHandler: def __init__(self, parsers): self.parsers = [] @@ -846,8 +1123,8 @@ class ShellHandler: parser.__done__() -def handle_product_params(message, product_params): +def handle_product_info(message, product_info): message = message[message.index("The"):] items = message[len("The "):].split(" is ") - product_params.setdefault(items[0].strip(), - items[1].strip().strip("[").strip("]")) + product_info.setdefault(items[0].strip(), + items[1].strip().strip("[").strip("]")) diff --git a/src/xdevice/_core/environment/__init__.py b/src/xdevice/_core/environment/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/environment/device_lite.py b/src/xdevice/_core/environment/device_lite.py index 27a68e36e2c78e1c64664d3e3633f4af23a3efed..0ddc5766dcd4ecb0c1140157190a655cbc3d48f3 100755 --- a/src/xdevice/_core/environment/device_lite.py +++ b/src/xdevice/_core/environment/device_lite.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,23 +20,29 @@ import re import telnetlib import time import os +import threading from _core.constants import DeviceOsType +from _core.constants import ConfigConst from _core.constants import ComType from _core.constants import DeviceLabelType +from _core.constants import ModeType from _core.environment.dmlib_lite import LiteHelper from _core.exception import LiteDeviceConnectError -from _core.exception import ParamError -from _core.exception import DeviceError +from _core.exception import LiteDeviceTimeout +from _core.exception import LiteParamError from _core.interface import IDevice -from _core.exception import ExecuteTerminate from _core.logger import platform_logger from _core.environment.manager_env import DeviceAllocationState from _core.plugin import Plugin from _core.utils import exec_cmd +from _core.utils import convert_serial +from _core.utils import convert_ip +from _core.utils import check_mode LOG = platform_logger("DeviceLite") TIMEOUT = 90 +RETRY_ATTEMPTS = 0 HDC = "litehdc.exe" @@ -58,7 +64,7 @@ def get_hdc_path(): if os.path.exists(file_path): return file_path else: - raise ParamError("config file not found") + raise LiteParamError("litehdc.exe not found", error_no="00108") def parse_available_com(com_str): @@ -69,6 +75,33 @@ def parse_available_com(com_str): return com_list +def perform_device_action(func): + def device_action(self, *args, **kwargs): + if not self.get_recover_state(): + LOG.debug("device %s %s is false" % (self.device_sn, + ConfigConst.recover_state)) + return "", "", "" + + tmp = int(kwargs.get("retry", RETRY_ATTEMPTS)) + retry = tmp + 1 if tmp > 0 else 1 + exception = None + for num in range(retry): + try: + result = func(self, *args, **kwargs) + return result + except LiteDeviceTimeout as error: + LOG.error(error) + exception = error + if num: + self.recover_device() + except Exception as error: + LOG.error(error) + exception = error + raise exception + + return device_action + + @Plugin(type=Plugin.DEVICE, id=DeviceOsType.lite) class DeviceLite(IDevice): """ @@ -83,42 +116,52 @@ class DeviceLite(IDevice): device_allocation_state = DeviceAllocationState.available def __init__(self): - self.error_message = "" self.device_sn = "" self.label = "" self.device_connect_type = "" self.device_kernel = "" - self.remote_device = None - self.local_device = None + self.device = None + self.ifconfig = None + self.extend_value = {} + self.device_lock = threading.RLock() def __set_serial__(self, device=None): for item in device: - if "ip" in item.keys(): - self.device_sn = "remote_%s" % item.get("ip") + if "ip" in item.keys() and "port" in item.keys(): + self.device_sn = "remote_%s_%s" % \ + (item.get("ip"), item.get("port")) break - elif "type" in item.keys() and ComType.deploy_com == item.get( - "type"): + elif "type" in item.keys() and "com" in item.keys(): self.device_sn = "local_%s" % item.get("com") break def __get_serial__(self): return self.device_sn + def get(self, key=None, default=None): + if not key: + return default + value = getattr(self, key, None) + if value: + return value + else: + return self.extend_value.get(key, default) + def __set_device_kernel__(self, kernel_type=""): self.device_kernel = kernel_type def __get_device_kernel__(self): return self.device_kernel - def __check_watch__(self, device): + @staticmethod + def _check_watchgt(device): for item in device: if "label" not in item.keys(): if "com" not in item.keys() or ("com" in item.keys() and not item.get("com")): - self.error_message = "watch local com cannot be " \ - "empty, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "watchGT local com cannot be " \ + "empty, please check" + raise LiteParamError(error_message, error_no="00108") else: hdc = get_hdc_path() result = exec_cmd([hdc]) @@ -126,72 +169,89 @@ class DeviceLite(IDevice): if item.get("com").upper() in com_list: return True else: - self.error_message = "watch local com does not exist" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "watchGT local com does not exist" + raise LiteParamError(error_message, error_no="00108") - def __check_wifiiot_config__(self, device): + @staticmethod + def _check_wifiiot_config(device): com_type_set = set() for item in device: if "label" not in item.keys(): if "com" not in item.keys() or ("com" in item.keys() and not item.get("com")): - self.error_message = "wifiiot local com cannot be " \ - "empty, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "wifiiot local com cannot be " \ + "empty, please check" + raise LiteParamError(error_message, error_no="00108") if "type" not in item.keys() or ("type" not in item.keys() and not item.get("type")): - self.error_message = "wifiiot com type cannot be " \ - "empty, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "wifiiot com type cannot be " \ + "empty, please check" + raise LiteParamError(error_message, error_no="00108") else: com_type_set.add(item.get("type")) if len(com_type_set) < 2: - self.error_message = "wifiiot need cmd com and deploy com" \ - " at the same time, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "wifiiot need cmd com and deploy com" \ + " at the same time, please check" + raise LiteParamError(error_message, error_no="00108") - def __check_ipcamera_local__(self, device): + @staticmethod + def _check_ipcamera_local(device): for item in device: if "label" not in item.keys(): if "com" not in item.keys() or ("com" in item.keys() and not item.get("com")): - self.error_message = "ipcamera local com cannot be " \ - "empty, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "ipcamera local com cannot be " \ + "empty, please check" + raise LiteParamError(error_message, error_no="00108") + + @staticmethod + def _check_ipcamera_remote(device=None): + for item in device: + if "label" not in item.keys(): + if "port" in item.keys() and item.get("port") and not item.get( + "port").isnumeric(): + error_message = "ipcamera remote port should be " \ + "a number, please check" + raise LiteParamError(error_message, error_no="00108") + elif "port" not in item.keys(): + error_message = "ipcamera remote port cannot be" \ + " empty, please check" + raise LiteParamError(error_message, error_no="00108") - def __check_ipcamera_remote__(self, device=None): + @staticmethod + def _check_agent(device=None): for item in device: if "label" not in item.keys(): if "port" in item.keys() and item.get("port") and not item.get( "port").isnumeric(): - self.error_message = "ipcamera remote port should be " \ - "a number, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "agent port should be " \ + "a number, please check" + raise LiteParamError(error_message, error_no="00108") elif "port" not in item.keys(): - self.error_message = "ipcamera remote port cannot be" \ - " empty, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "ipcamera agent port cannot be" \ + " empty, please check" + raise LiteParamError(error_message, error_no="00108") def __check_config__(self, device=None): - self.set_connect_type(device) + if "agent" == device[0].get("type"): + self.device_connect_type = "agent" + self.label = device[0].get("label") + else: + self.set_connect_type(device) if self.label == DeviceLabelType.wifiiot: - self.__check_wifiiot_config__(device) + self._check_wifiiot_config(device) elif self.label == DeviceLabelType.ipcamera and \ self.device_connect_type == "local": - self.__check_ipcamera_local__(device) + self._check_ipcamera_local(device) elif self.label == DeviceLabelType.ipcamera and \ self.device_connect_type == "remote": - self.__check_ipcamera_remote__(device) - elif self.label == DeviceLabelType.watch: - self.__check_watch__(device) + self._check_ipcamera_remote(device) + elif self.label == DeviceLabelType.watch_gt: + self._check_watchgt(device) + elif self.label == DeviceLabelType.ipcamera and \ + self.device_connect_type == "agent": + self._check_agent(device) def set_connect_type(self, device): pattern = r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[' \ @@ -205,95 +265,133 @@ class DeviceLite(IDevice): if re.match(pattern, item.get("ip")): self.device_connect_type = "remote" else: - self.error_message = "Remote device ip not in right" \ + error_message = "Remote device ip not in right" \ "format, please check user_config.xml" - LOG.error(self.error_message) - raise ParamError(self.error_message) + raise LiteParamError(error_message, error_no="00108") if not self.label: - self.error_message = "device label cannot be empty, " \ - "please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + error_message = "device label cannot be empty, " \ + "please check" + raise LiteParamError(error_message, error_no="00108") else: - if self.label != DeviceLabelType.wifiiot and self.label != \ - DeviceLabelType.ipcamera and self.label != \ - DeviceLabelType.watch: - self.error_message = "device label should be ipcamera or" \ + if self.label != DeviceLabelType.wifiiot and \ + self.label != DeviceLabelType.ipcamera and \ + self.label != DeviceLabelType.watch_gt: + error_message = "device label should be ipcamera or" \ " wifiiot, please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) + raise LiteParamError(error_message, error_no="00108") if not self.device_connect_type: - self.error_message = "device com or ip cannot be empty, " \ + error_message = "device com or ip cannot be empty, " \ "please check" - LOG.error(self.error_message) - raise ParamError(self.error_message) - - def __init_device__(self, device=None): - if not device: - LOG.error( - "no available device, please config it in lite_config.xml.") - raise DeviceError( - "no available device, please config it in lite_config.xml.") + raise LiteParamError(error_message, error_no="00108") + def __init_device__(self, device): self.__check_config__(device) self.__set_serial__(device) if self.device_connect_type == "remote": - self.remote_device = RemoteController(device) + self.device = CONTROLLER_DICT.get("remote")(device) + elif self.device_connect_type == "agent": + self.device = CONTROLLER_DICT.get("agent")(device) else: - self.local_device = LocalController(device) + self.device = CONTROLLER_DICT.get("local")(device) + + self.ifconfig = device[1].get("ifconfig") def connect(self): """ connect the device """ - if self.device_connect_type == "remote": - self.remote_device.connect() - else: - self.local_device.connect() - + try: + self.device.connect() + except LiteDeviceConnectError: + if check_mode(ModeType.decc): + LOG.debug("set device %s recover state to false" % + self.device_sn) + self.device_allocation_state = DeviceAllocationState.unusable + self.set_recover_state(False) + raise + + @perform_device_action def execute_command_with_timeout(self, command="", case_type="", - timeout=TIMEOUT, receiver=None): - """ - Executes command on the device. + timeout=TIMEOUT, **kwargs): + """Executes command on the device. - Parameters: + Args: command: the command to execute case_type: CTest or CppTest - receiver: parser handler timeout: timeout for read result + **kwargs: receiver - parser handler input + + Returns: + (filter_result, status, error_message) + + filter_result: command execution result + status: true or false + error_message: command execution error message """ - try: - if self.device_connect_type == "remote": - filter_result, status, error_message = \ - self.remote_device.execute_command_with_timeout( - command=command, - timeout=timeout, - receiver=receiver) - else: - filter_result, status, error_message = \ - self.local_device.execute_command_with_timeout( - command=command, - case_type=case_type, - timeout=timeout, - receiver=receiver) + receiver = kwargs.get("receiver", None) + if self.device_connect_type == "remote": + LOG.info("%s execute command shell %s with timeout %ss" % + (convert_serial(self.__get_serial__()), command, + str(timeout))) + filter_result, status, error_message = \ + self.device.execute_command_with_timeout( + command=command, + timeout=timeout, + receiver=receiver) + elif self.device_connect_type == "agent": + filter_result, status, error_message = \ + self.device.execute_command_with_timeout( + command=command, + case_type=case_type, + timeout=timeout, + receiver=receiver, type="cmd") + else: + filter_result, status, error_message = \ + self.device.execute_command_with_timeout( + command=command, + case_type=case_type, + timeout=timeout, + receiver=receiver) + if not receiver: LOG.debug("execute result:%s", filter_result) - if not status: - LOG.debug("error_message:%s", error_message) - return filter_result, status, error_message - except ExecuteTerminate: - error_message = "Execute terminate." - return "", False, error_message + if not status: + LOG.debug("error_message:%s", error_message) + return filter_result, status, error_message + + def recover_device(self): + self.reboot() + + def reboot(self): + self.connect() + filter_result, status, error_message = self. \ + execute_command_with_timeout(command="reset", timeout=30) + if not filter_result: + if check_mode(ModeType.decc): + LOG.debug("set device %s recover state to false" % + self.device_sn) + self.device_allocation_state = DeviceAllocationState.unusable + self.set_recover_state(False) + + if self.ifconfig: + self.execute_command_with_timeout(command=self.ifconfig, + timeout=60) def close(self): """ - Unmount the testcase from device server and close the telnet connection - with device server or close the local serial + Close the telnet connection with device server or close the local + serial """ - if self.device_connect_type == "remote": - return self.remote_device.close() - else: - return self.local_device.close() + self.device.close() + + def set_recover_state(self, state): + with self.device_lock: + setattr(self, ConfigConst.recover_state, state) + + def get_recover_state(self, default_state=True): + with self.device_lock: + state = getattr(self, ConfigConst.recover_state, default_state) + return state class RemoteController: @@ -306,7 +404,6 @@ class RemoteController: def __init__(self, device): self.host = device[1].get("ip") self.port = int(device[1].get("port")) - self.directory = device[1].get("dir") self.telnet = None def connect(self): @@ -314,17 +411,16 @@ class RemoteController: connect the device server """ - now_time = time.strftime('%Y-%m-%d %H:%M:%S', - time.localtime(time.time())) try: if self.telnet: return self.telnet self.telnet = telnetlib.Telnet(self.host, self.port, timeout=TIMEOUT) except Exception as err_msgs: - LOG.error('TIME: %s IP: %s STATUS: telnet failed\n error:%s' % ( - now_time, self.host, str(err_msgs))) - raise LiteDeviceConnectError("connect lite_device failed") + error_message = "Connect remote lite device failed, host is %s, " \ + "port is %s, error is %s" % \ + (convert_ip(self.host), self.port, str(err_msgs)) + raise LiteDeviceConnectError(error_message, error_no="00401") time.sleep(2) self.telnet.set_debuglevel(0) return self.telnet @@ -344,10 +440,8 @@ class RemoteController: def close(self): """ - Unmount the testcase directory from device server and close the telnet - connection with device server + Close the telnet connection with device server """ - error_message = "" try: if not self.telnet: return @@ -355,8 +449,7 @@ class RemoteController: self.telnet = None except (ConnectionError, Exception): error_message = "Remote device is disconnected abnormally" - LOG.error(error_message) - return error_message + LOG.error(error_message, error_no="00401") class LocalController: @@ -380,13 +473,7 @@ class LocalController: """ Open serial. """ - try: - self.com_dict.get(key).connect() - except Exception: - raise LiteDeviceConnectError("connect serial failed") - - def flush_input(self, key=ComType.cmd_com): - self.com_dict.get(key).flush_input() + self.com_dict.get(key).connect() def close(self, key=ComType.cmd_com): """ @@ -417,14 +504,13 @@ class ComController: Parameters: device: local com """ - self.serial_port = device.get("com") if device.get("com") else None - self.baund_rate = int(device.get("baund_rate")) if device.get( - "baund_rate") else 115200 self.is_open = False + self.com = None + self.serial_port = device.get("com") if device.get("com") else None + self.baud_rate = int(device.get("baud_rate")) if device.get( + "baud_rate") else 115200 self.timeout = int(device.get("timeout")) if device.get( "timeout") else TIMEOUT - self.directory = device.get("dir") if device.get("dir") else None - self.com = None def connect(self): """ @@ -434,23 +520,19 @@ class ComController: if not self.is_open: import serial self.com = serial.Serial(self.serial_port, - baudrate=self.baund_rate, + baudrate=self.baud_rate, timeout=self.timeout) self.is_open = True - except Exception: - LOG.error( - "connect %s serial failed, please make sure this port is not " - "occupied." % self.serial_port) - raise LiteDeviceConnectError("connect serial failed") - - def flush_input(self): - self.com.flushInput() + except Exception as error_msg: + error = "connect %s serial failed, please make sure this port is" \ + " not occupied, error is %s[00401]" % \ + (self.serial_port, str(error_msg)) + raise LiteDeviceConnectError(error, error_no="00401") def close(self): """ Close serial. """ - error_message = "" try: if not self.com: return @@ -459,8 +541,7 @@ class ComController: self.is_open = False except (ConnectionError, Exception): error_message = "Local device is disconnected abnormally" - LOG.error(error_message) - return error_message + LOG.error(error_message, error_no="00401") def execute_command_with_timeout(self, **kwargs): """ @@ -473,3 +554,51 @@ class ComController: Execute command on the serial and read all the output from the serial. """ LiteHelper.execute_local_command(self.com, command) + + +class AgentController: + def __init__(self, device): + """ + Init serial. + Parameters: + device: local com + """ + LOG.info("AgentController begin") + self.com_dict = {} + self.host = device[1].get("ip") + self.port = str(device[1].get("port")) + self.headers = {'Content-Type': 'application/json'} + self.local_source_dir = "" + self.target_save_path = "" + self.base_url = "http" + "://" + self.host + ":" + self.port + + def connect(self): + """ + Open serial. + """ + + def close(self): + """ + Close serial. + """ + pass + + def execute_command_with_timeout(self, command, **kwargs): + """ + Executes command on the device. + + Parameters: + command: the command to execute + timeout: timeout for read result + receiver: parser handler + """ + return LiteHelper.execute_agent_cmd_with_timeout(self, + command, + **kwargs) + + +CONTROLLER_DICT = { + "local": LocalController, + "remote": RemoteController, + "agent": AgentController +} diff --git a/src/xdevice/_core/environment/dmlib_lite.py b/src/xdevice/_core/environment/dmlib_lite.py index 51c796fe0123978634fcfa0a6c9b2f8c3e4bbe9c..06298e5b4f0b144d8c4d72c2c775d8fc649ff356 100755 --- a/src/xdevice/_core/environment/dmlib_lite.py +++ b/src/xdevice/_core/environment/dmlib_lite.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,47 +16,74 @@ # limitations under the License. # +import json import time import re from _core.constants import DeviceTestType from _core.exception import ExecuteTerminate +from _core.exception import LiteDeviceTimeout from _core.exception import LiteDeviceConnectError from _core.exception import LiteDeviceExecuteCommandError from _core.logger import platform_logger - +from _core.utils import convert_port __all__ = ["generate_report", "LiteHelper"] CPP_TEST_STANDARD_SIGN = "[==========]" -CPP_TEST_END_SIGN = "Global test environment tear-down" -CPP_SYS_STANDARD_SIGN = " #" +CPP_TEST_END_SIGN = "Gtest xml output finished" +CPP_SYS_STANDARD_SIGN = "OHOS #" CPP_ERR_MESSAGE = "[ERR]No such file or directory: " - -TIMEOUT = 90 CTEST_STANDARD_SIGN = "Start to run test suite" AT_CMD_ENDS = "OK" -CTEST_END_SIGN = "Framework finished." +CTEST_END_SIGN = "All the test suites finished" + +_START_JSUNIT_RUN_MARKER = "[start] start run suites" +_END_JSUNIT_RUN_MARKER = "[end] run suites end" +INSTALL_END_MARKER = "resultMessage is install success !" + PATTERN = re.compile(r'\x1B(\[([0-9]{1,2}(;[0-9]{1,2})*)?m)*') +TIMEOUT = 90 LOG = platform_logger("DmlibLite") +def check_open_source_test(result_output): + if result_output.find(CPP_TEST_STANDARD_SIGN) == -1 and \ + ("test pass" in result_output.lower() or + "test fail" in result_output.lower() or + "tests pass" in result_output.lower() or + "tests fail" in result_output.lower()): + return True + return False + + def check_read_test_end(result=None, input_command=None): - result_output = result.replace(input_command, "") + temp_result = result.replace("\n", "") + if input_command not in temp_result: + return False + index = result.find(input_command) + len(input_command) + result_output = result[index:] if input_command.startswith("./"): if result_output.find(CPP_TEST_STANDARD_SIGN) != -1: if result_output.count(CPP_TEST_STANDARD_SIGN) == 2 or \ result_output.find(CPP_TEST_END_SIGN) != -1: return True - if result_output.find(CPP_TEST_STANDARD_SIGN) == -1 and \ - ("test pass" in result_output.lower() or - "test fail" in result_output.lower()): + if check_open_source_test(result_output): return True + if result_output.find(_START_JSUNIT_RUN_MARKER) >= 1 and \ + result_output.find(_END_JSUNIT_RUN_MARKER) >= 1: + return True + + if result_output.find(INSTALL_END_MARKER) != -1: + return True + if "%s%s" % (CPP_ERR_MESSAGE, input_command[2:]) in result_output: - LOG.error("execute file not exist, result is %s" % result_output) - raise LiteDeviceExecuteCommandError("execute file not exist") + LOG.error("execute file not exist, result is %s" % result_output, + error_no="00402") + raise LiteDeviceExecuteCommandError("execute file not exist", + error_no="00402") else: - if " #" in result_output: + if "OHOS #" in result_output or "Linux" in result_output: if input_command == "reboot" or input_command == "reset": return False if input_command.startswith("mount"): @@ -100,37 +127,43 @@ class LiteHelper: status = True error_message = "" result = "" - LOG.info("execute command shell %s with timeout %s" % - (command, str(timeout))) if not telnet: - raise LiteDeviceConnectError("remote device is not connected.") + raise LiteDeviceConnectError("remote device is not connected.", + error_no="00402") telnet.write(command.encode('ascii') + b"\n") + while time.time() - start_time < timeout: + data = telnet.read_until(bytes(command, encoding="utf8"), + timeout=1) + data = PATTERN.sub('', data.decode('gbk', 'ignore')).replace( + "\r", "") + result = "{}{}".format(result, data) + if command in result: + break expect_result = [bytes(CPP_TEST_STANDARD_SIGN, encoding="utf8"), bytes(CPP_SYS_STANDARD_SIGN, encoding="utf8"), bytes(CPP_TEST_END_SIGN, encoding="utf8")] - while time.time()-start_time < timeout: + while time.time() - start_time < timeout: if not Scheduler.is_execute: - raise ExecuteTerminate() + raise ExecuteTerminate("Execute terminate", error_no="00300") _, _, data = telnet.expect(expect_result, timeout=1) - data = PATTERN.sub('', data.decode('gbk', 'ignore')) - result = result + data.replace("\r", "") - + data = PATTERN.sub('', data.decode('gbk', 'ignore')).replace( + "\r", "") + result = "{}{}".format(result, data) + if receiver and data: + receiver.__read__(data) if check_read_test_end(result, command): break else: - error_message = "execute command with timeout=%s " % command - LOG.debug("error_message=%s" % error_message) + error_message = "execute %s timed out %s " % (command, timeout) status = False if receiver: - if command in result: - index = result.find(command) + len(command) - result_output = result[index:] - else: - result_output = result - generate_report(receiver, result_output) + receiver.__done__() + + if not status and command.startswith("uname"): + raise LiteDeviceTimeout("Execute command time out:%s" % command) return result, status, error_message @@ -138,34 +171,43 @@ class LiteHelper: def read_local_output_test(com=None, command=None, timeout=TIMEOUT, receiver=None): input_command = command + linux_end_command = "" + if "--gtest_output=" in command: + linux_end_command = input_command.split(":")[1].split( + "reports")[0].rstrip("/") + " #" error_message = "" start_time = time.time() result = "" status = True from xdevice import Scheduler - while time.time()-start_time < timeout: + while time.time() - start_time < timeout: + data = com.readline().decode('gbk', errors='ignore') + data = PATTERN.sub('', data).replace("\r", "") + result = "{}{}".format(result, data) + if command in result or linux_end_command in result: + break + + while time.time() - start_time < timeout: if not Scheduler.is_execute: - raise ExecuteTerminate() + raise ExecuteTerminate("Execute terminate", error_no="00300") data = com.readline().decode('gbk', errors='ignore') - data = PATTERN.sub('', data) - result = "{}{}".format(result, data.replace("\r", "")) + data = PATTERN.sub('', data).replace("\r", "") + result = "{}{}".format(result, data) + if receiver and data: + receiver.__read__(data) if check_read_test_end(result, input_command): break else: - error_message = "execute command with timeout=%s " % command - LOG.debug("error_message:%s" % error_message) + error_message = "execute %s timed out %s " % (command, timeout) status = False if receiver: - if command in result: - index = result.find(command) + len(command) - result_output = result[index:] - else: - result_output = result - generate_report(receiver, result_output) + receiver.__done__() + + if not status and command.startswith("uname"): + raise LiteDeviceTimeout("Execute command time out:%s" % command) - LOG.info('Info: execute command success') return result, status, error_message @staticmethod @@ -178,17 +220,21 @@ class LiteHelper: from xdevice import Scheduler while True: if not Scheduler.is_execute: - raise ExecuteTerminate() + raise ExecuteTerminate("Execute terminate", error_no="00300") data = com.readline().decode('gbk', errors='ignore') data = PATTERN.sub('', data) if isinstance(input_command, list): data = "{} {}".format(get_current_time(), data) + if data and receiver: + receiver.__read__(data.replace("\r", "")) result = "{}{}".format(result, data.replace("\r", "")) if re.search(r"\d+\s+Tests\s+\d+\s+Failures\s+\d+\s+" r"Ignored", data): start = time.time() if (int(time.time()) - int(start)) > timeout: break + if CTEST_END_SIGN in data: + break else: result = "{}{}".format( result, data.replace("\r", "").replace("\n", "").strip()) @@ -197,11 +243,8 @@ class LiteHelper: if (int(time.time()) - int(start)) > timeout: return result, False, "" - result = PATTERN.sub('', result) if receiver: - result = "{}{}".format( - result, "{} {}".format(get_current_time(), CTEST_END_SIGN)) - generate_report(receiver, result) + receiver.__done__() LOG.info('Info: execute command success') return result, True, "" @@ -227,10 +270,12 @@ class LiteHelper: timeout = args.get("timeout", TIMEOUT) receiver = args.get("receiver", None) if not com: - raise LiteDeviceConnectError("local device is not connected.") + raise LiteDeviceConnectError("local device is not connected.", + error_no="00402") + + LOG.info("local_%s execute command shell %s with timeout %ss" % + (convert_port(com.port), command, str(timeout))) - LOG.info("%s \n execute command shell %s with timeout %s" % - (com, command, str(timeout))) if isinstance(command, str): command = command.encode("utf-8") if command[-2:] != b"\r\n": @@ -248,11 +293,92 @@ class LiteHelper: Execute command on the serial and read all the output from the serial. """ if not com: - raise LiteDeviceConnectError("local device is not connected.") + raise LiteDeviceConnectError("local device is not connected.", + error_no="00402") LOG.info( - "%s \n execute command shell %s" % (com, command)) + "local_%s execute command shell %s" % (convert_port(com.port), + command)) command = command.encode("utf-8") if command[-2:] != b"\r\n": command = command.rstrip() + b'\r\n' com.write(command) + + @staticmethod + def execute_agent_cmd_with_timeout(self, command, + **kwargs): + import requests + + base_url = self.base_url + headers = self.headers + local_source_dir = self.local_source_dir + target_save_path = self.target_save_path + args = kwargs + command_type = args.get("type", None) + sync = args.get("sync", 1) + response = None + try: + if "cmd" == command_type: + url = base_url + "/_processes/" + data = { + 'cmd': command, + 'sync': sync, + 'stdoutRedirect': 'file', + 'stderrRedirect': 'file' + } + response = requests.post(url=url, headers=headers, + data=json.dumps(data)) + if response.status_code == 200: + LOG.info("connect OK") + else: + LOG.info("connect failed") + response.close() + + elif "put" == command_type: + url = base_url + target_save_path + command + data = open(local_source_dir + command, "rb") + response = requests.put(url=url, headers=headers, data=data) + if response.status_code == 200: + LOG.info("{} upload file to {} " + "success".format(local_source_dir, + target_save_path)) + else: + LOG.info( + "{} upload file to {} fail".format(local_source_dir, + target_save_path)) + response.close() + elif "get" == command_type: + url = base_url + target_save_path + command + response = requests.get(url=url, headers=headers, stream=True) + if response.status_code == 200: + file_name = open(local_source_dir + command + "bak", "wb") + for file_data in response.iter_content(chunk_size=512): + file_name.write(file_data) + file_name.close() + LOG.info("from {} download file to {} success".format( + target_save_path + command, + local_source_dir)) + else: + LOG.info("{} download file to {} fail".format( + target_save_path + command, + command, + local_source_dir)) + elif "delete" == command_type: + url = base_url + target_save_path + command + LOG.info(url) + response = requests.delete(url) + if response.status_code == 200: + LOG.info("delete {} file success".format( + target_save_path + command)) + else: + LOG.info("delete {} file fail".format( + target_save_path + command)) + else: + LOG.info("type error") + except Exception as error_message: + LOG.info("error_message:{}".format(error_message)) + finally: + if response: + response.close() + + return "", True, "" diff --git a/src/xdevice/_core/environment/manager_env.py b/src/xdevice/_core/environment/manager_env.py index acc8f5100e63eba29ed73d01f1edd8a1d1098bd1..bf8cf476ad765135d8fc79b603ad7db78375c67a 100755 --- a/src/xdevice/_core/environment/manager_env.py +++ b/src/xdevice/_core/environment/manager_env.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,19 +16,17 @@ # limitations under the License. # -import sys from dataclasses import dataclass from _core.config.config_manager import UserConfigManager -from _core.exception import DeviceError -from _core.exception import ParamError from _core.logger import platform_logger +from _core.logger import change_logger_level from _core.plugin import Plugin from _core.plugin import get_plugin from _core.utils import convert_serial __all__ = ["EnvironmentManager", "DeviceSelectionOption", - "DeviceAllocationState"] + "DeviceAllocationState", "Environment"] LOG = platform_logger("ManagerEnv") @@ -61,7 +59,7 @@ class Environment(object): def add_device(self, device): if device.label == "phone": self.phone += 1 - device.device_id = "phone%s" % str(self.phone) + device.device_id = "Phone%s" % str(self.phone) self.devices.append(device) @@ -82,44 +80,39 @@ class EnvironmentManager(object): cls.__instance = super(EnvironmentManager, cls).__new__(cls) return cls.__instance - def __init__(self, environment=""): + def __init__(self, environment="", user_config_file=""): if EnvironmentManager.__init_flag: return - self.manager_device = None - self.manager_lite = None - self._get_device_manager(environment) + self.managers = {} + self.env_start(environment, user_config_file) EnvironmentManager.__init_flag = True - def _get_device_manager(self, environment=""): - try: - result = UserConfigManager(env=environment).get_user_config( - "environment/support_device") - except ParamError as error: - LOG.exception("ParamError: %s" % error.args, exc_info=False) - if not environment: - sys.exit(0) - else: - raise error - if result.get("device") == "true": - managers_device = get_plugin(plugin_type=Plugin.MANAGER, - plugin_id="device") - if managers_device: - self.manager_device = managers_device[0] - self.manager_device.init_environment(environment) - if result.get("device_lite") == "true": - managers_lite = get_plugin(plugin_type=Plugin.MANAGER, - plugin_id="device_lite") - if managers_lite: - self.manager_lite = managers_lite[0] - self.manager_lite.init_environment(environment) + def env_start(self, environment="", user_config_file=""): + + log_level_dict = UserConfigManager( + config_file=user_config_file, env=environment).get_log_level() + + if log_level_dict: + # change log level when load or reset EnvironmentManager object + change_logger_level(log_level_dict) + + manager_plugins = get_plugin(Plugin.MANAGER) + for manager_plugin in manager_plugins: + try: + manager_instance = manager_plugin.__class__() + manager_instance.init_environment(environment, + user_config_file) + self.managers[manager_instance.__class__.__name__] = \ + manager_instance + except Exception as error: + LOG.debug(error) def env_stop(self): - if self.manager_device: - self.manager_device.env_stop() - self.manager_device = None - if self.manager_lite: - self.manager_lite.devices_list = [] - self.manager_lite = None + for manager in self.managers.values(): + manager.env_stop() + manager.devices_list = [] + self.managers = {} + EnvironmentManager.__init_flag = False def apply_environment(self, device_options): @@ -128,61 +121,72 @@ class EnvironmentManager(object): device = self.apply_device(device_option) if device is not None: environment.add_device(device) + device.extend_value = device_option.extend_value + LOG.debug("device %s: extend value: %s", device.device_sn, + device.extend_value) return environment def release_environment(self, environment): for device in environment.devices: + device.extend_value = {} self.release_device(device) def apply_device(self, device_option, timeout=10): - - if not device_option.label or device_option.label == "phone": - device_manager = self.manager_device - else: - device_manager = self.manager_lite - - if device_manager: - return device_manager.apply_device(device_option, timeout) + for manager_type, manager in self.managers.items(): + support_labels = getattr(manager, "support_labels", []) + if not support_labels: + continue + if device_option.label is None: + if manager_type != "ManagerDevice": + continue + else: + if support_labels and \ + device_option.label not in support_labels: + continue + device = manager.apply_device(device_option, timeout) + if device: + return device else: - raise DeviceError( - "%s is not supported, please check %s and " - "environment config user_config.xml" % - (str(device_option.test_driver), device_option.source_file)) + return None def check_device_exist(self, device_options): """ Check if there are matched devices which can be allocated or available. """ + devices = [] for device_option in device_options: - device_exist = False - if self.manager_device: - for device in self.manager_device.devices_list: - if device_option.matches(device, False): - device_exist = True - if self.manager_lite: - for device in self.manager_lite.devices_list: + for manager_type, manager in self.managers.items(): + support_labels = getattr(manager, "support_labels", []) + if device_option.label is None: + if manager_type != "ManagerDevice": + continue + else: + if support_labels and \ + device_option.label not in support_labels: + continue + for device in manager.devices_list: + if device.device_sn in devices: + continue if device_option.matches(device, False): - device_exist = True - if not device_exist: + devices.append(device.device_sn) + break + else: + continue + break + else: return False - return True def release_device(self, device): - if self.manager_device and \ - device in self.manager_device.devices_list: - self.manager_device.release_device(device) - elif self.manager_lite and \ - device in self.manager_lite.devices_list: - self.manager_lite.release_device(device) + for manager in self.managers.values(): + if device in manager.devices_list: + manager.release_device(device) def list_devices(self): LOG.info("list devices.") - if self.manager_device: - self.manager_device.list_devices() - if self.manager_lite: - self.manager_lite.list_devices() + for manager in self.managers.values(): + manager.list_devices() class DeviceSelectionOption(object): @@ -195,18 +199,24 @@ class DeviceSelectionOption(object): self.label = label self.test_driver = test_source.test_type self.source_file = "" + self.extend_value = {} def get_label(self): return self.label def matches(self, device, allocate=True): + if not getattr(device, "task_state", True): + return False if allocate and device.device_allocation_state != \ DeviceAllocationState.available: return False - if not allocate and device.device_allocation_state == \ - DeviceAllocationState.ignored: - return False + if not allocate: + if device.device_allocation_state != \ + DeviceAllocationState.available and \ + device.device_allocation_state != \ + DeviceAllocationState.allocated: + return False if len(self.device_sn) != 0 and device.device_sn not in self.device_sn: return False @@ -222,3 +232,4 @@ class DeviceAllocationState: ignored = "Ignored" available = "Available" allocated = "Allocated" + unusable = "Unusable" diff --git a/src/xdevice/_core/environment/manager_lite.py b/src/xdevice/_core/environment/manager_lite.py index 926113eebf79cedc974cb2f45648699b16274d61..065a7e069dfc6bced6fdd0dd15037bc55b86e51a 100755 --- a/src/xdevice/_core/environment/manager_lite.py +++ b/src/xdevice/_core/environment/manager_lite.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,17 +17,19 @@ # import time +import threading from _core.config.config_manager import UserConfigManager from _core.constants import DeviceOsType from _core.constants import ManagerType from _core.environment.manager_env import DeviceAllocationState -from _core.exception import LiteDeviceConnectError -from _core.exception import ParamError +from _core.exception import LiteDeviceError from _core.plugin import Plugin from _core.plugin import get_plugin from _core.interface import IDeviceManager from _core.logger import platform_logger +from _core.utils import convert_ip +from _core.utils import convert_port __all__ = ["ManagerLite"] @@ -43,12 +45,15 @@ class ManagerLite(IDeviceManager): def __init__(self): self.devices_list = [] + self.list_con = threading.Condition() + self.support_labels = ["ipcamera", "wifiiot", "watchGT"] - def init_environment(self, environment=""): + def init_environment(self, environment="", user_config_file=""): device_lite = get_plugin(plugin_type=Plugin.DEVICE, plugin_id=DeviceOsType.lite)[0] - devices = UserConfigManager(env=environment).get_com_device( + devices = UserConfigManager( + config_file=user_config_file, env=environment).get_com_device( "environment/device") for device in devices: @@ -57,33 +62,50 @@ class ManagerLite(IDeviceManager): device_lite_instance.__init_device__(device) device_lite_instance.device_allocation_state = \ DeviceAllocationState.available - except (LiteDeviceConnectError, ParamError): + except LiteDeviceError as exception: + LOG.warning(exception) continue self.devices_list.append(device_lite_instance) + def env_stop(self): + pass + def apply_device(self, device_option, timeout=10): """ Request a device for testing that meets certain criteria. """ del timeout - allocated_device = None - - for device in self.devices_list: - if device_option.matches(device): + LOG.debug("lite apply_device: apply lock") + self.list_con.acquire() + try: + allocated_device = None + for device in self.devices_list: + if device_option.matches(device): + device.device_allocation_state = \ + DeviceAllocationState.allocated + LOG.debug("allocate device sn: %s, type: %s" % ( + device.__get_serial__(), device.__class__)) + return device + time.sleep(10) + return allocated_device + finally: + LOG.debug("lite apply_device: release lock") + self.list_con.release() + + def release_device(self, device): + LOG.debug("lite release_device: apply lock") + self.list_con.acquire() + try: + if device.device_allocation_state == \ + DeviceAllocationState.allocated: device.device_allocation_state = \ - DeviceAllocationState.allocated - LOG.debug("allocate device sn: %s, type: %s" % ( - device.__get_serial__(), device.__class__)) - return device - time.sleep(10) - return allocated_device - - @staticmethod - def release_device(device): - device.device_allocation_state = DeviceAllocationState.available - LOG.debug("free device sn: %s, type: %s" % ( - device.__get_serial__(), device.__class__)) + DeviceAllocationState.available + LOG.debug("free device sn: %s, type: %s" % ( + device.__get_serial__(), device.__class__)) + finally: + LOG.debug("lite release_device: release lock") + self.list_con.release() def list_devices(self): print("lite devices:") @@ -91,21 +113,22 @@ class ManagerLite(IDeviceManager): format("SerialPort/IP", "Baudrate/Port", "OsType", "Allocation", "Product", "ConnectType", "ComType")) for device in self.devices_list: - if device.device_connect_type == "remote": + if device.device_connect_type == "remote" or \ + device.device_connect_type == "agent": print("{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}".format( - device.remote_device.host, - device.remote_device.port, + convert_ip(device.device.host), + convert_port(device.device.port), device.device_os_type, device.device_allocation_state, device.label, device.device_connect_type)) else: - for com_controller in device.local_device.com_dict: + for com_controller in device.device.com_dict: print("{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}". - format(device.local_device.com_dict[ - com_controller].serial_port, - device.local_device.com_dict[ - com_controller].baund_rate, + format(convert_port(device.device.com_dict[ + com_controller].serial_port), + device.device.com_dict[ + com_controller].baud_rate, device.device_os_type, device.device_allocation_state, device.label, diff --git a/src/xdevice/_core/exception.py b/src/xdevice/_core/exception.py index 46bdfcaae4089c0f77903e6521a953627058ffdd..3b0ec55ab6e9a5ccf1a7aa2bd6bad0d31a62f416 100755 --- a/src/xdevice/_core/exception.py +++ b/src/xdevice/_core/exception.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -18,36 +18,40 @@ class ParamError(Exception): - def __init__(self, error_msg): - super(ParamError, self).__init__(error_msg) + def __init__(self, error_msg, error_no=""): + super(ParamError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) class LiteDeviceError(Exception): - def __init__(self, error_msg): - super(LiteDeviceError, self).__init__(error_msg) + def __init__(self, error_msg, error_no=""): + super(LiteDeviceError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) class DeviceError(Exception): - def __init__(self, error_msg): - super(DeviceError, self).__init__(error_msg) + def __init__(self, error_msg, error_no=""): + super(DeviceError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) class ExecuteTerminate(Exception): - def __init__(self, error_msg="ExecuteTerminate"): - super(ExecuteTerminate, self).__init__(error_msg) + def __init__(self, error_msg="ExecuteTerminate", error_no=""): + super(ExecuteTerminate, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) @@ -58,115 +62,72 @@ class ReportException(Exception): Exception thrown when a shell command executed on a device takes too long to send its output. """ - def __init__(self, error_msg="ReportException"): - super(ReportException, self).__init__(error_msg) + def __init__(self, error_msg="ReportException", error_no=""): + super(ReportException, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) -class LiteDeviceConnectError(LiteDeviceError): - def __init__(self, error_msg): - super(LiteDeviceConnectError, self).__init__(error_msg) - self.error_msg = error_msg - - def __str__(self): - return str(self.error_msg) - - -class LiteDeviceMountError(LiteDeviceError): - def __init__(self, error_msg): - super(LiteDeviceMountError, self).__init__(error_msg) - self.error_msg = error_msg - - def __str__(self): - return str(self.error_msg) - - -class LiteDeviceReadOutputError(LiteDeviceError): - def __init__(self, error_msg): - super(LiteDeviceReadOutputError, self).__init__(error_msg) - self.error_msg = error_msg - - def __str__(self): - return str(self.error_msg) - - -class LiteDeviceExecuteCommandError(LiteDeviceError): - def __init__(self, error_msg): - super(LiteDeviceExecuteCommandError, self).__init__(error_msg) +class LiteParamError(LiteDeviceError): + def __init__(self, error_msg, error_no=""): + super(LiteParamError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) -class HdcCommandRejectedException(DeviceError): - """ - Exception thrown when hdc refuses a command. - """ - - def __init__(self, error_msg): - super(HdcCommandRejectedException, self).__init__(error_msg) - self.error_msg = error_msg - - def __str__(self): - return str(self.error_msg) - - -class ShellCommandUnresponsiveException(DeviceError): - """ - Exception thrown when a shell command executed on a device takes too long - to send its output. - """ - def __init__(self, error_msg="ShellCommandUnresponsiveException"): - super(ShellCommandUnresponsiveException, self).__init__(error_msg) +class LiteDeviceConnectError(LiteDeviceError): + def __init__(self, error_msg, error_no=""): + super(LiteDeviceConnectError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) -class DeviceUnresponsiveException(DeviceError): - """ - Exception thrown when a shell command executed on a device takes too long - to send its output. - """ - def __init__(self, error_msg="DeviceUnresponsiveException"): - super(DeviceUnresponsiveException, self).__init__(error_msg) +class LiteDeviceMountError(LiteDeviceError): + def __init__(self, error_msg, error_no=""): + super(LiteDeviceMountError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) -class HdcError(DeviceError): - """ - Raised when there is an error in hdc operations. - """ - - def __init__(self, error_msg): - super(HdcError, self).__init__(error_msg) +class LiteDeviceReadOutputError(LiteDeviceError): + def __init__(self, error_msg, error_no=""): + super(LiteDeviceReadOutputError, self).__init__(error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) -class ApkInstallError(DeviceError): - def __init__(self, error_msg): - super(ApkInstallError, self).__init__(error_msg) +class LiteDeviceExecuteCommandError(LiteDeviceError): + def __init__(self, error_msg, error_no=""): + super(LiteDeviceExecuteCommandError, self).__init__( + error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): return str(self.error_msg) -class HapNotSupportTest(DeviceError): - def __init__(self, error_msg): - super(HapNotSupportTest, self).__init__(error_msg) +class LiteDeviceTimeout(LiteDeviceError): + def __init__(self, error_msg, error_no=""): + super(LiteDeviceTimeout, self).__init__( + error_msg, error_no) self.error_msg = error_msg + self.error_no = error_no def __str__(self): - return str(self.error_msg) + return str(self.error_msg) \ No newline at end of file diff --git a/src/xdevice/_core/executor/__init__.py b/src/xdevice/_core/executor/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/executor/concurrent.py b/src/xdevice/_core/executor/concurrent.py index 34f34d20df654ff91f51efbcdc4855604de73512..58a7977637edb8f3e42db80b83e6e13dd663af34 100755 --- a/src/xdevice/_core/executor/concurrent.py +++ b/src/xdevice/_core/executor/concurrent.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,6 +16,7 @@ # limitations under the License. # +import copy import os import shutil import threading @@ -23,10 +24,23 @@ import time from concurrent.futures import ThreadPoolExecutor from concurrent.futures import wait +from _core.constants import ModeType +from _core.constants import ConfigConst from _core.executor.request import Request from _core.logger import platform_logger from _core.plugin import Config from _core.utils import get_instance_name +from _core.utils import get_filename_extension +from _core.utils import check_mode +from _core.exception import ParamError +from _core.exception import ExecuteTerminate +from _core.exception import DeviceError +from _core.exception import LiteDeviceError +from _core.report.reporter_helper import VisionHelper +from _core.report.reporter_helper import ReportConstant +from _core.report.reporter_helper import DataHelper +from _core.report.reporter_helper import Suite +from _core.report.reporter_helper import Case LOG = platform_logger("Concurrent") @@ -73,6 +87,9 @@ class DriversThread(threading.Thread): def set_listeners(self, listeners): self.listeners = listeners + if self.environment is None: + return + for listener in listeners: listener.device_sn = self.environment.devices[0].device_sn @@ -82,6 +99,7 @@ class DriversThread(threading.Thread): def run(self): from xdevice import Scheduler LOG.debug("thread id: %s start" % self.thread_id) + start_time = time.time() execute_message = ExecuteMessage('', self.environment, self.test_driver, self.thread_id) driver, test = None, None @@ -98,135 +116,165 @@ class DriversThread(threading.Thread): self._do_task_setup(driver_request) # driver execute - driver.__check_config__(driver_request.config) + self.reset_device(driver_request.config) driver.__execute__(driver_request) except Exception as exception: - LOG.exception( - "device: %s, exception: %s" % ( - self.environment.__get_serial__(), exception.args)) + error_no = getattr(exception, "error_no", "00000") + if self.environment is None: + LOG.exception("exception: %s", exception, exc_info=False, + error_no=error_no) + else: + LOG.exception( + "device: %s, exception: %s" % ( + self.environment.__get_serial__(), exception), + exc_info=False, error_no=error_no) self.error_message = "{}: {}".format( - get_instance_name(exception), str(exception.args)) + get_instance_name(exception), str(exception)) finally: - if driver and test: - execute_result = driver.__result__() - if getattr(self.task.config, "history_report_path", ""): - execute_result = self._inherit_execute_result( - execute_result, test) - execute_message.set_result(execute_result) - - if self.error_message: - execute_message.set_state(ExecuteMessage.DEVICE_ERROR) - else: - execute_message.set_state(ExecuteMessage.DEVICE_FINISH) - LOG.debug("put thread %s result", self.thread_id) - self.message_queue.put(execute_message) + self._handle_finally(driver, execute_message, start_time, test) + + @staticmethod + def reset_device(config): + if getattr(config, "reboot_per_module", False): + for device in config.environment.devices: + device.reboot() + def _handle_finally(self, driver, execute_message, start_time, test): + from xdevice import Scheduler + # output execute time + end_time = time.time() + execute_time = VisionHelper.get_execute_time(int( + end_time - start_time)) + source_content = self.test_driver[1].source.source_file or \ + self.test_driver[1].source.source_string + LOG.info("Executed: %s, Execution Time: %s" % ( + source_content, execute_time)) + + # inherit history report under retry mode + if driver and test: + execute_result = driver.__result__() + LOG.debug("execute_result:%s" % execute_result) + if getattr(self.task.config, "history_report_path", ""): + execute_result = self._inherit_execute_result( + execute_result, test) + execute_message.set_result(execute_result) + + # set execute state + if self.error_message: + execute_message.set_state(ExecuteMessage.DEVICE_ERROR) + else: + execute_message.set_state(ExecuteMessage.DEVICE_FINISH) + + # free environment + if self.environment: LOG.debug("thread %s free environment", execute_message.get_thread_id()) - Scheduler.__free_environment__( - execute_message.get_environment()) - LOG.info("") + Scheduler.__free_environment__(execute_message.get_environment()) + + LOG.debug("put thread %s result", self.thread_id) + self.message_queue.put(execute_message) + LOG.info("") def _do_task_setup(self, driver_request): + if check_mode(ModeType.decc) or getattr( + driver_request.config, ConfigConst.check_device, False): + return + + if self.environment is None: + return + from xdevice import Scheduler for device in self.environment.devices: - if getattr(device, "need_kit_setup", True): - for kit in self.task.config.kits: - if not Scheduler.is_execute: - break + if not getattr(device, ConfigConst.need_kit_setup, True): + LOG.debug("device %s need kit setup is false" % device) + continue + + # do task setup for device + kits_copy = copy.deepcopy(self.task.config.kits) + setattr(device, ConfigConst.task_kits, kits_copy) + for kit in getattr(device, ConfigConst.task_kits, []): + if not Scheduler.is_execute: + break + try: kit.__setup__(device, request=driver_request) - setattr(device, "need_kit_setup", False) - - if getattr(driver_request, "product_params", "") and not getattr( - self.task, "product_params", ""): - product_params = getattr(driver_request, "product_params") - if not isinstance(product_params, dict): - LOG.warning("product params should be dict, %s", - product_params) + except (ParamError, ExecuteTerminate, DeviceError, + LiteDeviceError, ValueError, TypeError, + SyntaxError, AttributeError) as exception: + error_no = getattr(exception, "error_no", "00000") + LOG.exception( + "task setup device: %s, exception: %s" % ( + self.environment.__get_serial__(), + exception), exc_info=False, error_no=error_no) + LOG.debug("set device %s need kit setup to false" % device) + setattr(device, ConfigConst.need_kit_setup, False) + + # set product_info to self.task + if getattr(driver_request, ConfigConst.product_info, "") and not \ + getattr(self.task, ConfigConst.product_info, ""): + product_info = getattr(driver_request, ConfigConst.product_info) + if not isinstance(product_info, dict): + LOG.warning("product info should be dict, %s", + product_info) return - setattr(self.task, "product_params", product_params) + setattr(self.task, ConfigConst.product_info, product_info) def _get_driver_request(self, root_desc, execute_message): config = Config() - config.update(self.task.config.__dict__) + config.update(copy.deepcopy(self.task.config).__dict__) config.environment = self.environment if getattr(config, "history_report_path", ""): # modify config.testargs history_report_path = getattr(config, "history_report_path", "") + module_name = root_desc.source.module_name unpassed_test_params = self._get_unpassed_test_params( - history_report_path, root_desc) + history_report_path, module_name) if not unpassed_test_params: LOG.info("%s all test cases are passed, no need retry", - root_desc.source.test_name) + module_name) driver_request = Request(self.thread_id, root_desc, self.listeners, config) execute_message.set_request(driver_request) return None - test_args = getattr(config, "testargs", {}) - test_params = [] - for unpassed_test_param in unpassed_test_params: - if unpassed_test_param not in test_params: - test_params.append(unpassed_test_param) - test_args["test"] = test_params - if "class" in test_args.keys(): - test_args.pop("class") - setattr(config, "testargs", test_args) + if unpassed_test_params[0] != module_name and \ + unpassed_test_params[0] != str(module_name).split(".")[0]: + test_args = getattr(config, "testargs", {}) + test_params = [] + for unpassed_test_param in unpassed_test_params: + if unpassed_test_param not in test_params: + test_params.append(unpassed_test_param) + test_args["test"] = test_params + if "class" in test_args.keys(): + test_args.pop("class") + setattr(config, "testargs", test_args) for listener in self.listeners: - LOG.debug("thread id %s, listener %s" % ( - self.thread_id, listener)) + LOG.debug("thread id %s, listener %s" % (self.thread_id, listener)) driver_request = Request(self.thread_id, root_desc, self.listeners, config) execute_message.set_request(driver_request) return driver_request - def _get_unpassed_test_params(self, history_report_path, root_desc): - from xdevice import Scheduler - if Scheduler.mode == "decc": - return self._get_decc_unpassed_params(root_desc) - + @classmethod + def _get_unpassed_test_params(cls, history_report_path, module_name): unpassed_test_params = [] - # get target_report_file - history_report_file = "" - report_file_suffix = "%s.xml" % root_desc.source.test_name - for root_dir, _, files in os.walk(history_report_path): - for report_file in files: - if report_file.endswith(report_file_suffix): - history_report_file = os.path.abspath( - os.path.join(root_dir, report_file)) - break - if not history_report_file: + from _core.report.result_reporter import ResultReporter + params = ResultReporter.get_task_info_params(history_report_path) + if not params: return unpassed_test_params - # append unpassed_test_param - self._append_unpassed_test_param(history_report_file, - unpassed_test_params) + failed_list = params[3].get(module_name, []) + if not failed_list: + failed_list = params[3].get(str(module_name).split(".")[0], []) + unpassed_test_params.extend(failed_list) LOG.debug("get unpassed test params %s", unpassed_test_params) return unpassed_test_params - @classmethod - def _get_decc_unpassed_params(cls, root_desc): - from xdevice import SuiteReporter - from _core.report.reporter_helper import DataHelper - from _core.report.reporter_helper import ReportConstant - - if not SuiteReporter.get_report_result(): - LOG.warning("decc retry command no previous result") - return [] - summary_result = SuiteReporter.get_report_result()[0][1] - summary_element = DataHelper.parse_data_report(summary_result) - for child in summary_element: - if child.get(ReportConstant.module) == root_desc.source.test_name: - return [] - return [root_desc.source.test_name] - @classmethod def _append_unpassed_test_param(cls, history_report_file, unpassed_test_params): - from _core.report.reporter_helper import DataHelper - from _core.report.reporter_helper import Suite + testsuites_element = DataHelper.parse_data_report(history_report_file) for testsuite_element in testsuites_element: suite_name = testsuite_element.get("name", "") @@ -240,12 +288,8 @@ class DriversThread(threading.Thread): unpassed_test_params.append(unpassed_test_param) def _inherit_execute_result(self, execute_result, root_desc): - from xdevice import Scheduler - if Scheduler.mode == "decc": - return - - # get history execute result - execute_result_name = "%s.xml" % root_desc.source.test_name + module_name = root_desc.source.module_name + execute_result_name = "%s.xml" % module_name history_execute_result = self._get_history_execute_result( execute_result_name) if not history_execute_result: @@ -253,30 +297,56 @@ class DriversThread(threading.Thread): execute_result_name) return execute_result - if not os.path.exists(execute_result): - result_dir = os.path.join(self.task.config.report_path, "result") - os.makedirs(result_dir, exist_ok=True) - target_execute_result = os.path.join(result_dir, - execute_result_name) - shutil.copyfile(history_execute_result, target_execute_result) - LOG.info("copy %s to %s" % (history_execute_result, - target_execute_result)) - return target_execute_result + if not check_mode(ModeType.decc): + if not os.path.exists(execute_result): + result_dir = \ + os.path.join(self.task.config.report_path, "result") + os.makedirs(result_dir, exist_ok=True) + target_execute_result = os.path.join(result_dir, + execute_result_name) + shutil.copyfile(history_execute_result, target_execute_result) + LOG.info("copy %s to %s" % (history_execute_result, + target_execute_result)) + return target_execute_result + + real_execute_result = self._get_real_execute_result(execute_result) # inherit history execute result - from _core.report.reporter_helper import DataHelper - LOG.info("inherit history execute result: %s", history_execute_result) + testsuites_element = DataHelper.parse_data_report(real_execute_result) + if self._is_empty_report(testsuites_element): + if check_mode(ModeType.decc): + LOG.info("empty report no need to inherit history execute" + " result") + else: + LOG.info("empty report '%s' no need to inherit history execute" + " result", history_execute_result) + return execute_result + + real_history_execute_result = self._get_real_history_execute_result( + history_execute_result, module_name) + history_testsuites_element = DataHelper.parse_data_report( - history_execute_result) - testsuites_element = DataHelper.parse_data_report(execute_result) + real_history_execute_result) + if self._is_empty_report(history_testsuites_element): + LOG.info("history report '%s' is empty", history_execute_result) + return execute_result + if check_mode(ModeType.decc): + LOG.info("inherit history execute result") + else: + LOG.info("inherit history execute result: %s", + history_execute_result) self._inherit_element(history_testsuites_element, testsuites_element) - # generate inherit execute result - DataHelper.generate_report(testsuites_element, execute_result) + if check_mode(ModeType.decc): + from xdevice import SuiteReporter + SuiteReporter.append_report_result( + (execute_result, DataHelper.to_string(testsuites_element))) + else: + # generate inherit execute result + DataHelper.generate_report(testsuites_element, execute_result) return execute_result def _inherit_element(self, history_testsuites_element, testsuites_element): - from _core.report.reporter_helper import ReportConstant for history_testsuite_element in history_testsuites_element: history_testsuite_name = history_testsuite_element.get("name", "") target_testsuite_element = None @@ -309,7 +379,12 @@ class DriversThread(threading.Thread): testsuites_element.set(ReportConstant.tests, str(inherited_test)) def _get_history_execute_result(self, execute_result_name): - history_execute_result = "" + if execute_result_name.endswith(".xml"): + execute_result_name = execute_result_name[:-4] + history_execute_result = \ + self._get_data_report_from_record(execute_result_name) + if history_execute_result: + return history_execute_result for root_dir, _, files in os.walk( self.task.config.history_report_path): for result_file in files: @@ -320,8 +395,6 @@ class DriversThread(threading.Thread): @classmethod def _check_testcase_pass(cls, history_testcase_element): - from _core.report.reporter_helper import ReportConstant - from _core.report.reporter_helper import Case case = Case() case.result = history_testcase_element.get(ReportConstant.result, "") case.status = history_testcase_element.get(ReportConstant.status, "") @@ -334,6 +407,61 @@ class DriversThread(threading.Thread): return case.is_passed() + @classmethod + def _is_empty_report(cls, testsuites_element): + if len(testsuites_element) < 1: + return True + if len(testsuites_element) >= 2: + return False + + if int(testsuites_element[0].get(ReportConstant.unavailable, 0)) > 0: + return True + return False + + def _get_data_report_from_record(self, execute_result_name): + history_report_path = \ + getattr(self.task.config, "history_report_path", "") + if history_report_path: + from _core.report.result_reporter import ResultReporter + params = ResultReporter.get_task_info_params(history_report_path) + if params: + report_data_dict = dict(params[4]) + if execute_result_name in report_data_dict.keys(): + return report_data_dict.get(execute_result_name) + elif execute_result_name.split(".")[0] in \ + report_data_dict.keys(): + return report_data_dict.get( + execute_result_name.split(".")[0]) + return "" + + @classmethod + def _get_real_execute_result(cls, execute_result): + from xdevice import SuiteReporter + LOG.debug("get_real_execute_result length is: %s" % + len(SuiteReporter.get_report_result())) + if check_mode(ModeType.decc): + for suite_report, report_result in \ + SuiteReporter.get_report_result(): + if os.path.splitext(suite_report)[0] == \ + os.path.splitext(execute_result)[0]: + return report_result + return "" + else: + return execute_result + + @classmethod + def _get_real_history_execute_result(cls, history_execute_result, + module_name): + from xdevice import SuiteReporter + LOG.debug("get_real_history_execute_result: %s" % + SuiteReporter.history_report_result) + if check_mode(ModeType.decc): + virtual_report_path, report_result = SuiteReporter. \ + get_history_result_by_module(module_name) + return report_result + else: + return history_execute_result + class QueueMonitorThread(threading.Thread): diff --git a/src/xdevice/_core/executor/listener.py b/src/xdevice/_core/executor/listener.py index 02fb6e5c4259bdae6bc87b3a108211cd616e1286..e180b9e9b65d2d22780965f287e0004d056e38cf 100755 --- a/src/xdevice/_core/executor/listener.py +++ b/src/xdevice/_core/executor/listener.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,11 +21,12 @@ import uuid from dataclasses import dataclass from _core.plugin import Plugin +from _core.plugin import get_plugin from _core.constants import ListenerType +from _core.constants import TestType from _core.interface import LifeCycle from _core.interface import IListener from _core.logger import platform_logger -from _core.report.result_reporter import ResultReporter from _core.report.suite_reporter import SuiteReporter from _core.report.suite_reporter import ResultCode from _core.report.encrypt import check_pub_key_exist @@ -76,7 +77,7 @@ class SuitesResult: stacktrace = "" run_time = 0 is_completed = False - product_params = {} + product_info = {} @dataclass @@ -245,6 +246,10 @@ class ReportListener(IListener): else: return self.tests.get(self.current_test_id) + def _remove_current_test_result(self): + if self.current_test_id in self.tests: + del self.tests[self.current_test_id] + def __started__(self, lifecycle, test_result): if lifecycle == LifeCycle.TestSuites: suites = self._get_suite_result(test_result=test_result, @@ -300,10 +305,10 @@ class ReportListener(IListener): result_dir = os.path.join(self.report_path, "result") os.makedirs(result_dir, exist_ok=True) suites_name = kwargs.get("suites_name", "") - product_params = kwargs.get("product_params", "") + product_info = kwargs.get("product_info", "") suite_report = SuiteReporter(self.result, suites_name, result_dir, - product_params=product_params) + product_info=product_info) suite_report.generate_data_report() elif lifecycle == LifeCycle.TestCase: test = self._get_test_result(test_result=test_result, create=False) @@ -311,18 +316,21 @@ class ReportListener(IListener): test.stacktrace = test_result.stacktrace test.code = test_result.code elif lifecycle == LifeCycle.TestTask: - result_report = ResultReporter(report_path=self.report_path, - task_info=test_result) - result_report.generate_reports() + test_type = str(kwargs.get("test_type", TestType.all)) + reporter = get_plugin(plugin_type=Plugin.REPORTER, + plugin_id=test_type) + if not reporter: + reporter = get_plugin(plugin_type=Plugin.REPORTER, + plugin_id=TestType.all)[0] + else: + reporter = reporter[0] + reporter.__generate_reports__(self.report_path, + task_info=test_result) def __skipped__(self, lifecycle, test_result): - if lifecycle == LifeCycle.TestSuite: - suite = self._get_suite_result(test_result=test_result, - create=False) - suite.code = ResultCode.SKIPPED.value - elif lifecycle == LifeCycle.TestCase: - test = self._get_test_result(test_result=test_result, create=False) - test.code = ResultCode.SKIPPED.value + del test_result + if lifecycle == LifeCycle.TestCase: + self._remove_current_test_result() def __failed__(self, lifecycle, test_result): if lifecycle == LifeCycle.TestSuite: diff --git a/src/xdevice/_core/executor/request.py b/src/xdevice/_core/executor/request.py index 24e08319c5024de29303a74d3f2b005dbe125e08..ab5153a03485d689fa372f41ed4c9d0a302fc200 100755 --- a/src/xdevice/_core/executor/request.py +++ b/src/xdevice/_core/executor/request.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -19,6 +19,8 @@ import datetime import os +from _core.constants import ModeType +from _core.constants import ConfigConst from _core.exception import ParamError from _core.executor.source import TestSource from _core.logger import platform_logger @@ -68,20 +70,20 @@ class Task: def init(self, config): from xdevice import Variables + from xdevice import Scheduler start_time = datetime.datetime.now() LOG.debug("StartTime=%s" % start_time.strftime("%Y-%m-%d %H:%M:%S")) self.config.update(config.__dict__) - if config.reportpath == "": + if getattr(config, ConfigConst.report_path, "") == "": Variables.task_name = start_time.strftime('%Y-%m-%d-%H-%M-%S') else: - Variables.task_name = config.reportpath + Variables.task_name = config.report_path # create a report folder to store test report report_path = os.path.join(Variables.exec_dir, Variables.report_vars.report_dir, Variables.task_name) - LOG.info("Report path: %s", report_path) os.makedirs(report_path, exist_ok=True) self._check_report_path(report_path) @@ -99,6 +101,9 @@ class Task: self.config.report_path = report_path self.config.log_path = log_path self.config.start_time = start_time.strftime("%Y-%m-%d %H:%M:%S") + Scheduler.start_task_log(self.config.log_path) + Scheduler.start_encrypt_log(self.config.log_path) + LOG.info("Report path: %s", report_path) def _get_task_dir(self, task_file): from xdevice import Variables @@ -108,15 +113,16 @@ class Task: if os.path.normcase(Variables.exec_dir) == \ os.path.normcase(Variables.top_dir): raise ParamError("task file %s not exists, please add task " - "file to '%s'" % ( - task_file, exec_task_dir)) + "file to '%s'" % (task_file, exec_task_dir), + error_no="00101") top_task_dir = os.path.abspath( os.path.join(Variables.top_dir, self.TASK_CONFIG_DIR)) if not os.path.exists(os.path.join(top_task_dir, task_file)): raise ParamError("task file %s not exists, please add task " "file to '%s' or '%s'" % ( - task_file, exec_task_dir, top_task_dir)) + task_file, exec_task_dir, top_task_dir), + error_no="00101") else: return top_task_dir else: @@ -125,7 +131,8 @@ class Task: def _load_task(self, task_dir, file_name): task_file = os.path.join(task_dir, file_name) if not os.path.exists(task_file): - raise ParamError("task file %s not exists" % task_file) + raise ParamError("task file %s not exists" % task_file, + error_no="00101") # add kits to self.config json_config = JsonParser(task_file) @@ -140,9 +147,11 @@ class Task: self.root = root self._init_driver(root) if not self.test_drivers: - raise ParamError("no test driver to execute") + LOG.error("no test driver to execute", error_no="00106") def _init_driver(self, test_descriptor): + from xdevice import Scheduler + plugin_id = None source = test_descriptor.source @@ -150,15 +159,20 @@ class Task: if source.test_type is not None: plugin_id = source.test_type else: - LOG.error("'%s' no test driver specified" % source.test_name) + LOG.error("'%s' no test driver specified" % source.test_name, + error_no="00106") drivers = get_plugin(plugin_type=Plugin.DRIVER, plugin_id=plugin_id) if plugin_id is not None: if len(drivers) == 0: - LOG.error("'%s' can not find test driver '%s'" % - (source.test_name, plugin_id)) + error_message = "'%s' can not find test driver '%s'" % ( + source.test_name, plugin_id) + LOG.error(error_message, error_no="00106") + if Scheduler.mode == ModeType.decc: + error_message = "Load Error[00106]" + Scheduler.report_not_executed(self.config.report_path, [ + ("", test_descriptor)], error_message) else: - from xdevice import Scheduler check_result = False for driver in drivers: driver_instance = driver.__class__() @@ -172,7 +186,7 @@ class Task: break if check_result is False: LOG.error("'%s' can not find suitable test driver '%s'" % - (source.test_name, plugin_id)) + (source.test_name, plugin_id), error_no="00106") for desc in test_descriptor.children: self._init_driver(desc) @@ -182,7 +196,8 @@ class Task: for _, _, files in os.walk(report_path): for _file in files: if _file.endswith(".xml"): - raise ParamError("xml file exists in '%s'" % report_path) + raise ParamError("xml file exists in '%s'" % report_path, + error_no="00105") class Request: @@ -201,3 +216,49 @@ class Request: def get_config(self): return self.config + + def get(self, key=None, default=""): + # get value from self.config + if not key: + return default + return getattr(self.config, key, default) + + def get_devices(self): + if self.config is None: + return [] + if not hasattr(self.config, "environment"): + return [] + if not hasattr(self.config.environment, "devices"): + return [] + return getattr(self.config.environment, "devices", []) + + def get_config_file(self): + return self._get_source_value("config_file") + + def get_source_file(self): + return self._get_source_value("source_file") + + def get_test_name(self): + return self._get_source_value("test_name") + + def get_source_string(self): + return self._get_source_value("source_string") + + def get_test_type(self): + return self._get_source_value("test_type") + + def get_module_name(self): + return self._get_source_value("module_name") + + def _get_source(self): + if not hasattr(self.root, "source"): + return "" + return getattr(self.root, "source", "") + + def _get_source_value(self, key=None, default=""): + if not key: + return default + source = self._get_source() + if not source: + return default + return getattr(source, key, default) diff --git a/src/xdevice/_core/executor/scheduler.py b/src/xdevice/_core/executor/scheduler.py index 3491dddb0c483283261150ec6abc217b993673b1..fcd520717c3f0ed6f817a82a891a1ca4d43d92b1 100755 --- a/src/xdevice/_core/executor/scheduler.py +++ b/src/xdevice/_core/executor/scheduler.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,6 +16,7 @@ # limitations under the License. # +import copy import datetime import os import queue @@ -24,6 +25,8 @@ import uuid from xml.etree import ElementTree from _core.utils import unique_id +from _core.utils import check_mode +from _core.utils import get_sub_path from _core.utils import get_filename_extension from _core.utils import convert_serial from _core.utils import get_instance_name @@ -32,6 +35,8 @@ from _core.utils import check_result_report from _core.environment.manager_env import EnvironmentManager from _core.environment.manager_env import DeviceSelectionOption from _core.exception import ParamError +from _core.exception import ExecuteTerminate +from _core.exception import LiteDeviceError from _core.exception import DeviceError from _core.interface import LifeCycle from _core.executor.request import Request @@ -45,11 +50,12 @@ from _core.report.reporter_helper import ReportConstant from _core.report.reporter_helper import Case from _core.report.reporter_helper import DataHelper from _core.constants import TestExecType +from _core.constants import CKit +from _core.constants import ModeType from _core.constants import DeviceLabelType from _core.constants import SchedulerType -from _core.constants import HostDrivenTestType from _core.constants import ListenerType -from _core.executor.concurrent import Concurrent +from _core.constants import ConfigConst from _core.executor.concurrent import DriversThread from _core.executor.concurrent import QueueMonitorThread from _core.executor.source import TestSetSource @@ -58,6 +64,8 @@ from _core.executor.source import find_testdict_descriptors from _core.logger import platform_logger from _core.logger import add_task_file_handler from _core.logger import remove_task_file_handler +from _core.logger import add_encrypt_file_handler +from _core.logger import remove_encrypt_file_handler __all__ = ["Scheduler"] LOG = platform_logger("Scheduler") @@ -95,8 +103,8 @@ class Scheduler(object): return task def __execute__(self, task): + error_message = "" try: - self._start_task_logcat(task.config.log_path) Scheduler.is_execute = True if Scheduler.command_queue: LOG.debug("Run command: %s" % Scheduler.command_queue[-1]) @@ -107,6 +115,22 @@ class Scheduler(object): if len(Scheduler.command_queue) > self.max_command_num: Scheduler.command_queue.pop(0) + if getattr(task.config, ConfigConst.test_environment, ""): + self._reset_environment(task.config.get( + ConfigConst.test_environment, "")) + elif getattr(task.config, ConfigConst.configfile, ""): + self._reset_environment(config_file=task.config.get( + ConfigConst.configfile, "")) + + # do with the count of repeat about a task + if getattr(task.config, ConfigConst.repeat, 0) > 0: + drivers_list = list() + for repeat_index in range(task.config.repeat): + for driver_index in range(len(task.test_drivers)): + drivers_list.append( + copy.deepcopy(task.test_drivers[driver_index])) + task.test_drivers = drivers_list + self.test_number = len(task.test_drivers) if task.config.exectype == TestExecType.device_test: @@ -116,11 +140,22 @@ class Scheduler(object): else: LOG.info("exec type %s is bypassed" % task.config.exectype) + except (ParamError, ValueError, TypeError, SyntaxError, AttributeError, + DeviceError, LiteDeviceError, ExecuteTerminate) as exception: + error_no = getattr(exception, "error_no", "") + error_message = "%s[%s]" % (str(exception), error_no) \ + if error_no else str(exception) + error_no = error_no if error_no else "00000" + LOG.exception(exception, exc_info=False, error_no=error_no) + + finally: + if getattr(task.config, ConfigConst.test_environment, "") or\ + getattr(task.config, ConfigConst.configfile, ""): + self._restore_environment() + if Scheduler.upload_address: - Scheduler.upload_task_result(task) + Scheduler.upload_task_result(task, error_message) Scheduler.upload_report_end() - finally: - self._stop_task_logcat() def _device_test_execute(self, task): used_devices = {} @@ -132,30 +167,56 @@ class Scheduler(object): def _host_test_execute(self, task): """execute host test""" - test_drivers = task.test_drivers - if not test_drivers: - raise ParamError("test driver not found") - - params_list = [] - for test_driver in task.test_drivers: - listeners = self._create_listeners(task) - params_list.append((listeners, task, [test_driver])) try: - Concurrent.concurrent_execute(self._execute_test_drivers, - params_list, 2) + # initial params + current_driver_threads = {} + test_drivers = task.test_drivers + message_queue = queue.Queue() + + # execute test drivers + queue_monitor_thread = self._start_queue_monitor( + message_queue, test_drivers, current_driver_threads) + while test_drivers: + if len(current_driver_threads) > 5: + time.sleep(3) + continue + + # clear remaining test drivers when scheduler is terminated + if not Scheduler.is_execute: + LOG.info("clear test drivers") + self._clear_not_executed(task, test_drivers) + break + + # get test driver and device + test_driver = test_drivers[0] + + # display executing progress + self._display_executing_process(None, test_driver, + test_drivers) + + # start driver thread + self._start_driver_thread(current_driver_threads, ( + None, message_queue, task, test_driver)) + test_drivers.pop(0) + + # wait for all drivers threads finished and do kit teardown + while True: + if not queue_monitor_thread.is_alive(): + break + time.sleep(3) + finally: # generate reports self._generate_task_report(task) def _generate_task_report(self, task, used_devices=None): task_info = ExecInfo() - test_type = getattr(task, "testtype", []) + test_type = getattr(task.config, "testtype", []) task_name = getattr(task.config, "task", "") if task_name: task_info.test_type = str(task_name).upper() else: - task_info.test_type = ",".join( - test_type) if test_type else "Test" + task_info.test_type = ",".join(test_type) if test_type else "Test" if used_devices: serials = [] platforms = [] @@ -170,11 +231,12 @@ class Scheduler(object): task_info.device_name = "None" task_info.platform = "None" task_info.test_time = task.config.start_time - task_info.product_params = getattr(task, "product_params", "") + task_info.product_info = getattr(task, "product_info", "") listeners = self._create_listeners(task) for listener in listeners: - listener.__ended__(LifeCycle.TestTask, task_info) + listener.__ended__(LifeCycle.TestTask, task_info, + test_type=task_info.test_type) @classmethod def _create_listeners(cls, task): @@ -206,6 +268,9 @@ class Scheduler(object): if not label: continue device_option = DeviceSelectionOption(options, label, test_source) + device_dict.pop("type", None) + device_dict.pop("label", None) + device_option.extend_value = device_dict device_option.source_file = test_source.config_file or \ test_source.source_string devices_option.append(device_option) @@ -230,7 +295,8 @@ class Scheduler(object): continue else: raise DeviceError("The '%s' required device does not exist" - % test_driver[1].source.source_file) + % test_driver[1].source.source_file, + error_no="00104") return environment @@ -267,13 +333,99 @@ class Scheduler(object): env_manager = EnvironmentManager() env_manager.release_environment(environment) - def _dynamic_concurrent_execute(self, task, used_devices): - from xdevice import SuiteReporter + def _check_device_spt(self, kit, driver_request, device): + kit_version = self._parse_version_id(driver_request, kit) + kit_spt = kit.properties.get(ConfigConst.spt, None) + if not kit_spt or not kit_version: + setattr(device, ConfigConst.task_state, False) + LOG.error("spt or version is empty", error_no="00108") + return + if getattr(driver_request, ConfigConst.product_info, ""): + product_info = getattr(driver_request, + ConfigConst.product_info) + if not isinstance(product_info, dict): + LOG.warning("product info should be dict, %s", + product_info) + setattr(device, ConfigConst.task_state, False) + return + device_spt = product_info.get("Security Patch", None) + device_version = product_info.get("VersionID", None) + if not device_version or not device_version == kit_version: + LOG.error("The device %s VersionID is %s, " + "and the test case version is %s, " + "which does not meet the requirements" % + (device.device_sn, device_version, kit_version), + error_no="00116") + setattr(device, ConfigConst.task_state, False) + return + + if not device_spt or not \ + Scheduler.compare_spt_time(kit_spt, device_spt): + LOG.error("The device %s spt is %s, " + "and the test case spt is %s, " + "which does not meet the requirements" % + (device.device_sn, device_spt, kit_spt), + error_no="00116") + + setattr(device, ConfigConst.task_state, False) + return + + def _decc_task_setup(self, environment, task): + config = Config() + config.update(task.config.__dict__) + config.environment = environment + driver_request = Request(config=config) + + if environment is None: + return False + + for device in environment.devices: + if not getattr(device, ConfigConst.need_kit_setup, True): + LOG.debug("device %s need kit setup is false" % device) + continue + + # do task setup for device + kits_copy = copy.deepcopy(task.config.kits) + setattr(device, ConfigConst.task_kits, kits_copy) + for kit in getattr(device, ConfigConst.task_kits, []): + if not Scheduler.is_execute: + break + try: + kit.__setup__(device, request=driver_request) + except (ParamError, ExecuteTerminate, DeviceError, + LiteDeviceError, ValueError, TypeError, + SyntaxError, AttributeError) as exception: + error_no = getattr(exception, "error_no", "00000") + LOG.exception( + "task setup device: %s, exception: %s" % ( + environment.__get_serial__(), + exception), exc_info=False, error_no=error_no) + if kit.__class__.__name__ == CKit.query: + self._check_device_spt(kit, driver_request, device) + LOG.debug("set device %s need kit setup to false" % device) + setattr(device, ConfigConst.need_kit_setup, False) + + for device in environment.devices: + if not getattr(device, ConfigConst.task_state, True): + return False + + # set product_info to self.task + if getattr(driver_request, ConfigConst.product_info, "") and \ + not getattr(task, ConfigConst.product_info, ""): + product_info = getattr(driver_request, ConfigConst.product_info) + if not isinstance(product_info, dict): + LOG.warning("product info should be dict, %s", + product_info) + else: + setattr(task, ConfigConst.product_info, product_info) + return True + def _dynamic_concurrent_execute(self, task, used_devices): # initial params current_driver_threads = {} test_drivers = task.test_drivers message_queue = queue.Queue() + task_unused_env = [] # execute test drivers queue_monitor_thread = self._start_queue_monitor( @@ -282,25 +434,22 @@ class Scheduler(object): # clear remaining test drivers when scheduler is terminated if not Scheduler.is_execute: LOG.info("clear test drivers") - self._clear_not_executed(test_drivers, task) + self._clear_not_executed(task, test_drivers) break # get test driver and device test_driver = test_drivers[0] - if getattr(task.config, "history_report_path", "") and \ - Scheduler.mode == "decc": - if test_driver[1].source.test_name not in \ - SuiteReporter.get_failed_case_list(): + if getattr(task.config, ConfigConst.history_report_path, ""): + module_name = test_driver[1].source.module_name + if not self.is_module_need_retry(task, module_name): + self._display_executing_process(None, test_driver, + test_drivers) + LOG.info("%s are passed, no need to retry" % module_name) + self._append_history_result(task, module_name) + LOG.info("") test_drivers.pop(0) - self.test_number = self.test_number - 1 - LOG.info("%s has been passed, test number changes to %s", - test_driver[1].source.test_name, self.test_number) continue - else: - SuiteReporter.get_failed_case_list().remove( - test_driver[1].source.test_name) - # get environment try: environment = self.__allocate_environment__( @@ -314,6 +463,24 @@ class Scheduler(object): Scheduler.__free_environment__(environment) continue + if check_mode(ModeType.decc) or getattr( + task.config, ConfigConst.check_device, False): + LOG.info("start to check environment: %s" % + environment.__get_serial__()) + status = self._decc_task_setup(environment, task) + if not status: + Scheduler.__free_environment__(environment) + task_unused_env.append(environment) + error_message = "Load Error[00116]" + self.report_not_executed(task.config.report_path, + [test_drivers[0]], + error_message, task) + test_drivers.pop(0) + continue + else: + LOG.info("environment %s check success", + environment.__get_serial__()) + # display executing progress self._display_executing_process(environment, test_driver, test_drivers) @@ -331,45 +498,109 @@ class Scheduler(object): if not queue_monitor_thread.is_alive(): break time.sleep(3) - self._do_taskkit_teardown(task, used_devices) + + self._do_taskkit_teardown(used_devices, task_unused_env) + + @classmethod + def _append_history_result(cls, task, module_name): + history_report_path = getattr( + task.config, ConfigConst.history_report_path, "") + from _core.report.result_reporter import ResultReporter + params = ResultReporter.get_task_info_params( + history_report_path) + + if not params or not params[4]: + LOG.debug("task info record data reports is empty") + return + + report_data_dict = dict(params[4]) + if module_name not in report_data_dict.keys(): + module_name_ = str(module_name).split(".")[0] + if module_name_ not in report_data_dict.keys(): + LOG.error("%s not in data reports" % module_name) + return + module_name = module_name_ + + from xdevice import SuiteReporter + if check_mode(ModeType.decc): + virtual_report_path, report_result = SuiteReporter. \ + get_history_result_by_module(module_name) + LOG.debug("append history result: (%s, %s)" % ( + virtual_report_path, report_result)) + SuiteReporter.append_report_result( + (virtual_report_path, report_result)) + else: + import shutil + history_execute_result = report_data_dict.get(module_name, "") + LOG.info("start copy %s" % history_execute_result) + file_name = get_filename_extension(history_execute_result)[0] + if os.path.exists(history_execute_result): + result_dir = \ + os.path.join(task.config.report_path, "result") + os.makedirs(result_dir, exist_ok=True) + target_execute_result = "%s.xml" % os.path.join( + task.config.report_path, "result", file_name) + shutil.copyfile(history_execute_result, target_execute_result) + LOG.info("copy %s to %s" % ( + history_execute_result, target_execute_result)) + else: + error_msg = "copy failed! %s not exists!" %\ + history_execute_result + raise ParamError(error_msg) def _handle_device_error(self, exception, task, test_drivers): + self._display_executing_process(None, test_drivers[0], test_drivers) error_message = "%s: %s" % \ (get_instance_name(exception), exception) - LOG.exception(error_message, exc_info=False) - # under "decc" mode ,response device error - if Scheduler.mode == "decc": - self._report_not_executed( - task, test_drivers[1], error_message) + LOG.exception(error_message, exc_info=False, + error_no=exception.error_no) + if check_mode(ModeType.decc): + error_message = "Load Error[00104]" + self.report_not_executed(task.config.report_path, [test_drivers[0]], + error_message, task) + + LOG.info("") test_drivers.pop(0) - self.test_number = self.test_number - 1 - LOG.info("test number changes to %s", self.test_number) @classmethod def _clear_not_executed(cls, task, test_drivers): - if Scheduler.mode != "decc": + if Scheduler.mode != ModeType.decc: # clear all test_drivers.clear() return # The result is reported only in DECC mode, and also clear all. - error_message = "task execution terminated!" - cls._report_not_executed(task, test_drivers, error_message) + LOG.error("case no run: task execution terminated!", error_no="00300") + error_message = "Execute Terminate[00300]" + cls.report_not_executed(task.config.report_path, test_drivers, + error_message) test_drivers.clear() @classmethod - def _report_not_executed(cls, task, test_drivers, error_message): + def report_not_executed(cls, report_path, test_drivers, error_message, + task=None): # traversing list to get remained elements for test_driver in test_drivers: - report_path = task.config.report_path - report_file = os.path.join( - report_path, "result", - "%s.xml" % test_driver[1].source.test_name) + # get report file + if task and getattr(task.config, "testdict", ""): + report_file = os.path.join(get_sub_path( + test_driver[1].source.source_file), + "%s.xml" % test_driver[1].source.test_name) + else: + report_file = os.path.join( + report_path, "result", + "%s.xml" % test_driver[1].source.module_name) + + # get report name report_name = test_driver[1].source.test_name if \ not test_driver[1].source.test_name.startswith("{") \ else "report" + + # get module name + module_name = test_driver[1].source.module_name + # here, normally create empty report and then upload result check_result_report(report_path, report_file, error_message, - report_name) + report_name, module_name) def _start_driver_thread(self, current_driver_threads, thread_params): environment, message_queue, task, test_driver = thread_params @@ -383,18 +614,35 @@ class Scheduler(object): current_driver_threads.setdefault(thread_id, driver_thread) @classmethod - def _do_taskkit_teardown(cls, task, used_devices): + def _do_taskkit_teardown(cls, used_devices, task_unused_env): for device in used_devices.values(): - if getattr(device, "need_kit_setup", True): + if getattr(device, ConfigConst.need_kit_setup, True): continue - for kit in task.config.kits: - kit.__teardown__(device) - setattr(device, "need_kit_setup", True) + + for kit in getattr(device, ConfigConst.task_kits, []): + try: + kit.__teardown__(device) + except Exception as error: + LOG.debug("do taskkit teardown: %s" % error) + setattr(device, ConfigConst.task_kits, []) + setattr(device, ConfigConst.need_kit_setup, True) + + for environment in task_unused_env: + for device in environment.devices: + setattr(device, ConfigConst.task_state, True) + setattr(device, ConfigConst.need_kit_setup, True) def _display_executing_process(self, environment, test_driver, test_drivers): source_content = test_driver[1].source.source_file or \ test_driver[1].source.source_string + if environment is None: + LOG.info("[%d / %d] Executing: %s, Driver: %s" % + (self.test_number - len(test_drivers) + 1, + self.test_number, source_content, + test_driver[1].source.test_type)) + return + LOG.info("[%d / %d] Executing: %s, Device: %s, Driver: %s" % (self.test_number - len(test_drivers) + 1, self.test_number, source_content, @@ -416,7 +664,6 @@ class Scheduler(object): device_serial = device.__get_serial__() if device else "None" if device_serial and device_serial not in used_devices.keys(): used_devices[device_serial] = device - setattr(device, "need_kit_setup", True) @staticmethod def _start_queue_monitor(message_queue, test_drivers, @@ -428,39 +675,20 @@ class Scheduler(object): queue_monitor_thread.start() return queue_monitor_thread - @classmethod - def _execute_test_drivers(cls, listeners, task, allocated_test_drivers): - LOG.debug('========= test_drivers count = %s =========' % len( - allocated_test_drivers)) - for listener in listeners: - LOG.debug("listener %s" % listener) - for driver, test in allocated_test_drivers: - config = Config() - config.update(task.config.__dict__) - driver_request = Request(test, listeners, config) - driver.__execute__(driver_request) - def exec_command(self, command, options): """ Directly executes a command without adding it to the command queue. """ if command != "run": - LOG.error("exec_command can only deal with run command") - raise SystemError("exec_command can only deal with run command") - - try: - if getattr(options, "test_environment", ""): - self._reset_environment(options.test_environment) - - exec_type = options.exectype - if exec_type in [TestExecType.device_test, TestExecType.host_test, - TestExecType.host_driven_test]: - self._exec_task(options) - else: - LOG.error("unsupported execution type '%s'" % exec_type) - finally: - if getattr(options, "test_environment", ""): - self._restore_environment() + raise ParamError("unsupported command action: %s" % command, + error_no="00100") + exec_type = options.exectype + if exec_type in [TestExecType.device_test, TestExecType.host_test, + TestExecType.host_driven_test]: + self._exec_task(options) + else: + LOG.error("unsupported execution type '%s'" % exec_type, + error_no="00100") return @@ -468,14 +696,26 @@ class Scheduler(object): """ Directly allocates a device and execute a device test. """ - task = self.__discover__(options.__dict__) - self.__execute__(task) + try: + task = self.__discover__(options.__dict__) + self.__execute__(task) + except (ParamError, ValueError, TypeError, SyntaxError, + AttributeError) as exception: + error_no = getattr(exception, "error_no", "00000") + LOG.exception("%s: %s" % (get_instance_name(exception), exception), + exc_info=False, error_no=error_no) + if Scheduler.upload_address: + Scheduler.upload_unavailable_result(str(exception.args)) + Scheduler.upload_report_end() + finally: + self.stop_task_log() + self.stop_encrypt_log() @classmethod - def _reset_environment(cls, environment): + def _reset_environment(cls, environment="", config_file=""): env_manager = EnvironmentManager() env_manager.env_stop() - EnvironmentManager(environment) + EnvironmentManager(environment, config_file) @classmethod def _restore_environment(cls): @@ -484,37 +724,53 @@ class Scheduler(object): EnvironmentManager() @classmethod - def _start_task_logcat(cls, log_path): + def start_task_log(cls, log_path): tool_file_name = "task_log.log" tool_log_file = os.path.join(log_path, tool_file_name) add_task_file_handler(tool_log_file) @classmethod - def _stop_task_logcat(cls): + def start_encrypt_log(cls, log_path): + from _core.report.encrypt import check_pub_key_exist + if check_pub_key_exist(): + encrypt_file_name = "task_log.ept" + encrypt_log_file = os.path.join(log_path, encrypt_file_name) + add_encrypt_file_handler(encrypt_log_file) + + @classmethod + def stop_task_log(cls): remove_task_file_handler() + @classmethod + def stop_encrypt_log(cls): + remove_encrypt_file_handler() + @staticmethod def _find_test_root_descriptor(config): - # read test list from testfile, testlist or task - if getattr(config, "testfile", "") or getattr(config, "testlist", "") \ - or getattr(config, "task", ""): - test_set = config.testfile or config.testlist or config.task - fname, _ = get_filename_extension(test_set) - uid = unique_id("Scheduler", fname) - root = Descriptor(uuid=uid, name=fname, - source=TestSetSource(test_set), container=True) - root.children = find_test_descriptors(config) - return root # read test list from testdict - elif getattr(config, "testdict", "") != "": + if getattr(config, "testdict", "") != "" and getattr( + config, "testfile", "") == "": uid = unique_id("Scheduler", "testdict") root = Descriptor(uuid=uid, name="testdict", source=TestSetSource(config.testdict), container=True) root.children = find_testdict_descriptors(config) return root + + # read test list from testfile, testlist or task + test_set = getattr(config, "testfile", "") or getattr( + config, "testlist", "") or getattr(config, "task", "") or getattr( + config, "testcase") + if test_set: + fname, _ = get_filename_extension(test_set) + uid = unique_id("Scheduler", fname) + root = Descriptor(uuid=uid, name=fname, + source=TestSetSource(test_set), container=True) + root.children = find_test_descriptors(config) + return root else: - raise ParamError("no test file, list, dict or task found") + raise ParamError("no test file, list, dict, case or task found", + error_no="00102") @classmethod def terminate_cmd_exec(cls): @@ -546,43 +802,59 @@ class Scheduler(object): test_name = request.root.source.test_name if not result_file or not os.path.exists(result_file): - LOG.error("%s result not exists", test_name) + LOG.error("%s result not exists", test_name, error_no="00200") return test_type = request.root.source.test_type - LOG.info("need upload result: %s, test type: %s, upload address: %s" % - (result_file, test_type, Scheduler.upload_address)) - upload_params, _, _ = \ - cls._get_upload_params(result_file, test_type) + LOG.info("need upload result: %s, test type: %s" % + (result_file, test_type)) + upload_params, _, _ = cls._get_upload_params(result_file, request) if not upload_params: - LOG.error("%s no test case result to upload" % result_file) + LOG.error("%s no test case result to upload" % result_file, + error_no="00201") return + LOG.info("need upload %s case" % len(upload_params)) + from agent.factory import upload_batch + upload_suite = [] for upload_param in upload_params: - cls.upload_case_result(upload_param) + case_id, result, error, start_time, end_time, report_path = \ + upload_param + case = {"caseid": case_id, "result": result, "error": error, + "start": start_time, "end": end_time, + "report": report_path} + LOG.info("case info: %s", case) + upload_suite.append(case) + upload_batch(upload_suite) @classmethod - def _get_upload_params(cls, result_file, test_type): + def _get_upload_params(cls, result_file, request): upload_params = [] report_path = result_file - testsuite_element = DataHelper.parse_data_report(report_path) - start_time, end_time = cls._get_time(testsuite_element) - - if test_type == HostDrivenTestType.device_test: - for model_element in testsuite_element: - case_id = model_element.get(ReportConstant.name, "") - result, error = cls.get_script_result(model_element) - upload_params.append((case_id, result, error, start_time, - end_time, report_path)) - else: - for model_element in testsuite_element: - model_name = model_element.get(ReportConstant.name, "none") - for case_element in model_element: - case_id = cls._get_case_id(case_element, model_name) - case_result, error = cls._get_case_result(case_element) - upload_params.append( - (case_id, case_result, error, start_time, - end_time, report_path)) + task_log_path = os.path.join(request.config.report_path, "log", + "task_log.log") + testsuites_element = DataHelper.parse_data_report(report_path) + start_time, end_time = cls._get_time(testsuites_element) + + for testsuite_element in testsuites_element: + if check_mode(ModeType.developer): + module_name = str(get_filename_extension( + report_path)[0]).split(".")[0] + else: + module_name = testsuite_element.get(ReportConstant.name, + "none") + for case_element in testsuite_element: + case_id = cls._get_case_id(case_element, module_name) + case_result, error = cls._get_case_result(case_element) + if error and len(error) > 150: + error = "%s..." % error[:150] + if case_result == "Ignored": + LOG.info("get upload params: %s result is ignored", + case_id) + continue + upload_params.append( + (case_id, case_result, error, start_time, + end_time, task_log_path)) return upload_params, start_time, end_time @classmethod @@ -604,7 +876,7 @@ class Scheduler(object): if result == "Passed": return result, "" - if Scheduler.mode == "decc": + if Scheduler.mode == ModeType.decc: result = "Failed" error_msg = model_element.get(ReportConstant.message, "") @@ -615,11 +887,11 @@ class Scheduler(object): return result, error_msg @classmethod - def _get_case_id(cls, case_element, model_name): - package_name = case_element.get(ReportConstant.class_name, "none") + def _get_case_id(cls, case_element, package_name): + class_name = case_element.get(ReportConstant.class_name, "none") method_name = case_element.get(ReportConstant.name, "none") - case_id = "{}#{}#{}#{}".format(Scheduler.task_name, model_name, - package_name, method_name) + case_id = "{}#{}#{}#{}".format(Scheduler.task_name, package_name, + class_name, method_name) return case_id @classmethod @@ -640,6 +912,8 @@ class Scheduler(object): result = "Failed" elif case.is_blocked(): result = "Blocked" + elif case.is_ignored(): + result = "Ignored" else: result = "Unavailable" return result, case.message @@ -648,26 +922,35 @@ class Scheduler(object): def _get_time(cls, testsuite_element): start_time = testsuite_element.get(ReportConstant.start_time, "") end_time = testsuite_element.get(ReportConstant.end_time, "") - if start_time and end_time: - start_time = int(time.mktime(time.strptime( - start_time, ReportConstant.time_format)) * 1000) - end_time = int(time.mktime(time.strptime( - end_time, ReportConstant.time_format)) * 1000) - else: - timestamp = str(testsuite_element.get( - ReportConstant.time_stamp, "")).replace("T", " ") - cost_time = testsuite_element.get(ReportConstant.time, "") - if timestamp and cost_time: + try: + if start_time and end_time: + start_time = int(time.mktime(time.strptime( + start_time, ReportConstant.time_format)) * 1000) end_time = int(time.mktime(time.strptime( - timestamp, ReportConstant.time_format)) * 1000) - start_time = int(end_time - float(cost_time) * 1000) + end_time, ReportConstant.time_format)) * 1000) else: - current_time = int(time.time() * 1000) - start_time, end_time = current_time, current_time + timestamp = str(testsuite_element.get( + ReportConstant.time_stamp, "")).replace("T", " ") + cost_time = testsuite_element.get(ReportConstant.time, "") + if timestamp and cost_time: + try: + end_time = int(time.mktime(time.strptime( + timestamp, ReportConstant.time_format)) * 1000) + except ArithmeticError as error: + LOG.error("get time error %s" % error) + end_time = int(time.time() * 1000) + start_time = int(end_time - float(cost_time) * 1000) + else: + current_time = int(time.time() * 1000) + start_time, end_time = current_time, current_time + except ArithmeticError as error: + LOG.error("get time error %s" % error) + current_time = int(time.time() * 1000) + start_time, end_time = current_time, current_time return start_time, end_time @classmethod - def upload_task_result(cls, task): + def upload_task_result(cls, task, error_message=""): if not Scheduler.task_name: LOG.info("no need upload summary report") return @@ -677,12 +960,20 @@ class Scheduler(object): summary_vision_report = os.path.join( task.config.report_path, ReportConstant.summary_vision_report) if not os.path.exists(summary_data_report): - Scheduler.upload_unavailable_result("summary report not exists") + task_log_path = os.path.join(task.config.report_path, "log", + "task_log.log") + if not os.path.exists(task_log_path): + task_log_path = "" + Scheduler.upload_unavailable_result(str( + error_message) or "summary report not exists", task_log_path) return task_element = ElementTree.parse(summary_data_report).getroot() start_time, end_time = cls._get_time(task_element) task_result = cls._get_task_result(task_element) + if task_result == "Unavailable": + summary_vision_report = os.path.join(task.config.report_path, + "log", "task_log.log") error_msg = "" for child in task_element: if child.get(ReportConstant.message, ""): @@ -711,17 +1002,75 @@ class Scheduler(object): return task_result @classmethod - def upload_unavailable_result(cls, error_msg): + def upload_unavailable_result(cls, error_msg, report_path=""): start_time = int(time.time() * 1000) - LOG.info( - "get exception upload params: %s, %s, %s, %s, %s, %s" % ( - Scheduler.task_name, "Unavailable", - error_msg, start_time, start_time, "")) Scheduler.upload_case_result((Scheduler.task_name, "Unavailable", - error_msg, start_time, start_time, "")) + error_msg, start_time, start_time, + report_path)) @classmethod def upload_report_end(cls): from agent.factory import report_end LOG.info("upload report end") report_end() + + @classmethod + def is_module_need_retry(cls, task, module_name): + failed_flag = False + if check_mode(ModeType.decc): + from xdevice import SuiteReporter + for module, failed in SuiteReporter.get_failed_case_list(): + if module_name == module or str(module_name).split( + ".")[0] == module: + failed_flag = True + break + else: + from xdevice import ResultReporter + history_report_path = \ + getattr(task.config, ConfigConst.history_report_path, "") + params = ResultReporter.get_task_info_params(history_report_path) + if params and params[3]: + if dict(params[3]).get(module_name, []): + failed_flag = True + elif dict(params[3]).get(str(module_name).split(".")[0], []): + failed_flag = True + return failed_flag + + @classmethod + def compare_spt_time(cls, kit_spt, device_spt): + if not kit_spt or not device_spt: + return False + try: + kit_time = str(kit_spt).split("-")[:2] + device_time = str(device_spt).split("-")[:2] + k_spt = datetime.datetime.strptime( + "-".join(kit_time), "%Y-%m") + d_spt = datetime.datetime.strptime("-".join(device_time), "%Y-%m") + except ValueError as value_error: + LOG.debug("date format is error, %s" % value_error.args) + return False + month_interval = int(k_spt.month) - int(d_spt.month) + year_interval = int(k_spt.year) - int(d_spt.year) + LOG.debug("kit spt (year=%s, month=%s), device spt (year=%s, month=%s)" + % (k_spt.year, k_spt.month, d_spt.year, d_spt.month)) + if year_interval == 0 and month_interval in (0, 1, 2): + return True + if year_interval == 1 and month_interval + 12 in (1, 2): + return True + + @classmethod + def _parse_version_id(cls, driver_request, kit): + test_args = copy.deepcopy( + driver_request.config.get(ConfigConst.testargs, dict())) + version_id = "" + if ConfigConst.pass_through in test_args.keys(): + import json + pt_dict = json.loads(test_args.get(ConfigConst.pass_through, "")) + version_id = pt_dict.get("VersionID", None) + elif "VersionID" in test_args.keys(): + version_id = test_args.get("VersionID", None) + if version_id: + kit_version = version_id + else: + kit_version = kit.properties.get(ConfigConst.version, None) + return kit_version diff --git a/src/xdevice/_core/executor/source.py b/src/xdevice/_core/executor/source.py index cfa4a42aa533ed6b74b87e1051d0ca0e93f39fbc..e2bb2504b2397ec0e1575880ddfa34c7f64952f0 100755 --- a/src/xdevice/_core/executor/source.py +++ b/src/xdevice/_core/executor/source.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,6 +20,7 @@ import os from collections import namedtuple from _core.constants import DeviceTestType +from _core.constants import ModeType from _core.constants import HostDrivenTestType from _core.exception import ParamError from _core.logger import platform_logger @@ -27,33 +28,38 @@ from _core.utils import get_filename_extension from _core.utils import is_config_str from _core.utils import unique_id +from xdevice_adapter.constants import AppConst + __all__ = ["TestSetSource", "TestSource", "find_test_descriptors", "find_testdict_descriptors"] TestSetSource = namedtuple('TestSetSource', 'set') TestSource = namedtuple('TestSource', 'source_file source_string config_file ' - 'test_name test_type') + 'test_name test_type module_name') TEST_TYPE_DICT = {"DEX": DeviceTestType.dex_test, "HAP": DeviceTestType.hap_test, - "APK": DeviceTestType.hap_test, + AppConst.app_name: DeviceTestType.hap_test, "PYT": HostDrivenTestType.device_test, "JST": DeviceTestType.jsunit_test, "CXX": DeviceTestType.cpp_test, "BIN": DeviceTestType.lite_cpp_test} EXT_TYPE_DICT = {".dex": DeviceTestType.dex_test, ".hap": DeviceTestType.hap_test, - ".apk": DeviceTestType.hap_test, + AppConst.app_ext: DeviceTestType.hap_test, ".py": HostDrivenTestType.device_test, ".js": DeviceTestType.jsunit_test, ".bin": DeviceTestType.lite_cpp_test, "default": DeviceTestType.cpp_test} +PY_SUFFIX = ".py" +PYD_SUFFIX = ".pyd" MODULE_CONFIG_SUFFIX = ".json" LOG = platform_logger("TestSource") def find_test_descriptors(config): - if config.testfile == "" and config.testlist == "" and config.task == "": + if not config.testfile and not config.testlist and not config.task and \ + not config.testcase: return None # get test sources @@ -62,7 +68,8 @@ def find_test_descriptors(config): LOG.debug("test sources: %s", test_sources) # normalize test sources - test_sources = _normalize_test_sources(testcases_dirs, test_sources) + test_sources = _normalize_test_sources(testcases_dirs, test_sources, + config) # make test descriptors test_descriptors = _make_test_descriptors_from_testsources(test_sources, @@ -119,7 +126,7 @@ def find_testdict_descriptors(config): if desc is not None: test_descriptors.append(desc) if not test_descriptors: - raise ParamError("test source is none") + raise ParamError("test source is none", error_no="00110") return test_descriptors @@ -127,7 +134,8 @@ def _get_test_sources(config, testcases_dirs): test_sources = [] # get test sources from testcases_dirs - if not config.testfile and not config.testlist and config.task: + if not config.testfile and not config.testlist and not config.testcase \ + and config.task: for testcases_dir in testcases_dirs: _append_module_test_source(testcases_dir, test_sources) return test_sources @@ -140,11 +148,22 @@ def _get_test_sources(config, testcases_dirs): return test_sources # get test sources from config.testfile - test_file = _get_test_file(config, testcases_dirs) - with open(test_file, "r") as file_content: - for line in file_content: - if line.strip(): - test_sources.append(line.strip()) + if getattr(config, "testfile", ""): + test_file = _get_test_file(config, testcases_dirs) + import stat + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(test_file, flags, modes), "r") as file_content: + for line in file_content: + if line.strip(): + test_sources.append(line.strip()) + + # get test sources from config.testcase + if getattr(config, "testcase", ""): + for test_source in config.testcase.split(";"): + if test_source.strip(): + test_sources.append(test_source.strip()) + return test_sources return test_sources @@ -163,7 +182,8 @@ def _get_test_file(config, testcases_dirs): if os.path.exists(config.testfile): return config.testfile else: - raise ParamError("test file '%s' not exists" % config.testfile) + raise ParamError("test file '%s' not exists" % config.testfile, + error_no="00110") for testcases_dir in testcases_dirs: test_file = os.path.join(testcases_dir, config.testfile) @@ -173,16 +193,18 @@ def _get_test_file(config, testcases_dirs): raise ParamError("test file '%s' not exists" % config.testfile) -def _normalize_test_sources(testcases_dirs, test_sources): +def _normalize_test_sources(testcases_dirs, test_sources, config): norm_test_sources = [] for test_source in test_sources: append_result = False for testcases_dir in testcases_dirs: + # append test source absolute path append_result = _append_norm_test_source( - norm_test_sources, test_source, testcases_dir) + norm_test_sources, test_source, testcases_dir, config) if append_result: break + # append test source if no corresponding file founded if not append_result: norm_test_sources.append(test_source) if not norm_test_sources: @@ -190,18 +212,33 @@ def _normalize_test_sources(testcases_dirs, test_sources): return norm_test_sources -def _append_norm_test_source(norm_test_sources, test_source, testcases_dir): +def _append_norm_test_source(norm_test_sources, test_source, testcases_dir, + config): # get norm_test_source norm_test_source = test_source if not os.path.isabs(test_source): norm_test_source = os.path.abspath( os.path.join(testcases_dir, test_source)) + + # find py or pyd for test case input + if config.testcase and not config.testlist: + if os.path.isfile("%s%s" % (norm_test_source, PY_SUFFIX)): + norm_test_sources.append( + "%s%s" % (norm_test_source, PY_SUFFIX)) + return True + elif os.path.isfile("%s%s" % (norm_test_source, PYD_SUFFIX)): + norm_test_sources.append( + "%s%s" % (norm_test_source, PYD_SUFFIX)) + return True + return False + # append to norm_test_sources if os.path.isfile(norm_test_source): norm_test_sources.append(norm_test_source) return True - elif os.path.isfile(norm_test_source + MODULE_CONFIG_SUFFIX): - norm_test_sources.append(norm_test_source + MODULE_CONFIG_SUFFIX) + elif os.path.isfile("%s%s" % (norm_test_source, MODULE_CONFIG_SUFFIX)): + norm_test_sources.append("%s%s" % (norm_test_source, + MODULE_CONFIG_SUFFIX)) return True return False @@ -218,10 +255,11 @@ def _make_test_descriptor(file_path, test_type_key): config_file = _get_config_file( os.path.join(os.path.dirname(file_path), filename)) + module_name = _parse_module_name(config_file, filename) # make test descriptor desc = Descriptor(uuid=uid, name=filename, source=TestSource(file_path, "", config_file, filename, - test_type)) + test_type, module_name)) return desc @@ -231,7 +269,7 @@ def _get_test_driver(test_source): json_config = JsonParser(test_source) return json_config.get_driver_type() except ParamError as error: - LOG.error(error) + LOG.error(error, error_no=error.error_no) return "" @@ -249,63 +287,116 @@ def _make_test_descriptors_from_testsources(test_sources, config): # get params config_file = _get_config_file( - os.path.join(os.path.dirname(test_source), filename)) + os.path.join(os.path.dirname(test_source), filename), ext, config) test_type = _get_test_type(config_file, test_driver, ext) - if not test_type: - LOG.error("no driver to execute '%s'" % test_source) - continue desc = _create_descriptor(config_file, filename, test_source, - test_type) - test_descriptors.append(desc) + test_type, config) + if desc: + test_descriptors.append(desc) return test_descriptors -def _create_descriptor(config_file, filename, test_source, test_type): +def _create_descriptor(config_file, filename, test_source, test_type, config): + from xdevice import Scheduler from _core.executor.request import Descriptor - uid = unique_id("TestSource", filename) + error_message = "" + if not test_type: + error_message = "no driver to execute '%s'" % test_source + LOG.error(error_message, error_no="00112") + if Scheduler.mode != ModeType.decc: + return None + # create Descriptor - if os.path.isfile(test_source): - desc = Descriptor(uuid=uid, name=filename, - source=TestSource(test_source, "", config_file, - filename, test_type)) - elif is_config_str(test_source): - desc = Descriptor(uuid=uid, name=filename, - source=TestSource("", test_source, config_file, - filename, test_type)) - else: - raise ParamError("test source '%s' or '%s' not exists" % ( - test_source, "%s%s" % (test_source, ".json"))) + uid = unique_id("TestSource", filename) + module_name = _parse_module_name(config_file, filename) + desc = Descriptor(uuid=uid, name=filename, + source=TestSource(test_source, "", config_file, + filename, test_type, module_name)) + if not os.path.isfile(test_source): + if is_config_str(test_source): + desc = Descriptor(uuid=uid, name=filename, + source=TestSource("", test_source, config_file, + filename, test_type, + module_name)) + else: + if config.testcase and not config.testlist: + error_message = "test case '%s' or '%s' not exists" % ( + "%s%s" % (test_source, PY_SUFFIX), "%s%s" % ( + test_source, PYD_SUFFIX)) + error_no = "00103" + else: + error_message = "test source '%s' or '%s' not exists" % ( + test_source, "%s%s" % (test_source, MODULE_CONFIG_SUFFIX)) + error_no = "00102" + if Scheduler.mode != ModeType.decc: + raise ParamError(error_message, error_no=error_no) + + if Scheduler.mode == ModeType.decc and error_message: + Scheduler.report_not_executed(config.report_path, [("", desc)], + error_message) + return None + return desc -def _get_config_file(filename): +def _get_config_file(filename, ext=None, config=None): config_file = None - if os.path.exists(filename + MODULE_CONFIG_SUFFIX): - config_file = filename + MODULE_CONFIG_SUFFIX + if os.path.exists("%s%s" % (filename, MODULE_CONFIG_SUFFIX)): + config_file = "%s%s" % (filename, MODULE_CONFIG_SUFFIX) + return config_file + if ext and os.path.exists("%s%s%s" % (filename, ext, + MODULE_CONFIG_SUFFIX)): + config_file = "%s%s%s" % (filename, ext, MODULE_CONFIG_SUFFIX) + return config_file + if config and getattr(config, "testcase", "") and not getattr( + config, "testlist"): + return _get_testcase_config_file(filename) + return config_file +def _get_testcase_config_file(filename): + depth = 1 + dirname = os.path.dirname(filename) + while dirname and depth < 6: + for item in os.listdir(dirname): + item_path = os.path.join(dirname, item) + if os.path.isfile(item_path) and item.endswith( + MODULE_CONFIG_SUFFIX): + return item_path + depth += 1 + dirname = os.path.dirname(dirname) + return None + + def _get_test_type(config_file, test_driver, ext): if test_driver: return test_driver if config_file: if not os.path.exists(config_file): - LOG.error("config file '%s' not exists" % config_file) + LOG.error("config file '%s' not exists" % config_file, + error_no="00110") return "" return _get_test_driver(config_file) - # .py .js .hap, .dex, .bin if ext in [".py", ".js", ".dex", ".hap", ".bin"] \ and ext in EXT_TYPE_DICT.keys(): test_type = EXT_TYPE_DICT[ext] - # .apk - elif ext in [".apk"] and ext in EXT_TYPE_DICT.keys(): + elif ext in [AppConst.app_ext] and ext in EXT_TYPE_DICT.keys(): test_type = DeviceTestType.hap_test - # cxx else: test_type = DeviceTestType.cpp_test return test_type + + +def _parse_module_name(config_file, file_name): + if config_file: + return get_filename_extension(config_file)[0] + else: + if "{" in file_name: + return "report" + return file_name diff --git a/src/xdevice/_core/interface.py b/src/xdevice/_core/interface.py index d8ec4c13b19e6113fec6e4baf69b3c7c5847b9aa..1990af910042ed8b9e4c5a108a70444b11049391 100755 --- a/src/xdevice/_core/interface.py +++ b/src/xdevice/_core/interface.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,7 +21,7 @@ from abc import abstractmethod from enum import Enum __all__ = ["LifeCycle", "IDevice", "IDriver", "IListener", "IShellReceiver", - "IParser", "ITestKit", "IScheduler", "IDeviceManager"] + "IParser", "ITestKit", "IScheduler", "IDeviceManager", "IReporter"] class LifeCycle(Enum): @@ -49,6 +49,7 @@ class IDeviceManager(ABC): Class managing the set of different types of devices for testing """ __slots__ = () + support_labels = [] @abstractmethod def apply_device(self, device_option, timeout=10): @@ -64,6 +65,18 @@ class IDeviceManager(ABC): return _check_methods(class_info, "__serial__") return NotImplemented + @abstractmethod + def init_environment(self, environment, user_config_file): + pass + + @abstractmethod + def env_stop(self): + pass + + @abstractmethod + def list_devices(self): + pass + class IDevice(ABC): """ @@ -71,6 +84,7 @@ class IDevice(ABC): devices """ __slots__ = () + extend_value = {} @abstractmethod def __set_serial__(self, device_sn=""): @@ -86,6 +100,16 @@ class IDevice(ABC): return _check_methods(class_info, "__serial__") return NotImplemented + @abstractmethod + def get(self, key=None, default=None): + if not key: + return default + value = getattr(self, key, None) + if value: + return value + else: + return self.extend_value.get(key, default) + class IDriver(ABC): """ @@ -306,3 +330,20 @@ class ITestKit(ABC): return _check_methods(class_info, "__check_config__", "__setup__", "__teardown__") return NotImplemented + + +class IReporter(ABC): + """ + A reporter to generate reports + """ + __slots__ = () + + @abstractmethod + def __generate_reports__(self, report_path, **kwargs): + pass + + @classmethod + def __subclasshook__(cls, class_info): + if cls is IReporter: + return _check_methods(class_info, "__generate_reports__") + return NotImplemented diff --git a/src/xdevice/_core/logger.py b/src/xdevice/_core/logger.py index 75e0dc8bc62e16c9d2da714ea0a656ff8e0057df..12c3c5978d2b8e639f601b74b2c0172c18a84605 100755 --- a/src/xdevice/_core/logger.py +++ b/src/xdevice/_core/logger.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -18,18 +18,24 @@ import logging import sys +import time from logging.handlers import RotatingFileHandler from _core.constants import LogType from _core.plugin import Plugin from _core.plugin import get_plugin +from _core.exception import ParamError + __all__ = ["Log", "platform_logger", "device_logger", "shutdown", - "add_task_file_handler", "remove_task_file_handler"] + "add_task_file_handler", "remove_task_file_handler", + "change_logger_level", "add_encrypt_file_handler", + "remove_encrypt_file_handler"] _HANDLERS = [] _LOGGERS = [] MAX_LOG_LENGTH = 10 * 1024 * 1024 +MAX_ENCRYPT_LOG_LENGTH = 5 * 1024 * 1024 class Log: @@ -40,6 +46,7 @@ class Log: self.handlers = [] self.loggers = {} self.task_file_handler = None + self.encrypt_file_handler = None def __initial__(self, log_handler_flag, log_file=None, level=None, log_format=None): @@ -64,6 +71,9 @@ class Log: self.task_file_handler = None _HANDLERS.extend(self.handlers) + def set_level(self, level): + self.level = level + def __logger__(self, name=None): if not name: return _init_global_logger(name) @@ -98,6 +108,27 @@ class Log: self.task_file_handler.close() self.task_file_handler = None + def add_encrypt_file_handler(self, log_file): + from xdevice import Variables + + file_handler = \ + EncryptFileHandler(log_file, mode="ab", + max_bytes=MAX_ENCRYPT_LOG_LENGTH, + backup_count=5, encoding="utf-8") + file_handler.setFormatter(logging.Formatter( + Variables.report_vars.log_format)) + self.encrypt_file_handler = file_handler + for _, log in self.loggers.items(): + log.add_encrypt_log(self.encrypt_file_handler) + + def remove_encrypt_file_handler(self): + if self.encrypt_file_handler is None: + return + for _, log in self.loggers.items(): + log.remove_encrypt_log(self.encrypt_file_handler) + self.encrypt_file_handler.close() + self.encrypt_file_handler = None + class FrameworkLog: @@ -105,12 +136,23 @@ class FrameworkLog: self.name = name self.platform_log = logging.Logger(name) self.task_log = None + self.encrypt_log = None + + def set_level(self, level): + # apply to dynamic change logger level, and + # only change the level of platform log + cache = getattr(self.platform_log, "_cache", None) + if cache and isinstance(cache, dict): + cache.clear() + self.platform_log.setLevel(level) def add_task_log(self, handler): if self.task_log: return self.task_log = logging.Logger(self.name) - self.task_log.setLevel(logging.DEBUG) + log_level = getattr(sys, "log_level", logging.INFO) if hasattr( + sys, "log_level") else logging.DEBUG + self.task_log.setLevel(log_level) self.task_log.addHandler(handler) def remove_task_log(self, handler): @@ -119,30 +161,105 @@ class FrameworkLog: self.task_log.removeHandler(handler) self.task_log = None + def add_encrypt_log(self, handler): + if self.encrypt_log: + return + self.encrypt_log = logging.Logger(self.name) + log_level = getattr(sys, "log_level", logging.INFO) if hasattr( + sys, "log_level") else logging.DEBUG + self.encrypt_log.setLevel(log_level) + self.encrypt_log.addHandler(handler) + + def remove_encrypt_log(self, handler): + if not self.encrypt_log: + return + self.encrypt_log.removeHandler(handler) + self.encrypt_log = None + def info(self, msg, *args, **kwargs): - self.platform_log.info(msg, *args, **kwargs) + additional_output = self._get_additional_output(**kwargs) + updated_msg = self._update_msg(additional_output, msg) + self.platform_log.info(updated_msg, *args) if self.task_log: - self.task_log.info(msg, *args, **kwargs) + self.task_log.info(updated_msg, *args) + if self.encrypt_log: + self.encrypt_log.info(updated_msg, *args) def debug(self, msg, *args, **kwargs): - self.platform_log.debug(msg, *args, **kwargs) - if self.task_log: - self.task_log.debug(msg, *args, **kwargs) + additional_output = self._get_additional_output(**kwargs) + updated_msg = self._update_msg(additional_output, msg) + from _core.report.encrypt import check_pub_key_exist + if not check_pub_key_exist(): + self.platform_log.debug(updated_msg, *args) + if self.task_log: + self.task_log.debug(updated_msg, *args) + else: + if self.encrypt_log: + self.encrypt_log.debug(updated_msg, *args) def error(self, msg, *args, **kwargs): - self.platform_log.error(msg, *args, **kwargs) + error_no = kwargs.get("error_no", "00000") + additional_output = self._get_additional_output(error_no, **kwargs) + updated_msg = self._update_msg(additional_output, msg) + + self.platform_log.error(updated_msg, *args) if self.task_log: - self.task_log.error(msg, *args, **kwargs) + self.task_log.error(updated_msg, *args) + if self.encrypt_log: + self.encrypt_log.error(updated_msg, *args) def warning(self, msg, *args, **kwargs): - self.platform_log.warning(msg, *args, **kwargs) - if self.task_log: - self.task_log.warning(msg, *args, **kwargs) + additional_output = self._get_additional_output(**kwargs) + updated_msg = self._update_msg(additional_output, msg) - def exception(self, msg, exc_info=True, **kwargs): - self.platform_log.exception(msg, exc_info=exc_info, **kwargs) + self.platform_log.warning(updated_msg, *args) + if self.task_log: + self.task_log.warning(updated_msg, *args) + if self.encrypt_log: + self.encrypt_log.warning(updated_msg, *args) + + def exception(self, msg, *args, **kwargs): + error_no = kwargs.get("error_no", "00000") + exc_info = kwargs.get("exc_info", True) + if exc_info is not True and exc_info is not False: + exc_info = True + additional_output = self._get_additional_output(error_no, **kwargs) + updated_msg = self._update_msg(additional_output, msg) + + self.platform_log.exception(updated_msg, exc_info=exc_info, *args) if self.task_log: - self.task_log.exception(msg, exc_info=exc_info, **kwargs) + self.task_log.exception(updated_msg, exc_info=exc_info, *args) + if self.encrypt_log: + self.encrypt_log.exception(updated_msg, exc_info=exc_info, *args) + + @classmethod + def _update_msg(cls, additional_output, msg): + msg = "[%s]" % msg if msg else msg + if msg and additional_output: + msg = "%s [%s]" % (msg, additional_output) + return msg + + def _get_additional_output(self, error_number=None, **kwargs): + dict_str = self._get_dict_str(**kwargs) + if error_number: + additional_output = "ErrorNo=%s" % error_number + else: + return dict_str + + if dict_str: + additional_output = "%s, %s" % (additional_output, dict_str) + return additional_output + + @classmethod + def _get_dict_str(cls, **kwargs): + dict_str = "" + for key, value in kwargs.items(): + if key in ["error_no", "exc_info"]: + continue + dict_str = "%s%s=%s, " % (dict_str, key, value) + if dict_str: + dict_str = dict_str[:-2] + return dict_str def platform_logger(name=None): @@ -189,11 +306,131 @@ def remove_task_file_handler(): log_plugin.remove_task_file_handler() +def add_encrypt_file_handler(log_file=None): + if log_file is None: + return + plugins = get_plugin(Plugin.LOG, LogType.tool) + for log_plugin in plugins: + if log_plugin.get_plugin_config().enabled: + log_plugin.add_encrypt_file_handler(log_file) + + +def remove_encrypt_file_handler(): + plugins = get_plugin(Plugin.LOG, LogType.tool) + for log_plugin in plugins: + if log_plugin.get_plugin_config().enabled: + log_plugin.remove_encrypt_file_handler() + + def _init_global_logger(name=None): handler = logging.StreamHandler(sys.stdout) - log_format = "%(asctime)s %(name)-15s %(levelname)-8s %(message)s" + log_format = "[%(asctime)s] [%(name)s] [%(levelname)s] [%(message)s]" handler.setFormatter(logging.Formatter(log_format)) - log = logging.Logger(name) - log.setLevel(logging.INFO) - log.addHandler(handler) + log = FrameworkLog(name) + log.platform_log.setLevel(logging.INFO) + log.platform_log.addHandler(handler) return log + + +def change_logger_level(leve_dict): + level_map = {"debug": logging.DEBUG, "info": logging.INFO} + if "console" in leve_dict.keys(): + level = leve_dict["console"] + if not level: + return + if str(level).lower() in level_map.keys(): + logger_level = level_map.get(str(level).lower(), logging.INFO) + + # change level of loggers which will to be instantiated. + # Actually, it changes the level attribute in ToolLog, + # which will be used when instantiating the FrameLog object. + plugins = get_plugin(Plugin.LOG, LogType.tool) + for log_plugin in plugins: + log_plugin.set_level(logger_level) + # change level of loggers which have instantiated + for logger in _LOGGERS: + if getattr(logger, "setLevel", None): + logger.setLevel(logger_level) + elif getattr(logger, "set_level", None): + logger.set_level(logger_level) + + if "file" in leve_dict.keys(): + level = leve_dict["file"] + if not level: + return + if str(level).lower() in level_map.keys(): + logger_level = level_map.get(str(level).lower(), logging.INFO) + setattr(sys, "log_level", logger_level) + + +class EncryptFileHandler(RotatingFileHandler): + + def __init__(self, filename, mode='ab', max_bytes=0, backup_count=0, + encoding=None, delay=False): + RotatingFileHandler.__init__(self, filename, mode, max_bytes, + backup_count, encoding, delay) + self.mode = mode + self.encrypt_error = None + + def _open(self): + if not self.mode == "ab": + self.mode = "ab" + + # baseFilename is the attribute in FileHandler + base_file_name = getattr(self, "baseFilename", None) + return open(base_file_name, self.mode) + + def emit(self, record): + try: + if not self._encrypt_valid(): + return + + # shouldRoller and doRoller is the method in RotatingFileHandler + should_rollover = getattr(self, "shouldRollover", None) + if callable(should_rollover) and should_rollover(record): + self.doRollover() + + # stream is the attribute in StreamHandler + if not getattr(self, "stream", None): + setattr(self, "stream", self._open()) + msg = self.format(record) + stream = getattr(self, "stream", self._open()) + stream.write(msg) + self.flush() + except RecursionError: + raise + + def _encrypt_valid(self): + from _core.report.encrypt import check_pub_key_exist + if check_pub_key_exist() and not self.encrypt_error: + return True + + def format(self, record): + """ + Customize the implementation method. If the log format of the + framework changes, update the return value format of the method + in a timely manner. + :param record: logging.LogRecord + :return: bytes + """ + from _core.report.encrypt import do_rsa_encrypt + create_time = "{},{}".format( + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(record.created)), + "{:0>3d}".format(int("%d" % record.msecs))) + name = record.name + level_name = record.levelname + msg = record.msg + if msg and "%s" in msg: + msg = msg % record.args + info = "[%s] [%s] [%s] %s%s" \ + % (create_time, name, level_name, msg, "\n") + + try: + return do_rsa_encrypt(info) + except ParamError as error: + error_no_str = \ + "ErrorNo={}".format(getattr(error, "error_no", "00113")) + info = "[%s] [%s] [%s] [%s] [%s]\n" % ( + create_time, name, "ERROR", error, error_no_str) + self.encrypt_error = bytes(info, "utf-8") + return self.encrypt_error diff --git a/src/xdevice/_core/plugin.py b/src/xdevice/_core/plugin.py index 57702b74a43ca56a67fbba395fa3f667b4982dd1..05700f09c5f827ed806979b1b1799e741d432f02 100755 --- a/src/xdevice/_core/plugin.py +++ b/src/xdevice/_core/plugin.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -24,6 +24,8 @@ from _core.interface import IListener from _core.interface import IScheduler from _core.interface import IDevice from _core.interface import ITestKit +from _core.interface import IDeviceManager +from _core.interface import IReporter __all__ = ["Config", "Plugin", "get_plugin", "set_plugin_params", "get_all_plugins", "clear_plugin_cache"] @@ -53,6 +55,12 @@ class Config: def update(self, params): self.__dict__.update(params) + def get(self, key, default=""): + return self.__dict__.get(key, default) + + def set(self, key, value): + self.__dict__[key] = value + class Plugin(object): """ @@ -68,6 +76,7 @@ class Plugin(object): LISTENER = "listener" TEST_KIT = "testkit" MANAGER = "manager" + REPORTER = "reporter" _builtin_plugin = dict({ SCHEDULER: [IScheduler], @@ -75,7 +84,9 @@ class Plugin(object): DEVICE: [IDevice], PARSER: [IParser], LISTENER: [IListener], - TEST_KIT: [ITestKit] + TEST_KIT: [ITestKit], + MANAGER: [IDeviceManager], + REPORTER: [IReporter] }) def __init__(self, *args, **kwargs): @@ -129,8 +140,12 @@ class Plugin(object): "{} plugin must implement {} interface.".format( cls.__name__, interface)) - _PLUGINS.setdefault((self.plugin_type, self.plugin_id), []).insert( - 0, instance) + if "xdevice" in str(instance.__class__).lower(): + _PLUGINS.setdefault((self.plugin_type, self.plugin_id), []).append( + instance) + else: + _PLUGINS.setdefault((self.plugin_type, self.plugin_id), []).insert( + 0, instance) return cls @@ -149,8 +164,20 @@ def get_plugin(plugin_type, plugin_id=None): :return: the instance list of plugin """ if plugin_id is None: - plugin_id = plugin_type - return _PLUGINS.get((plugin_type, plugin_id), []) + plugins = [] + for key in _PLUGINS: + if key[0] != plugin_type: + continue + if not _PLUGINS.get(key): + continue + if key[1] == plugin_type: + plugins.insert(0, _PLUGINS.get(key)[0]) + else: + plugins.append(_PLUGINS.get(key)[0]) + return plugins + + else: + return _PLUGINS.get((plugin_type, plugin_id), []) def set_plugin_params(plugin_type, plugin_id=None, **kwargs): diff --git a/src/xdevice/_core/report/__init__.py b/src/xdevice/_core/report/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/report/__main__.py b/src/xdevice/_core/report/__main__.py index 29b620e5290e24863f49cd9fa1e06925387d66e5..33d3a492029c18c6c642cbc13fe5d3eebaf8b57f 100755 --- a/src/xdevice/_core/report/__main__.py +++ b/src/xdevice/_core/report/__main__.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -55,8 +55,8 @@ def main_report(): task_info.device_name = "None" task_info.test_time = time.strftime(ReportConstant.time_format, time.localtime()) - result_report = ResultReporter(report_path, task_info) - result_report.generate_reports() + result_report = ResultReporter() + result_report.__generate_reports__(report_path, task_info=task_info) if __name__ == "__main__": diff --git a/src/xdevice/_core/report/encrypt.py b/src/xdevice/_core/report/encrypt.py index 5ac9de986c9c47acb3071717b27684352d64ba03..32c895802836911569630f8cc546085a368fd301 100755 --- a/src/xdevice/_core/report/encrypt.py +++ b/src/xdevice/_core/report/encrypt.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,16 +20,21 @@ import os import hashlib from _core.logger import platform_logger +from _core.exception import ParamError __all__ = ["check_pub_key_exist", "do_rsa_encrypt", "do_rsa_decrypt", "generate_key_file", "get_file_summary"] PUBLIC_KEY_FILE = "config/pub.key" +PRIVATE_KEY_FILE = "config/pri.key" LOG = platform_logger("Encrypt") def check_pub_key_exist(): from xdevice import Variables + if Variables.report_vars.pub_key_string: + return Variables.report_vars.pub_key_string + if Variables.report_vars.pub_key_file is not None: if Variables.report_vars.pub_key_file == "": return False @@ -62,27 +67,31 @@ def do_rsa_encrypt(content): import rsa from xdevice import Variables - with open(Variables.report_vars.pub_key_file, 'rb') as key_content: - # get params - public_key = rsa.PublicKey.load_pkcs1(key_content.read()) - max_encrypt_len = int(public_key.n.bit_length() / 8) - 11 + if not Variables.report_vars.pub_key_string: + with open(Variables.report_vars.pub_key_file, + 'rb') as key_content: + Variables.report_vars.pub_key_string = key_content.read() - try: - # encrypt - cipher_text = b"" - for frag in _get_frags(plain_text, max_encrypt_len): - cipher_text_frag = rsa.encrypt(frag, public_key) - cipher_text += cipher_text_frag - return cipher_text - except rsa.pkcs1.CryptoError as error: - error_msg = "rsa encryption error occurs, %s" % error.args[0] - LOG.error(error_msg) - return bytes(error_msg, 'utf-8') + if isinstance(Variables.report_vars.pub_key_string, str): + Variables.report_vars.pub_key_string =\ + bytes(Variables.report_vars.pub_key_string, "utf-8") - except (ModuleNotFoundError, ValueError, TypeError, UnicodeError) as error: + public_key = rsa.PublicKey.load_pkcs1_openssl_pem( + Variables.report_vars.pub_key_string) + + max_encrypt_len = int(public_key.n.bit_length() / 8) - 11 + + # encrypt + cipher_text = b"" + for frag in _get_frags(plain_text, max_encrypt_len): + cipher_text_frag = rsa.encrypt(frag, public_key) + cipher_text += cipher_text_frag + return cipher_text + + except (ModuleNotFoundError, ValueError, TypeError, UnicodeError, + Exception) as error: error_msg = "rsa encryption error occurs, %s" % error.args[0] - LOG.error(error_msg) - return bytes(error_msg, 'utf-8') + raise ParamError(error_msg, error_no="00113") def do_rsa_decrypt(content): @@ -96,8 +105,11 @@ def do_rsa_decrypt(content): import rsa from xdevice import Variables - pri_key_file = os.path.join(os.path.dirname( - Variables.report_vars.pub_key_file), "pri.key") + pri_key_path = os.path.join(Variables.exec_dir, PRIVATE_KEY_FILE) + if os.path.exists(pri_key_path): + pri_key_file = pri_key_path + else: + pri_key_file = os.path.join(Variables.top_dir, PRIVATE_KEY_FILE) if not os.path.exists(pri_key_file): return content with open(pri_key_file, "rb") as key_content: @@ -114,12 +126,12 @@ def do_rsa_decrypt(content): return plain_text.decode(encoding='utf-8') except rsa.pkcs1.CryptoError as error: error_msg = "rsa decryption error occurs, %s" % error.args[0] - LOG.error(error_msg) + LOG.error(error_msg, error_no="00114") return error_msg except (ModuleNotFoundError, ValueError, TypeError, UnicodeError) as error: error_msg = "rsa decryption error occurs, %s" % error.args[0] - LOG.error(error_msg) + LOG.error(error_msg, error_no="00114") return error_msg @@ -134,10 +146,17 @@ def generate_key_file(length=2048): pub_key, pri_key = key.newkeys(int(length)) pub_key_pem = pub_key.save_pkcs1().decode() pri_key_pem = pri_key.save_pkcs1().decode() - with open("pri.key", "w") as file_pri: + + file_pri_open = os.open("pri.key", os.O_WRONLY | os.O_CREAT | + os.O_APPEND, 0o755) + file_pub_open = os.open("pub.key", os.O_WRONLY | os.O_CREAT | + os.O_APPEND, 0o755) + with os.fdopen(file_pri_open, "w") as file_pri, \ + os.fdopen(file_pub_open, "w") as file_pub: file_pri.write(pri_key_pem) - with open("pub.key", "w") as file_pub: + file_pri.flush() file_pub.write(pub_key_pem) + file_pub.flush() except ModuleNotFoundError: return diff --git a/src/xdevice/_core/report/reporter_helper.py b/src/xdevice/_core/report/reporter_helper.py index efd099d261646705ea070aa99049a6fd974f5caa..994e6eeb210384cff3422a19a8eef49510158575 100755 --- a/src/xdevice/_core/report/reporter_helper.py +++ b/src/xdevice/_core/report/reporter_helper.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -26,6 +26,7 @@ from xml.etree import ElementTree from _core.logger import platform_logger from _core.report.encrypt import check_pub_key_exist from _core.report.encrypt import do_rsa_encrypt +from _core.exception import ParamError LOG = platform_logger("ReporterHelper") @@ -37,13 +38,13 @@ class ReportConstant: summary_vision_report = "summary_report.html" details_vision_report = "details_report.html" failures_vision_report = "failures_report.html" + task_info_record = "task_info.record" summary_ini = "summary.ini" summary_report_hash = "summary_report.hash" title_name = "title_name" summary_title = "Summary Report" details_title = "Details Report" failures_title = "Failures Report" - decc_mode = "decc" # exec_info constants platform = "platform" @@ -56,9 +57,11 @@ class ReportConstant: execute_time = "execute_time" # summary constants - product_params = "product_params" + product_info = "productinfo" + product_info_ = "product_info" modules = "modules" - modules_done = "modules_done" + run_modules = "runmodules" + run_modules_ = "run_modules" name = "name" time = "time" total = "total" @@ -73,7 +76,8 @@ class ReportConstant: message = "message" # case result constants - module = "module" + module_name = "modulename" + module_name_ = "module_name" result = "result" status = "status" run = "run" @@ -87,8 +91,8 @@ class ReportConstant: # time constants time_stamp = "timestamp" - start_time = "start_time" - end_time = "end_time" + start_time = "starttime" + end_time = "endtime" time_format = "%Y-%m-%d %H:%M:%S" # xml tag constants @@ -117,14 +121,17 @@ class DataHelper: @staticmethod def parse_data_report(data_report): - if os.path.exists(data_report): - with open(data_report, 'r', encoding='UTF-8') as file_content: + if "<" not in data_report and os.path.exists(data_report): + with open(data_report, 'r', encoding='UTF-8', errors="ignore") as \ + file_content: data_str = file_content.read() else: data_str = data_report - data_str = data_str.replace(chr(2), "").replace(chr(15), "") \ - .replace(chr(16), "").replace(chr(1), "") + for char_index in range(32): + if char_index in [10, 13]: # chr(10): LF, chr(13): CR + continue + data_str = data_str.replace(chr(char_index), "") try: return ElementTree.fromstring(data_str) except SyntaxError as error: @@ -157,9 +164,22 @@ class DataHelper: self.LINE_BREAK_INDENT + self.INDENT, "") @classmethod - def get_summary_result(cls, report_path, file_name, key=None, - reverse=False): - data_reports = cls._get_data_reports(report_path) + def update_suite_result(cls, suite, case): + update_time = round(float(suite.get( + ReportConstant.time, 0)) + float( + case.get(ReportConstant.time, 0)), 3) + suite.set(ReportConstant.time, str(update_time)) + update_tests = str(int(suite.get(ReportConstant.tests, 0))+1) + suite.set(ReportConstant.tests, update_tests) + if case.findall('failure'): + update_failures = str(int(suite.get(ReportConstant.failures, 0))+1) + suite.set(ReportConstant.failures, update_failures) + + @classmethod + def get_summary_result(cls, report_path, file_name, key=None, **kwargs): + reverse = kwargs.get("reverse", False) + file_prefix = kwargs.get("file_prefix", None) + data_reports = cls._get_data_reports(report_path, file_prefix) if not data_reports: return if key: @@ -171,24 +191,39 @@ class DataHelper: ReportConstant.unavailable] for data_report in data_reports: data_report_element = cls.parse_data_report(data_report) + if not len(list(data_report_element)): + continue if not summary_result: summary_result = data_report_element continue - summary_testsuite = summary_result[0] - data_testsuite = data_report_element[0] - cls._update_attributes(summary_testsuite, data_testsuite, - need_update_attributes) - for element in data_testsuite: - summary_testsuite.append(element) - - need_update_attributes.append(ReportConstant.time) - for attribute in need_update_attributes: - summary_result.set(attribute, summary_result[0].get(attribute)) - - cls.generate_report(summary_result, file_name) + if not summary_result or not data_report_element: + continue + for data_suite in data_report_element: + for summary_suite in summary_result: + if data_suite.get("name", None) == \ + summary_suite.get("name", None): + for data_case in data_suite: + for summary_case in summary_suite: + if data_case.get("name", None) == \ + summary_case.get("name", None): + break + else: + summary_suite.append(data_case) + DataHelper.update_suite_result(summary_result, + data_case) + DataHelper.update_suite_result(summary_suite, + data_case) + break + else: + summary_result.append(data_suite) + DataHelper._update_attributes(summary_result, data_suite, + need_update_attributes) + if summary_result: + cls.generate_report(summary_result, file_name) + return summary_result @classmethod - def _get_data_reports(cls, report_path): + def _get_data_reports(cls, report_path, file_prefix=None): if not os.path.isdir(report_path): return [] data_reports = [] @@ -196,6 +231,8 @@ class DataHelper: for file_name in files: if not file_name.endswith(cls.DATA_REPORT_SUFFIX): continue + if file_prefix and not file_name.startswith(file_prefix): + continue data_reports.append(os.path.join(root, file_name)) return data_reports @@ -209,16 +246,26 @@ class DataHelper: # update time updated_time = round(float(summary_element.get( ReportConstant.time, 0)) + float( - summary_element.get(ReportConstant.time, 0)), 3) + data_element.get(ReportConstant.time, 0)), 3) summary_element.set(ReportConstant.time, str(updated_time)) @staticmethod def generate_report(element, file_name): if check_pub_key_exist(): plain_text = DataHelper.to_string(element) - cipher_text = do_rsa_encrypt(plain_text) - with open(file_name, "wb") as file_handler: + try: + cipher_text = do_rsa_encrypt(plain_text) + except ParamError as error: + LOG.error(error, error_no=error.error_no) + cipher_text = b"" + if platform.system() == "Windows": + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND | os.O_BINARY + else: + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND + file_name_open = os.open(file_name, flags, 0o755) + with os.fdopen(file_name_open, "wb") as file_handler: file_handler.write(cipher_text) + file_handler.flush() else: tree = ElementTree.ElementTree(element) tree.write(file_name, encoding="UTF-8", xml_declaration=True, @@ -244,7 +291,7 @@ class ExecInfo: log_path = "" platform = "" execute_time = "" - product_params = dict() + product_info = dict() class Result: @@ -268,12 +315,12 @@ class Summary: keys = [ReportConstant.modules, ReportConstant.total, ReportConstant.passed, ReportConstant.failed, ReportConstant.blocked, ReportConstant.unavailable, - ReportConstant.ignored, ReportConstant.modules_done] + ReportConstant.ignored, ReportConstant.run_modules_] def __init__(self): self.result = Result() self.modules = None - self.modules_done = 0 + self.run_modules = 0 def get_result(self): return self.result @@ -283,11 +330,11 @@ class Summary: class Suite: - keys = [ReportConstant.module, ReportConstant.name, ReportConstant.total, - ReportConstant.passed, ReportConstant.failed, - ReportConstant.blocked, ReportConstant.ignored, - ReportConstant.time] - module = ReportConstant.empty_name + keys = [ReportConstant.module_name_, ReportConstant.name, + ReportConstant.total, ReportConstant.passed, + ReportConstant.failed, ReportConstant.blocked, + ReportConstant.ignored, ReportConstant.time] + module_name = ReportConstant.empty_name name = "" time = "" @@ -301,14 +348,14 @@ class Suite: def set_cases(self, element): if len(element) == 0: - LOG.warning("%s has no testcase", - element.get(ReportConstant.name, "")) + LOG.debug("%s has no testcase", + element.get(ReportConstant.name, "")) return # get case context and add to self.cases for child in element: case = Case() - case.module = self.module + case.module_name = self.module_name for key, value in child.items(): setattr(case, key, value) if len(child) > 0: @@ -325,7 +372,7 @@ class Suite: class Case: - module = ReportConstant.empty_name + module_name = ReportConstant.empty_name name = ReportConstant.empty_name classname = ReportConstant.empty_name status = "" @@ -405,27 +452,31 @@ class VisionHelper: "None") exec_info.host_info = platform.platform() start_time = self.summary_element.get(ReportConstant.start_time, "") + if not start_time: + start_time = self.summary_element.get("start_time", "") end_time = self.summary_element.get(ReportConstant.end_time, "") + if not end_time: + end_time = self.summary_element.get("end_time", "") exec_info.test_time = "%s/ %s" % (start_time, end_time) start_time = time.mktime(time.strptime( start_time, ReportConstant.time_format)) end_time = time.mktime(time.strptime( end_time, ReportConstant.time_format)) - exec_info.execute_time = self._get_execute_time(round( + exec_info.execute_time = self.get_execute_time(round( end_time - start_time, 3)) exec_info.log_path = os.path.abspath(os.path.join(report_path, "log")) try: - product_params = self.summary_element.get( - ReportConstant.product_params, "") - if product_params: - exec_info.product_params = literal_eval(str(product_params)) + product_info = self.summary_element.get( + ReportConstant.product_info, "") + if product_info: + exec_info.product_info = literal_eval(str(product_info)) except SyntaxError as error: LOG.error("summary report error: %s", error.args) return exec_info @classmethod - def _get_execute_time(cls, second_time): + def get_execute_time(cls, second_time): hour, day = 0, 0 second, minute = second_time % 60, second_time // 60 if minute > 0: @@ -445,8 +496,8 @@ class VisionHelper: summary = Summary() summary.modules = self.summary_element.get( ReportConstant.modules, 0) - summary.modules_done = self.summary_element.get( - ReportConstant.modules_done, 0) + summary.run_modules = self.summary_element.get( + ReportConstant.run_modules, 0) summary.result.total = int(self.summary_element.get( ReportConstant.tests, 0)) summary.result.failed = int( @@ -466,8 +517,8 @@ class VisionHelper: suites = [] for child in self.summary_element: suite = Suite() - suite.module = child.get(ReportConstant.module, - ReportConstant.empty_name) + suite.module_name = child.get(ReportConstant.module_name, + ReportConstant.empty_name) suite.name = child.get(ReportConstant.name, "") suite.message = child.get(ReportConstant.message, "") suite.result.total = int(child.get(ReportConstant.tests)) if \ @@ -526,34 +577,39 @@ class VisionHelper: value = self._get_hidden_style_value(getattr( exec_info, key, "None")) file_context = self._render_key(prefix, key, value, file_context) - file_context = self._render_product_params(exec_info, file_context, - prefix) + file_context = self._render_product_info(exec_info, file_context, + prefix) return file_context - def _render_product_params(self, exec_info, file_context, prefix): - """construct product params context and render it to file context - product params sample: + def _render_product_info(self, exec_info, file_context, prefix): + """construct product info context and render it to file context + + rendered product info sample: key: value key: value + + Args: + exec_info: dict that used to update file_content + file_context: exist html content + prefix: target replace prefix key + + Returns: + updated file context that includes rendered product info """ row_start = True try: - keys = list(exec_info.product_params.keys()) + keys = list(exec_info.product_info.keys()) except AttributeError: - LOG.error("product params error %s", exec_info.product_params) + LOG.error("product info error %s", exec_info.product_info) keys = [] - if ReportConstant.log_path_title not in keys: - keys.append(ReportConstant.log_path_title) - exec_info.product_params[ReportConstant.log_path_title] = \ - exec_info.log_path render_value = "" for key in keys: - value = exec_info.product_params[key] + value = exec_info.product_info[key] if row_start: render_value = "%s\n" % render_value render_value = "{}{}".format( @@ -563,9 +619,8 @@ class VisionHelper: row_start = not row_start if not row_start: render_value = "%s\n" % render_value - file_context = self._render_key(prefix, ReportConstant.product_params, + file_context = self._render_key(prefix, ReportConstant.product_info_, render_value, file_context) - exec_info.product_params.pop(ReportConstant.log_path_title) return file_context def _get_exec_info_td(self, key, value, row_start): @@ -646,7 +701,7 @@ class VisionHelper: Operate - base + {suite.module_name} {suite.name} {suite.result.total} {suite.result.passed} @@ -735,14 +790,14 @@ class VisionHelper: Module Testsuite - Test + Testcase Time
Result - {case.module} + {case.module_name} {case.classname} {case.name} {case.time} @@ -794,8 +849,8 @@ class VisionHelper: " " "
\n" " %s\n" - "\n" % (case.module, case.classname, case.name, case.time, - result, rendered_result)) + "\n" % (case.module_name, case.classname, case.name, + case.time, result, rendered_result)) return case_td_context @classmethod @@ -811,7 +866,7 @@ class VisionHelper: "\n" \ " Module\n" \ " Testsuite\n" \ - " Test\n" \ + " Testcase\n" \ " Time\n" \ "
\n" \ @@ -841,10 +896,10 @@ class VisionHelper: - {suite.module}#{suite.name} + {suite.module_name}#{suite.name} or - {case.module}#{case.classname}#{case.name} + {case.module_name}#{case.classname}#{case.name}
{case.result/status} @@ -896,17 +951,18 @@ class VisionHelper: failure_case_td_context = "\n" if index % 2 == 0 else \ "\n" if result == ReportConstant.unavailable: - test_context = "%s#%s" % (case.module, case.name) + test_context = "%s#%s" % (case.module_name, case.name) href_id = suite_name else: test_context = \ - "%s#%s#%s" % (case.module, case.classname, case.name) + "%s#%s#%s" % (case.module_name, case.classname, case.name) href_id = "%s.%s" % (suite_name, case.name) details_context = case.message if details_context: details_context = str(details_context).replace("<", "<"). \ replace(">", ">").replace("\\r\\n", "
"). \ - replace("\\n", "
").replace("\n", "
") + replace("\\n", "
").replace("\n", "
"). \ + replace(" ", " ") failure_case_td_context = "{}{}".format( failure_case_td_context, " %s\n" @@ -943,11 +999,21 @@ class VisionHelper: @staticmethod def generate_report(summary_vision_path, report_context): - vision_file = open(summary_vision_path, "wb") + if platform.system() == "Windows": + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND | os.O_BINARY + else: + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND + vision_file_open = os.open(summary_vision_path, flags, 0o755) + vision_file = os.fdopen(vision_file_open, "wb") if check_pub_key_exist(): - cipher_text = do_rsa_encrypt(report_context) + try: + cipher_text = do_rsa_encrypt(report_context) + except ParamError as error: + LOG.error(error, error_no=error.error_no) + cipher_text = b"" vision_file.write(cipher_text) else: - vision_file.write(bytes(report_context, "utf-8")) + vision_file.write(bytes(report_context, "utf-8", "ignore")) + vision_file.flush() vision_file.close() LOG.info("generate vision report: %s", summary_vision_path) diff --git a/src/xdevice/_core/report/result_reporter.py b/src/xdevice/_core/report/result_reporter.py index aa7b4fc011a32b86719b660d74ad329bde826508..300292f45d3bf07b9de2d38db254dae016a408c1 100755 --- a/src/xdevice/_core/report/result_reporter.py +++ b/src/xdevice/_core/report/result_reporter.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,54 +17,69 @@ # import os +import platform import shutil import time import zipfile from ast import literal_eval +from _core.interface import IReporter +from _core.plugin import Plugin +from _core.constants import ModeType +from _core.constants import TestType from _core.logger import platform_logger +from _core.exception import ParamError +from _core.utils import get_filename_extension from _core.report.encrypt import check_pub_key_exist from _core.report.encrypt import do_rsa_encrypt from _core.report.encrypt import get_file_summary from _core.report.reporter_helper import DataHelper +from _core.report.reporter_helper import ExecInfo from _core.report.reporter_helper import VisionHelper from _core.report.reporter_helper import ReportConstant LOG = platform_logger("ResultReporter") -class ResultReporter: +@Plugin(type=Plugin.REPORTER, id=TestType.all) +class ResultReporter(IReporter): + summary_report_result = [] - def __init__(self, report_path, task_info): - self.report_path = report_path - self.task_info = task_info - self.data_helper = DataHelper() - self.vision_helper = VisionHelper() - self.summary_data_path = os.path.join( - self.report_path, ReportConstant.summary_data_report) - self.exec_info = task_info + def __init__(self): + self.report_path = None + self.task_info = None + self.summary_data_path = None + self.summary_data_str = "" + self.exec_info = None + self.parsed_data = None + self.data_helper = None + self.vision_helper = None - def generate_reports(self): + def __generate_reports__(self, report_path, **kwargs): LOG.info("") LOG.info("**************************************************") LOG.info("************** Start generate reports ************") LOG.info("**************************************************") LOG.info("") - # generate data report - self._generate_data_report() + if self._check_params(report_path, **kwargs): + # generate data report + self._generate_data_report() + + # generate vision reports + self._generate_vision_reports() - # generate vision reports - self._generate_vision_reports() + # generate task info record + self._generate_task_info_record() - # generate summary ini - self._generate_summary() + # generate summary ini + self._generate_summary() - # copy reports to reports/latest folder - self._copy_report() + # copy reports to reports/latest folder + self._copy_report() - # compress report folder - self._compress_report_folder() + # compress report folder + self._compress_report_folder() LOG.info("") LOG.info("**************************************************") @@ -72,6 +87,27 @@ class ResultReporter: LOG.info("**************************************************") LOG.info("") + def _check_params(self, report_path, **kwargs): + task_info = kwargs.get("task_info", "") + if not report_path: + LOG.error("report path is wrong", error_no="00440", + ReportPath=report_path) + return False + if not task_info or not isinstance(task_info, ExecInfo): + LOG.error("task info is wrong", error_no="00441", + TaskInfo=task_info) + return False + + os.makedirs(report_path, exist_ok=True) + self.report_path = report_path + self.task_info = task_info + self.summary_data_path = os.path.join( + self.report_path, ReportConstant.summary_data_report) + self.exec_info = task_info + self.data_helper = DataHelper() + self.vision_helper = VisionHelper() + return True + def _generate_data_report(self): # initial element test_suites_element = self.data_helper.initial_suites_element() @@ -82,32 +118,24 @@ class ResultReporter: return # generate report - if not self._check_mode(ReportConstant.decc_mode): + if not self._check_mode(ModeType.decc): self.data_helper.generate_report(test_suites_element, self.summary_data_path) # set SuiteReporter.suite_report_result if not check_pub_key_exist() and not self._check_mode( - ReportConstant.decc_mode): + ModeType.decc): return - from xdevice import SuiteReporter - SuiteReporter.suite_report_result = [( - self.summary_data_path, DataHelper.to_string( - test_suites_element))] + self.set_summary_report_result( + self.summary_data_path, DataHelper.to_string(test_suites_element)) - if self._check_mode(ReportConstant.decc_mode): + if self._check_mode(ModeType.decc): try: - from devicetest.agent.auth_server import Handler + from agent.decc import Handler from xdevice import Scheduler + LOG.info("upload task summary result to decc") Handler.upload_task_summary_results( - SuiteReporter.suite_report_result[0][1]) - tmp_element = self.data_helper.initial_suites_element() - for child in test_suites_element: - result, _ = Scheduler.get_script_result(child) - if result == "Passed": - tmp_element.append(child) - SuiteReporter.suite_report_result = [( - self.summary_data_path, DataHelper.to_string(tmp_element))] + self.get_result_of_summary_report()) except ModuleNotFoundError as error: LOG.error("module not found %s", error.args) @@ -128,12 +156,13 @@ class ResultReporter: total = int(root.get(ReportConstant.tests, 0)) modules[module_name] = modules.get(module_name, 0) + total - self._append_product_params(test_suites_attributes, root) + self._append_product_info(test_suites_attributes, root) for child in root: child.tail = self.data_helper.LINE_BREAK_INDENT - if not child.get(ReportConstant.module) or child.get( - ReportConstant.module) == ReportConstant.empty_name: - child.set(ReportConstant.module, module_name) + if not child.get(ReportConstant.module_name) or child.get( + ReportConstant.module_name) == \ + ReportConstant.empty_name: + child.set(ReportConstant.module_name, module_name) self._check_tests_and_unavailable(child) test_suite_elements.append(child) for update_attribute in need_update_attributes: @@ -156,7 +185,7 @@ class ResultReporter: if modules_zero: LOG.info("the total tests of %s module is 0", ",".join( modules_zero)) - test_suites_attributes[ReportConstant.modules_done] = \ + test_suites_attributes[ReportConstant.run_modules] = \ len(modules) - len(modules_zero) test_suites_attributes[ReportConstant.modules] = len(modules) self.data_helper.set_element_attributes(test_suites_element, @@ -175,37 +204,36 @@ class ResultReporter: ReportConstant.name), total, unavailable) @classmethod - def _append_product_params(cls, test_suites_attributes, root): - product_params = root.get(ReportConstant.product_params, "") - if not product_params: + def _append_product_info(cls, test_suites_attributes, root): + product_info = root.get(ReportConstant.product_info, "") + if not product_info: return try: - product_params = literal_eval(str(product_params)) + product_info = literal_eval(str(product_info)) except SyntaxError as error: LOG.error("%s %s", root.get(ReportConstant.name, ""), error.args) - return + product_info = {} - if not test_suites_attributes[ReportConstant.product_params]: - test_suites_attributes[ReportConstant.product_params] = \ - product_params + if not test_suites_attributes[ReportConstant.product_info]: + test_suites_attributes[ReportConstant.product_info] = \ + product_info return - for key, value in product_params.items(): + for key, value in product_info.items(): exist_value = test_suites_attributes[ - ReportConstant.product_params].get(key, "") + ReportConstant.product_info].get(key, "") if not exist_value: test_suites_attributes[ - ReportConstant.product_params][key] = value + ReportConstant.product_info][key] = value continue if value in exist_value: continue - test_suites_attributes[ReportConstant.product_params][key] = \ + test_suites_attributes[ReportConstant.product_info][key] = \ "%s,%s" % (exist_value, value) @classmethod def _get_module_name(cls, data_report, root): # get module name from data report - from _core.utils import get_filename_extension module_name = get_filename_extension(data_report)[0] if "report" in module_name or "summary" in module_name or \ "<" in data_report or ">" in data_report: @@ -225,8 +253,8 @@ class ResultReporter: ReportConstant.errors: 0, ReportConstant.disabled: 0, ReportConstant.failures: 0, ReportConstant.tests: 0, ReportConstant.ignored: 0, ReportConstant.unavailable: 0, - ReportConstant.product_params: self.task_info.product_params, - ReportConstant.modules: 0, ReportConstant.modules_done: 0} + ReportConstant.product_info: self.task_info.product_info, + ReportConstant.modules: 0, ReportConstant.run_modules: 0} need_update_attributes = [ReportConstant.tests, ReportConstant.ignored, ReportConstant.failures, ReportConstant.disabled, @@ -235,31 +263,43 @@ class ResultReporter: return test_suites_attributes, need_update_attributes def _generate_vision_reports(self): - if not self.summary_data_report_exist: + if not self._check_mode(ModeType.decc) and not \ + self.summary_data_report_exist: LOG.error("summary data report not exists") return - if check_pub_key_exist(): - from xdevice import SuiteReporter - if not SuiteReporter.get_report_result(): + if check_pub_key_exist() or self._check_mode(ModeType.decc): + if not self.summary_report_result_exists(): LOG.error("summary data report not exists") return - self.summary_data_path = SuiteReporter.get_report_result()[0][1] - SuiteReporter.clear_report_result() + self.summary_data_str = \ + self.get_result_of_summary_report() + if check_pub_key_exist(): + from xdevice import SuiteReporter + SuiteReporter.clear_report_result() # parse data - summary_element_tree = self.data_helper.parse_data_report( - self.summary_data_path) + if self.summary_data_str: + # only in decc mode and pub key, self.summary_data_str is not empty + summary_element_tree = self.data_helper.parse_data_report( + self.summary_data_str) + else: + summary_element_tree = self.data_helper.parse_data_report( + self.summary_data_path) parsed_data = self.vision_helper.parse_element_data( summary_element_tree, self.report_path, self.task_info) + self.parsed_data = parsed_data self.exec_info, summary, _ = parsed_data - if not check_pub_key_exist(): - LOG.info("Summary result: modules: %s, modules done: %s, total: " - "%s, passed: %s, failed: %s, blocked: %s, ignored: %s, " - "unavailable: %s", summary.modules, summary.modules_done, - summary.result.total, summary.result.passed, - summary.result.failed, summary.result.blocked, - summary.result.ignored, summary.result.unavailable) + + if self._check_mode(ModeType.decc): + return + + LOG.info("Summary result: modules: %s, run modules: %s, total: " + "%s, passed: %s, failed: %s, blocked: %s, ignored: %s, " + "unavailable: %s", summary.modules, summary.run_modules, + summary.result.total, summary.result.passed, + summary.result.failed, summary.result.blocked, + summary.result.ignored, summary.result.unavailable) LOG.info("Log path: %s", self.exec_info.log_path) # generate summary vision report @@ -298,18 +338,23 @@ class ResultReporter: @property def summary_data_report_exist(self): - if self._check_mode(ReportConstant.decc_mode): - return False - return os.path.exists(self.summary_data_path) or ( - "<" in self.summary_data_path and ">" in self.summary_data_path) + return "<" in self.summary_data_str or \ + os.path.exists(self.summary_data_path) @property def data_reports(self): - if check_pub_key_exist() or self._check_mode(ReportConstant.decc_mode): + if check_pub_key_exist() or self._check_mode(ModeType.decc): from xdevice import SuiteReporter suite_reports = SuiteReporter.get_report_result() - data_reports = [(suite_report[1], ReportConstant.empty_name) for - suite_report in suite_reports] + if self._check_mode(ModeType.decc): + LOG.debug("handle history result, data_reports length:{}". + format(len(suite_reports))) + SuiteReporter.clear_history_result() + SuiteReporter.append_history_result(suite_reports) + data_reports = [] + for report_path, report_result in suite_reports: + module_name = get_filename_extension(report_path)[0] + data_reports.append((report_result, module_name)) SuiteReporter.clear_report_result() return data_reports @@ -344,7 +389,8 @@ class ResultReporter: return module_name def _generate_summary(self): - if not self.summary_data_report_exist: + if not self.summary_data_report_exist or \ + self._check_mode(ModeType.decc): return summary_ini_content = \ "[default]\n" \ @@ -357,27 +403,41 @@ class ResultReporter: self.exec_info.platform, self.exec_info.test_type, self.exec_info.device_name, self.exec_info.host_info, self.exec_info.test_time, self.exec_info.execute_time) - if self.exec_info.product_params: - for key, value in self.exec_info.product_params.items(): + if self.exec_info.product_info: + for key, value in self.exec_info.product_info.items(): summary_ini_content = "{}{}".format( summary_ini_content, "%s=%s\n" % (key, value)) - summary_ini_content = "{}{}".format( - summary_ini_content, "Log Path=%s\n" % self.exec_info.log_path) + + if not self._check_mode(ModeType.factory): + summary_ini_content = "{}{}".format( + summary_ini_content, "Log Path=%s\n" % self.exec_info.log_path) # write summary_ini_content summary_filepath = os.path.join(self.report_path, ReportConstant.summary_ini) - with open(summary_filepath, 'wb') as file_handler: + + if platform.system() == "Windows": + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND | os.O_BINARY + else: + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND + summary_filepath_open = os.open(summary_filepath, flags, 0o755) + + with os.fdopen(summary_filepath_open, "wb") as file_handler: if check_pub_key_exist(): - cipher_text = do_rsa_encrypt(summary_ini_content) + try: + cipher_text = do_rsa_encrypt(summary_ini_content) + except ParamError as error: + LOG.error(error, error_no=error.error_no) + cipher_text = b"" file_handler.write(cipher_text) else: file_handler.write(bytes(summary_ini_content, 'utf-8')) + file_handler.flush() LOG.info("generate summary ini: %s", summary_filepath) def _copy_report(self): from xdevice import Scheduler - if Scheduler.upload_address: + if Scheduler.upload_address or self._check_mode(ModeType.decc): return from xdevice import Variables @@ -398,11 +458,12 @@ class ResultReporter: return def _compress_report_folder(self): + if self._check_mode(ModeType.decc): + return + if not os.path.isdir(self.report_path): LOG.error("'%s' is not folder!" % self.report_path) return - if self._check_mode(ReportConstant.decc_mode): - return # get file path list file_path_list = [] @@ -431,12 +492,149 @@ class ResultReporter: # generate hex digest, then save it to summary_report.hash hash_file = os.path.abspath(os.path.join( self.report_path, ReportConstant.summary_report_hash)) - with open(hash_file, "w") as hash_file_handler: + hash_file_open = os.open(hash_file, + os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o755) + with os.fdopen(hash_file_open, "w") as hash_file_handler: hash_file_handler.write(get_file_summary(zipped_file)) LOG.info("generate hash file: %s", hash_file) + hash_file_handler.flush() return zipped_file @classmethod def _check_mode(cls, mode): from xdevice import Scheduler return Scheduler.mode == mode + + def _generate_task_info_record(self): + # under encryption status, don't handle anything directly + if check_pub_key_exist() and not self._check_mode(ModeType.decc): + return + + # get info from command_queue + from xdevice import Scheduler + if not Scheduler.command_queue: + return + _, command, report_path = Scheduler.command_queue[-1] + + # handle parsed data + record = self._parse_record_from_data(command, report_path) + + def encode(content): + # inner function to encode + return ' '.join([bin(ord(c)).replace('0b', '') for c in content]) + + # write into file + import json + record_file = os.path.join(self.report_path, + ReportConstant.task_info_record) + _record_json = json.dumps(record, indent=2) + + with open(file=record_file, mode="wb") as file: + if Scheduler.mode == ModeType.decc: + # under decc, write in encoded text + file.write(bytes(encode(_record_json), encoding="utf-8")) + else: + # others, write in plain text + file.write(bytes(_record_json, encoding="utf-8")) + + LOG.info("generate record file: %s", record_file) + + def _parse_record_from_data(self, command, report_path): + record = dict() + if self.parsed_data: + _, _, suites = self.parsed_data + unsuccessful = dict() + module_set = set() + for suite in suites: + module_set.add(suite.module_name) + + failed = unsuccessful.get(suite.module_name, []) + # because suite not contains case's some attribute, + # for example, 'module', 'classname', 'name' . so + # if unavailable, only add module's name into list. + if int(suite.result.unavailable) > 0: + failed.append(suite.module_name) + else: + # others, get key attributes join string + for case in suite.get_cases(): + if not case.is_passed(): + failed.append( + "{}#{}".format(case.classname, case.name)) + unsuccessful.update({suite.module_name: failed}) + data_reports = self._get_data_reports(module_set) + record = {"command": command, + "session_id": os.path.split(report_path)[-1], + "report_path": report_path, + "unsuccessful_params": unsuccessful, + "data_reports": data_reports + } + return record + + def _get_data_reports(self, module_set): + data_reports = dict() + if self._check_mode(ModeType.decc): + from xdevice import SuiteReporter + for module_name, report_path, report_result in \ + SuiteReporter.get_history_result_list(): + if module_name in module_set: + data_reports.update({module_name: report_path}) + else: + for report_path, module_name in self.data_reports: + if module_name == ReportConstant.empty_name: + root = self.data_helper.parse_data_report(report_path) + module_name = self._get_module_name(report_path, root) + if module_name in module_set: + data_reports.update({module_name: report_path}) + + return data_reports + + @classmethod + def get_task_info_params(cls, history_path): + # under encryption status, don't handle anything directly + if check_pub_key_exist() and not cls._check_mode(ModeType.decc): + return () + + def decode(content): + return ''.join([chr(i) for i in [int(b, 2) for b in + content.split(' ')]]) + + record_path = os.path.join(history_path, + ReportConstant.task_info_record) + if not os.path.exists(record_path): + LOG.error("%s not exists!", ReportConstant.task_info_record) + return () + + import json + from xdevice import Scheduler + with open(record_path, mode="rb") as file: + if Scheduler.mode == ModeType.decc: + # under decc, read from encoded text + result = json.loads(decode(file.read().decode("utf-8"))) + else: + # others, read from plain text + result = json.loads(file.read()) + if not len(result.keys()) == 5: + LOG.error("%s error!", ReportConstant.task_info_record) + return () + + return result["session_id"], result["command"], result["report_path"],\ + result["unsuccessful_params"], result["data_reports"] + + @classmethod + def set_summary_report_result(cls, summary_data_path, result_xml): + cls.summary_report_result.clear() + cls.summary_report_result.append((summary_data_path, result_xml)) + + @classmethod + def get_result_of_summary_report(cls): + if cls.summary_report_result: + return cls.summary_report_result[0][1] + + @classmethod + def summary_report_result_exists(cls): + return True if cls.summary_report_result else False + + @classmethod + def get_path_of_summary_report(cls): + if cls.summary_report_result: + return cls.summary_report_result[0][0] diff --git a/src/xdevice/_core/report/suite_reporter.py b/src/xdevice/_core/report/suite_reporter.py index 9bc5a751ae7f29f70558ca06f669fe3fce890368..fb717302d21679ec3d9121cf2fce7443142e6be6 100755 --- a/src/xdevice/_core/report/suite_reporter.py +++ b/src/xdevice/_core/report/suite_reporter.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,6 +21,7 @@ import time from enum import Enum from threading import RLock +from _core.constants import ModeType from _core.logger import platform_logger from _core.report.encrypt import check_pub_key_exist from _core.report.reporter_helper import DataHelper @@ -32,6 +33,7 @@ SUITE_REPORTER_LOCK = RLock() class ResultCode(Enum): UNKNOWN = -1010 + BLOCKED = -1 PASSED = 0 FAILED = 1 SKIPPED = 2 @@ -41,6 +43,7 @@ class SuiteReporter: suite_list = [] suite_report_result = [] failed_case_list = [] + history_report_result = [] def __init__(self, results, report_name, report_path=None, **kwargs): """ @@ -55,11 +58,11 @@ class SuiteReporter: self.report_name = report_name self.report_path = report_path self.suite_data_path = os.path.join( - self.report_path, report_name + - self.data_helper.DATA_REPORT_SUFFIX) + self.report_path, "%s%s" % ( + report_name, self.data_helper.DATA_REPORT_SUFFIX)) self.args = kwargs from xdevice import Scheduler - if not check_pub_key_exist() and Scheduler.mode != "decc": + if not check_pub_key_exist() and Scheduler.mode != ModeType.decc: SuiteReporter.suite_report_result.clear() def create_empty_report(self): @@ -83,6 +86,10 @@ class SuiteReporter: "", self.data_helper.LINE_BREAK test_suite_attributes[ReportConstant.unavailable] = 1 test_suite_attributes[ReportConstant.message] = suite_result.stacktrace + + from xdevice import Scheduler + if Scheduler.mode == ModeType.decc: + test_suite_attributes[ReportConstant.result] = ReportConstant.false self.data_helper.set_element_attributes(test_suite_element, test_suite_attributes) @@ -92,7 +99,7 @@ class SuiteReporter: # generate report if test_suites_element: from xdevice import Scheduler - if Scheduler.mode != "decc": + if Scheduler.mode != ModeType.decc: self.data_helper.generate_report(test_suites_element, self.suite_data_path) SuiteReporter.append_report_result(( @@ -155,8 +162,11 @@ class SuiteReporter: ReportConstant.tests: 0, ReportConstant.ignored: 0, ReportConstant.unavailable: 0, - ReportConstant.product_params: self.args.get( - ReportConstant.product_params, "")} + ReportConstant.product_info: self.args.get( + ReportConstant.product_info_, "")} + if self.args.get(ReportConstant.module_name, ""): + test_suites_attributes[ReportConstant.name] = self.args.get( + ReportConstant.module_name, "") need_update_attributes = [ReportConstant.time, ReportConstant.errors, ReportConstant.tests, ReportConstant.ignored, ReportConstant.disabled, @@ -192,7 +202,7 @@ class SuiteReporter: child = test_case_elements[-1] child.tail = self.data_helper.LINE_BREAK_INDENT else: - LOG.error("no case executed") + LOG.debug("no case executed") test_suite_element.extend(test_case_elements) # set test suite attributes @@ -237,12 +247,18 @@ class SuiteReporter: ReportConstant.message: suite_result.stacktrace } + if self.args.get(ReportConstant.module_name, ""): + test_suite_attributes[ReportConstant.module_name] = self.args.get( + ReportConstant.module_name, "") return test_suite_element, test_suite_attributes def _initial_test_case(self, case_result): test_case_element = self.data_helper.initial_case_element() - case_stacktrace = str(case_result.stacktrace).replace(chr(2), "")\ - .replace(chr(15), "").replace(chr(16), "") + case_stacktrace = str(case_result.stacktrace) + for char_index in range(32): + if char_index in [10, 13]: # chr(10): LF, chr(13): CR + continue + case_stacktrace = case_stacktrace.replace(chr(char_index), "") test_case_attributes = {ReportConstant.name: case_result.test_name, ReportConstant.status: "", ReportConstant.time: float( @@ -257,11 +273,13 @@ class SuiteReporter: @classmethod def clear_report_result(cls): with SUITE_REPORTER_LOCK: + LOG.debug("clear_report_result") cls.suite_report_result.clear() @classmethod def clear_failed_case_list(cls): with SUITE_REPORTER_LOCK: + LOG.debug("clear_failed_case_list") cls.failed_case_list.clear() @classmethod @@ -282,36 +300,81 @@ class SuiteReporter: @classmethod def _upload_case_result(cls, result_str): from xdevice import Scheduler - if Scheduler.mode != ReportConstant.decc_mode: + if Scheduler.mode != ModeType.decc: return element = DataHelper.parse_data_report(result_str) + if len(element) == 0: + LOG.debug("%s is error", result_str) + return + element = element[0] result, error_msg = Scheduler.get_script_result(element) case_name = element.get(ReportConstant.name, "") - if result != "Passed": - cls.failed_case_list.append(case_name) try: - from devicetest.agent.auth_server import Handler + from agent.decc import Handler + LOG.info("upload case result to decc") Handler.upload_case_result(case_name, result, error_msg) except ModuleNotFoundError as error: from xdevice import Scheduler - if Scheduler.mode == ReportConstant.decc_mode: + if Scheduler.mode == ModeType.decc: LOG.error("module not found %s", error.args) @classmethod def get_report_result(cls): with SUITE_REPORTER_LOCK: + LOG.debug("get_report_result, length is {}". + format(len(cls.suite_report_result))) return SuiteReporter.suite_report_result @classmethod def set_suite_list(cls, suite_list): + LOG.debug("set_suite_list, length is {}".format(len(suite_list))) cls.suite_list = suite_list @classmethod def get_suite_list(cls): with SUITE_REPORTER_LOCK: + LOG.debug("get_suite_list, length is {}". + format(len(cls.suite_list))) return SuiteReporter.suite_list @classmethod def get_failed_case_list(cls): with SUITE_REPORTER_LOCK: + LOG.debug("get_failed_case_list, length is {}". + format(len(cls.failed_case_list))) return SuiteReporter.failed_case_list + + @classmethod + def append_history_result(cls, suite_reports): + from _core.utils import get_filename_extension + with SUITE_REPORTER_LOCK: + LOG.debug("append_history_result,suite_reports length is {}". + format(len(suite_reports))) + for report_path, report_result in suite_reports: + module_name = get_filename_extension(report_path)[0] + cls.history_report_result. \ + append((module_name, report_path, report_result)) + + @classmethod + def clear_history_result(cls): + with SUITE_REPORTER_LOCK: + LOG.debug("clear_history_result") + cls.history_report_result.clear() + + @classmethod + def get_history_result_by_module(cls, name): + with SUITE_REPORTER_LOCK: + LOG.debug("get_history_result_by_module,module_name:{}". + format(name)) + for module_name, report_path, report_result in \ + cls.history_report_result: + if name == module_name: + return report_path, report_result + return "", "" + + @classmethod + def get_history_result_list(cls): + with SUITE_REPORTER_LOCK: + LOG.debug("get_history_result_list,length is {}". + format(len(cls.history_report_result))) + return cls.history_report_result diff --git a/src/xdevice/_core/resource/config/user_config.xml b/src/xdevice/_core/resource/config/user_config.xml index c576199e86316846dca24a446cb7aaf5dfea1d12..5439585176ed811b6236df8389b9aa7185fee04d 100755 --- a/src/xdevice/_core/resource/config/user_config.xml +++ b/src/xdevice/_core/resource/config/user_config.xml @@ -1,65 +1,66 @@ - - - - - - true - - - - - cmd - 115200 - 8 - 1 - 20 - - - - deploy - 115200 - - - - - - cmd - 115200 - 8 - 1 - 1 - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + cmd + 115200 + 8 + 1 + 20 + + + + deploy + 115200 + + + + + + cmd + 115200 + 8 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + INFO + diff --git a/src/xdevice/_core/resource/template/report.html b/src/xdevice/_core/resource/template/report.html old mode 100755 new mode 100644 index af976ca9ff3abe0099913a128d757d95d5b09514..8f72eb46cd9da8be35ab8a1c8c8a3f0569d3ca58 --- a/src/xdevice/_core/resource/template/report.html +++ b/src/xdevice/_core/resource/template/report.html @@ -149,7 +149,7 @@ table.summary th.modules { padding: 20px 35px 0 34px; } - table.summary th.modules-done, table.summary th.total-tests, table.summary th.passed { + table.summary th.run-modules, table.summary th.total-tests, table.summary th.passed { padding: 20px 35px 0 0; } table.summary th.failed, table.summary th.blocked, table.summary th.ignored { @@ -161,7 +161,7 @@ table.summary td.modules { padding: 5px 35px 20px 34px; } - table.summary td.modules-done, table.summary td.total-tests, table.summary td.passed { + table.summary td.run-modules, table.summary td.total-tests, table.summary td.passed { padding: 5px 35px 20px 0; } table.summary td.failed, table.summary td.blocked, table.summary td.ignored { @@ -378,20 +378,24 @@ } table.failure-test td.test { + vertical-align: top; width: 569px; - padding: 0 0 0 20px; + padding: 2px 0 0 20px; } table.failure-test td.status { + vertical-align: top; width: 11px; - padding: 0 0 0 0; + padding: 8px 0 0 0; } table.failure-test td.result { + vertical-align: top; width: 80px; - padding: 0 0 0 0; + padding: 2px 0 0 0; } table.failure-test td.details { + vertical-align: top; width: 480px; - padding: 0 0 0 0; + padding: 2px 0 0 0; } div.hidden { @@ -433,13 +437,13 @@ Execution Time: - + - + @@ -449,7 +453,7 @@ - + diff --git a/src/xdevice/_core/testkit/__init__.py b/src/xdevice/_core/testkit/__init__.py old mode 100755 new mode 100644 diff --git a/src/xdevice/_core/testkit/json_parser.py b/src/xdevice/_core/testkit/json_parser.py index f616aa7e97c0ede63794d867c63d8fefff9e4069..921a43dc4843bc97756bd24dfbd2b67e3a15fd0b 100755 --- a/src/xdevice/_core/testkit/json_parser.py +++ b/src/xdevice/_core/testkit/json_parser.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -18,7 +18,7 @@ import json import os - +import stat from _core.exception import ParamError from _core.logger import platform_logger from _core.plugin import Config @@ -68,11 +68,16 @@ class JsonParser: else: if not os.path.exists(path_or_content): raise ParamError("The json file {} does not exist".format( - path_or_content)) - with open(path_or_content, encoding="utf-8") as file_content: + path_or_content), error_no="00110") + + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(path_or_content, flags, modes), + "r") as file_content: json_content = json.load(file_content) except (TypeError, ValueError, AttributeError) as error: - raise ParamError("%s %s" % (path_or_content, error)) + raise ParamError("json file error: %s %s" % ( + path_or_content, error), error_no="00111") self._check_config(json_content) # set self.config diff --git a/src/xdevice/_core/testkit/kit_lite.py b/src/xdevice/_core/testkit/kit_lite.py index fc541a1650c2ff8ed99fdeea0203e583755fd80f..5ea3989de1d7867b151ea85f062bae792ecbe8c6 100755 --- a/src/xdevice/_core/testkit/kit_lite.py +++ b/src/xdevice/_core/testkit/kit_lite.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -23,16 +23,19 @@ import string import subprocess import shutil import platform +import glob from _core.logger import platform_logger from _core.plugin import Plugin from _core.config.config_manager import UserConfigManager from _core.constants import CKit +from _core.constants import ConfigConst from _core.constants import ComType from _core.constants import DeviceLiteKernel from _core.constants import DeviceTestType from _core.exception import LiteDeviceMountError from _core.exception import ParamError +from _core.exception import LiteDeviceError from _core.interface import ITestKit from _core.utils import get_config_value from _core.utils import get_file_absolute_path @@ -40,9 +43,11 @@ from _core.utils import get_local_ip from _core.utils import get_test_component_version from _core.exception import LiteDeviceConnectError from _core.constants import DeviceLabelType +from _core.environment.manager_env import DeviceAllocationState -__all__ = ["DeployKit", "MountKit", "RootFsKit", "QueryKit"] +__all__ = ["DeployKit", "MountKit", "RootFsKit", "QueryKit", "LiteShellKit", + "LiteAppInstallKit"] LOG = platform_logger("KitLite") RESET_CMD = "0xEF, 0xBE, 0xAD, 0xDE, 0x0C, 0x00, 0x87, 0x78, 0x00, 0x00, " \ @@ -58,68 +63,53 @@ class DeployKit(ITestKit): self.paths = "" def __check_config__(self, config): - self.timeout = str(int(get_config_value('timeout', config, - is_list=False)) * 1000) + self.timeout = str(int(get_config_value( + 'timeout', config, is_list=False, default=0)) * 1000) self.burn_file = get_config_value('burn_file', config, is_list=False) burn_command = get_config_value('burn_command', config, is_list=False, default=RESET_CMD) self.burn_command = burn_command.replace(" ", "").split(",") self.paths = get_config_value('paths', config) - if not self.timeout or not self.burn_file or not self.burn_command: - msg = "The config for deploy kit is invalid with timeout:{} " \ - "burn_file:{} burn_command:{}".format(self.timeout, - self.burn_file, - self.burn_command) - LOG.error(msg) - raise TypeError(msg) + if self.timeout == "0" or not self.burn_file: + msg = "The config for deploy kit is invalid with timeout:{}, " \ + "burn_file:{}".format(self.timeout, self.burn_file) + raise ParamError(msg, error_no="00108") - def __setup__(self, device, **kwargs): - """ - Execute reset command on the device by cmd serial port and then upload - patch file by deploy tool. - Parameters: - device: the instance of LocalController with one or more - ComController - """ - del kwargs - LOG.debug( - "Deploy kit params:{}".format(self.get_plugin_config().__dict__)) - cmd_com = device.com_dict.get(ComType.cmd_com) - LOG.info("The cmd com is {}".format(cmd_com.serial_port)) + def _reset(self, device): + cmd_com = device.device.com_dict.get(ComType.cmd_com) try: cmd_com.connect() cmd_com.execute_command( command='AT+RST={}'.format(self.timeout)) cmd_com.close() except (LiteDeviceConnectError, IOError) as error: + device.device_allocation_state = DeviceAllocationState.unusable LOG.error( "The exception {} happened in deploy kit running".format( - error)) - return False + error), error_no=getattr(error, "error_no", + "00000")) + raise LiteDeviceError("%s port set_up wifiiot failed" % + cmd_com.serial_port, + error_no=getattr(error, "error_no", + "00000")) finally: if cmd_com: cmd_com.close() + + def _send_file(self, device): burn_tool_name = "HiBurn.exe" if os.name == "nt" else "HiBurn" burn_tool_path = get_file_absolute_path( os.path.join("tools", burn_tool_name), self.paths) - if not os.path.exists(burn_tool_path): - LOG.error('The burn tool {} does not exist, please check!'.format( - burn_tool_path)) - return False patch_file = get_file_absolute_path(self.burn_file, self.paths) - if not os.path.exists(patch_file): - LOG.error('The patch file {} does not exist, please check!'.format( - patch_file)) - return False - deploy_serial_port = device.com_dict.get( + deploy_serial_port = device.device.com_dict.get( ComType.deploy_com).serial_port - deploy_baudrate = device.com_dict.get(ComType.deploy_com).baund_rate + deploy_baudrate = device.device.com_dict.\ + get(ComType.deploy_com).baud_rate port_number = re.findall(r'\d+$', deploy_serial_port) if not port_number: - LOG.error( - "The config of serial port {} to deploy is invalid".format( - deploy_serial_port)) - return False + raise LiteDeviceError("The config of serial port {} to deploy is " + "invalid".format(deploy_serial_port), + error_no="00108") new_temp_tool_path = copy_file_as_temp(burn_tool_path, 10) cmd = '{} -com:{} -bin:{} -signalbaud:{}' \ .format(new_temp_tool_path, port_number[0], patch_file, @@ -131,9 +121,22 @@ class DeployKit(ITestKit): 'Deploy kit to execute burn tool finished with return_code: {} ' 'output: {}'.format(return_code, out)) os.remove(new_temp_tool_path) - if 0 == return_code: - return True - return False + if 0 != return_code: + device.device_allocation_state = DeviceAllocationState.unusable + raise LiteDeviceError("%s port set_up wifiiot failed" % + deploy_serial_port, error_no="00402") + + def __setup__(self, device, **kwargs): + """ + Execute reset command on the device by cmd serial port and then upload + patch file by deploy tool. + Parameters: + device: the instance of LocalController with one or more + ComController + """ + del kwargs + self._reset(device) + self._send_file(device) def __teardown__(self, device): pass @@ -159,8 +162,8 @@ class MountKit(ITestKit): if not self.mount_list: msg = "The config for mount kit is invalid with mount:{}" \ .format(self.mount_list) - LOG.error(msg) - raise TypeError(msg) + LOG.error(msg, error_no="00108") + raise TypeError("Load Error[00108]") def mount_on_board(self, device=None, remote_info=None, case_type=""): """ @@ -180,7 +183,8 @@ class MountKit(ITestKit): True or False, represent init Failed or success """ if not remote_info: - raise ParamError("failed to get server environment") + raise ParamError("failed to get server environment", + error_no="00108") linux_host = remote_info.get("ip", "") linux_directory = remote_info.get("dir", "") @@ -188,40 +192,47 @@ class MountKit(ITestKit): liteos_commands = ["cd /", "umount device_directory", "mount nfs_ip:nfs_directory device" "_directory nfs"] - linux_commands = ["cd /", "umount device_directory", - "mount -t nfs -o nolock -o tcp nfs_ip:nfs_directory" - "device_directory", "chmod 755 -R device_directory"] + linux_commands = ["cd /%s" % "storage", + "fuser -k /%s/%s" % ("storage", "device_directory"), + "umount -f /%s/%s" % ("storage", "device_directory"), + "mount -t nfs -o nolock -o tcp nfs_ip:nfs_directory " + "/%s/%s" % ("storage", "device_directory"), + "chmod 755 -R /%s/%s" % ( + "storage", "device_directory")] if not linux_host or not linux_directory: - raise LiteDeviceMountError("nfs server miss ip or directory") - if device.device_connect_type == "local": - device.local_device.flush_input() + raise LiteDeviceMountError( + "nfs server miss ip or directory[00108]", error_no="00108") commands = [] if device.label == "ipcamera": env_result, status, _ = device.execute_command_with_timeout( - command="uname", timeout=1) + command="uname", timeout=1, retry=2) if status: - if env_result.find(DeviceLiteKernel.linux_kernel) != -1: + if env_result.find(DeviceLiteKernel.linux_kernel) != -1 or \ + env_result.find("Linux") != -1: commands = linux_commands device.__set_device_kernel__(DeviceLiteKernel.linux_kernel) else: commands = liteos_commands device.__set_device_kernel__(DeviceLiteKernel.lite_kernel) else: - raise LiteDeviceMountError("failed to get device env") + raise LiteDeviceMountError("failed to get device env[00402]", + error_no="00402") for mount_file in self.mount_list: target = mount_file.get("target", "/test_root") if target in self.mounted_dir: + LOG.debug("%s is mounted" % target) continue mkdir_on_board(device, target) + # local nfs server need use alias of dir to mount if is_remote.lower() == "false": linux_directory = get_mount_dir(linux_directory) for command in commands: command = command.replace("nfs_ip", linux_host). \ replace("nfs_directory", linux_directory).replace( - "device_directory", target) + "device_directory", target).replace("//", "/") timeout = 15 if command.startswith("mount") else 1 result, status, _ = device.execute_command_with_timeout( command=command, case_type=case_type, timeout=timeout) @@ -233,12 +244,17 @@ class MountKit(ITestKit): """ Mount the file to the board by the nfs server. """ + LOG.debug("start mount kit setup") + request = kwargs.get("request", None) if not request: - raise ParamError("MountKit setup request is None") + raise ParamError("MountKit setup request is None", + error_no="02401") device.connect() - config_manager = UserConfigManager(env=request.config.test_environment) + config_manager = UserConfigManager( + config_file=request.get(ConfigConst.configfile, ""), + env=request.get(ConfigConst.test_environment, "")) remote_info = config_manager.get_user_config("testcases/server", filter_name=self.server) @@ -271,7 +287,9 @@ class MountKit(ITestKit): else: file_local_paths.append(file_path) - config_manager = UserConfigManager(env=request.config.test_environment) + config_manager = UserConfigManager( + config_file=request.get(ConfigConst.configfile, ""), + env=request.get(ConfigConst.test_environment, "")) remote_info = config_manager.get_user_config("testcases/server", filter_name=self.server) self.remote_info = remote_info @@ -279,7 +297,7 @@ class MountKit(ITestKit): if not remote_info: err_msg = "The name of remote device {} does not match". \ format(self.remote) - LOG.error(err_msg) + LOG.error(err_msg, error_no="00403") raise TypeError(err_msg) is_remote = remote_info.get("remote", "false") if (str(get_local_ip()) == linux_host) and ( @@ -303,47 +321,53 @@ class MountKit(ITestKit): except (OSError, Exception) as exception: msg = "copy file to nfs server failed with error {}" \ .format(exception) - LOG.error(msg) - raise LiteDeviceMountError(exception) + LOG.error(msg, error_no="00403") # local copy else: - shutil.copy(_file, remote_info.get("dir")) + for count in range(1, 4): + shutil.copy(_file, remote_info.get("dir")) + if check_server_file(_file, remote_info.get("dir")): + break + else: + LOG.info( + "Trying to copy the file from {} to nfs " + "server {} times".format(_file, count)) + if count == 3: + msg = "copy {} to nfs server " \ + "failed {} times".format( + os.path.basename(_file), count) + LOG.error(msg, error_no="00403") + LOG.debug("Nfs server:{}".format(glob.glob( + os.path.join(remote_info.get("dir"), '*.*')))) + self.file_name_list.append(os.path.basename(_file)) return self.file_name_list def __teardown__(self, device): - device.execute_command_with_timeout(command="cd /", timeout=1) - for mounted_dir in self.mounted_dir: - device.execute_command_with_timeout(command="umount {}". - format(mounted_dir), - timeout=2) - device.execute_command_with_timeout(command="rm -r {}". - format(mounted_dir), + if device.__get_device_kernel__() == DeviceLiteKernel.linux_kernel: + device.execute_command_with_timeout(command="cd /storage", timeout=1) - - is_remote = self.remote_info.get("remote", "false") - for _file in self.file_name_list: - LOG.info("Trying to delete the file from nfs server". - format(_file)) - try: - if is_remote.lower() == "true": - import paramiko - client = paramiko.Transport( - (self.remote_info.get("ip"), - int(self.remote_info.get("port")))) - client.connect( - username=self.remote_info.get("username"), - password=self.remote_info.get("password")) - sftp = paramiko.SFTPClient.from_transport(client) - file_path = "{}{}".format( - self.remote_info.get("dir"), _file) - sftp.remove(file_path) - client.close() - else: - os.remove(os.path.join(self.remote_info.get("dir"), _file)) - except FileNotFoundError: - LOG.debug("delete file %s failed" % _file) + for mounted_dir in self.mounted_dir: + device.execute_command_with_timeout(command="fuser -k {}". + format(mounted_dir), + timeout=2) + device.execute_command_with_timeout(command="umount -f " + "/storage{}". + format(mounted_dir), + timeout=2) + device.execute_command_with_timeout(command="rm -r /storage{}". + format(mounted_dir), + timeout=1) + else: + device.execute_command_with_timeout(command="cd /", timeout=1) + for mounted_dir in self.mounted_dir: + device.execute_command_with_timeout(command="umount {}". + format(mounted_dir), + timeout=2) + device.execute_command_with_timeout(command="rm -r {}". + format(mounted_dir), + timeout=1) def copy_file_as_temp(original_file, str_length): @@ -369,7 +393,10 @@ def mkdir_on_board(device, dir_path): device : the L1 board dir_path: the dir path to make """ - device.execute_command_with_timeout(command="cd /", timeout=1) + if device.__get_device_kernel__() == DeviceLiteKernel.linux_kernel: + device.execute_command_with_timeout(command="cd /storage", timeout=1) + else: + device.execute_command_with_timeout(command="cd /", timeout=1) for sub_dir in dir_path.split("/"): if sub_dir in ["", "/"]: continue @@ -377,7 +404,10 @@ def mkdir_on_board(device, dir_path): timeout=1) device.execute_command_with_timeout(command="cd {}".format(sub_dir), timeout=1) - device.execute_command_with_timeout(command="cd /", timeout=1) + if device.__get_device_kernel__() == DeviceLiteKernel.linux_kernel: + device.execute_command_with_timeout(command="cd /storage", timeout=1) + else: + device.execute_command_with_timeout(command="cd /", timeout=1) def get_mount_dir(mount_dir): @@ -400,6 +430,13 @@ def get_mount_dir(mount_dir): return mount_dir +def check_server_file(local_file, target_path): + for file_list in glob.glob(os.path.join(target_path, '*.*')): + if os.path.basename(local_file) in file_list: + return True + return False + + @Plugin(type=Plugin.TEST_KIT, id=CKit.rootfs) class RootFsKit(ITestKit): def __init__(self): @@ -420,7 +457,7 @@ class RootFsKit(ITestKit): " hash_file_name:{} device_label:{}" \ .format(self.checksum_command, self.hash_file_name, self.device_label) - LOG.error(msg) + LOG.error(msg, error_no="00108") return TypeError(msg) def __setup__(self, device, **kwargs): @@ -428,7 +465,8 @@ class RootFsKit(ITestKit): # check device label if not device.label == self.device_label: - LOG.error("device label is not match '%s '" % "demo_label") + LOG.error("device label is not match '%s '" % "demo_label", + error_no="00108") return False else: report_path = self._get_report_dir() @@ -436,6 +474,8 @@ class RootFsKit(ITestKit): # execute command of checksum device.connect() + device.execute_command_with_timeout( + command="cd /", case_type=DeviceTestType.cpp_test_lite) result, _, _ = device.execute_command_with_timeout( command=self.checksum_command, case_type=DeviceTestType.cpp_test_lite) @@ -451,13 +491,16 @@ class RootFsKit(ITestKit): hash_file_name = "".join((self.hash_file_name, serial)) hash_file_path = os.path.join(report_path, hash_file_name) # write result to file - with open(hash_file_path, mode="w", encoding="utf-8") \ - as hash_file: + hash_file_path_open = os.open(hash_file_path, os.O_WRONLY | + os.O_CREAT | os.O_APPEND, 0o755) + + with os.fdopen(hash_file_path_open, mode="w") as hash_file: hash_file.write(result) + hash_file.flush() else: msg = "RootFsKit teardown, log path [%s] not exists!" \ % report_path - LOG.error(msg) + LOG.error(msg, error_no="00440") return False return True @@ -484,28 +527,37 @@ class QueryKit(ITestKit): setattr(self.mount_kit, "server", get_config_value( 'server', config, is_list=False, default="NfsServer")) self.query = get_config_value('query', config, is_list=False) + self.properties = get_config_value('properties', config, is_list=False) if not self.query: msg = "The config for query kit is invalid with query:{}" \ .format(self.query) - LOG.error(msg) + LOG.error(msg, error_no="00108") raise TypeError(msg) def __setup__(self, device, **kwargs): + LOG.debug("start query kit setup") if device.label != DeviceLabelType.ipcamera: return request = kwargs.get("request", None) if not request: - raise ParamError("the request of queryKit is None") + raise ParamError("the request of queryKit is None", + error_no="02401") self.mount_kit.__setup__(device, request=request) - device.execute_command_with_timeout(command="cd /", timeout=0.2) - output, _, _ = device.execute_command_with_timeout( - command=".{}".format(self.query), timeout=5) - product_param = {} + if device.__get_device_kernel__() == DeviceLiteKernel.linux_kernel: + device.execute_command_with_timeout(command="cd /storage", + timeout=0.2) + output, _, _ = device.execute_command_with_timeout( + command=".{}{}".format("/storage", self.query), timeout=5) + else: + device.execute_command_with_timeout(command="cd /", timeout=0.2) + output, _, _ = device.execute_command_with_timeout( + command=".{}".format(self.query), timeout=5) + product_info = {} for line in output.split("\n"): - process_product_params(line, product_param) - product_param["version"] = get_test_component_version(request.config) - request.product_params = product_param + process_product_info(line, product_info) + product_info["version"] = get_test_component_version(request.config) + request.product_info = product_info def __teardown__(self, device): if device.label != DeviceLabelType.ipcamera: @@ -515,9 +567,95 @@ class QueryKit(ITestKit): device.close() -def process_product_params(message, product_params): +@Plugin(type=Plugin.TEST_KIT, id=CKit.liteshell) +class LiteShellKit(ITestKit): + def __init__(self): + self.command_list = [] + self.tear_down_command = [] + self.paths = None + + def __check_config__(self, config): + self.command_list = get_config_value('run-command', config) + self.tear_down_command = get_config_value('teardown-command', config) + + def __setup__(self, device, **kwargs): + del kwargs + LOG.debug("LiteShellKit setup, device:{}".format(device.device_sn)) + if len(self.command_list) == 0: + LOG.info("No setup_command to run, skipping!") + return + for command in self.command_list: + run_command(device, command) + + def __teardown__(self, device): + LOG.debug("LiteShellKit teardown: device:{}".format(device.device_sn)) + if len(self.tear_down_command) == 0: + LOG.info("No teardown_command to run, skipping!") + return + for command in self.tear_down_command: + run_command(device, command) + + +def run_command(device, command): + LOG.debug("The command:{} is running".format(command)) + if command.strip() == "reset": + device.reboot() + else: + device.execute_shell_command(command) + + +@Plugin(type=Plugin.TEST_KIT, id=CKit.liteinstall) +class LiteAppInstallKit(ITestKit): + def __init__(self): + self.app_list = "" + self.is_clean = "" + self.alt_dir = "" + self.bundle_name = None + self.paths = "" + self.signature = False + + def __check_config__(self, options): + self.app_list = get_config_value('test-file-name', options) + self.is_clean = get_config_value('cleanup-apps', options, False) + self.signature = get_config_value('signature', options, False) + self.alt_dir = get_config_value('alt-dir', options, False) + if self.alt_dir and self.alt_dir.startswith("resource/"): + self.alt_dir = self.alt_dir[len("resource/"):] + self.paths = get_config_value('paths', options) + + def __setup__(self, device, **kwargs): + del kwargs + LOG.debug("LiteAppInstallKit setup, device:{}". + format(device.device_sn)) + if len(self.app_list) == 0: + LOG.info("No app to install, skipping!") + return + + for app in self.app_list: + if app.endswith(".hap"): + device.execute_command_with_timeout("cd /", timeout=1) + if self.signature: + device.execute_command_with_timeout( + command="./bin/bm set -d enable", timeout=10) + else: + device.execute_command_with_timeout( + command="./bin/bm set -s disable", timeout=10) + + device.execute_command_with_timeout( + "./bin/bm install -p %s" % app, timeout=60) + + def __teardown__(self, device): + LOG.debug("LiteAppInstallKit teardown: device:{}".format( + device.device_sn)) + if self.is_clean and str(self.is_clean).lower() == "true" \ + and self.bundle_name: + device.execute_command_with_timeout( + "./bin/bm uninstall -n %s" % self.bundle_name, timeout=90) + + +def process_product_info(message, product_info): if "The" in message: message = message[message.index("The"):] items = message[len("The "):].split(" is ") - product_params.setdefault(items[0].strip(), - items[1].strip().strip("[").strip("]")) + product_info.setdefault(items[0].strip(), + items[1].strip().strip("[").strip("]")) diff --git a/src/xdevice/_core/testkit/uikit_lite.py b/src/xdevice/_core/testkit/uikit_lite.py deleted file mode 100755 index 82080f782fda40af1d51ec5ad921cf8a78b5d705..0000000000000000000000000000000000000000 --- a/src/xdevice/_core/testkit/uikit_lite.py +++ /dev/null @@ -1,476 +0,0 @@ -#!/usr/bin/env python3 -# coding=utf-8 - -# -# Copyright (c) 2020 Huawei Device Co., Ltd. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import datetime -import platform -import signal -import os -import json -import time -import subprocess -import re -from dataclasses import dataclass - -from json import JSONDecodeError -from shutil import copyfile -from threading import Timer -from _core.constants import CKit -from _core.exception import ExecuteTerminate -from _core.exception import ParamError -from _core.logger import platform_logger -from _core.plugin import Plugin - -TIMEOUT = 90 -MAX_VALID_POSITION = 454 -LOG = platform_logger("UiKitLite") - - -def timeout_callback(proc, timeout): - try: - timeout.is_timeout = True - LOG.error("Error: execute command timeout.") - LOG.error(proc.pid) - if platform.system() != "Windows": - os.killpg(proc.pid, signal.SIGKILL) - else: - subprocess.call( - ["C:\\Windows\\System32\\taskkill", "/F", "/T", "/PID", - str(proc.pid)], - shell=False) - except (FileNotFoundError, KeyboardInterrupt, AttributeError) as error: - LOG.exception("timeout callback exception: %s" % error) - - -def get_dump_str(dump_file): - dump_str = "" - if os.path.exists(dump_file): - with open(dump_file, "r") as json_content: - dump_str = json_content.read() - return dump_str - - -def get_center_position(parsed_dict, attr_name, attr_value): - if attr_value == parsed_dict.get(attr_name, ""): - return _calculate_center_position(parsed_dict) - - child_dicts = parsed_dict.get("child", []) - for child_dict in child_dicts: - x_center, y_center = get_center_position(child_dict, attr_name, - attr_value) - if check_position(x_center, y_center): - return x_center, y_center - - return -1, -1 - - -def check_position(x_position, y_position): - return (0 <= x_position <= MAX_VALID_POSITION) and \ - (0 <= y_position <= MAX_VALID_POSITION) - - -def copy_file(destination_path, screen_file_name): - from xdevice import Variables - abs_paths = [Variables.exec_dir, Variables.top_dir, Variables.modules_dir] - for path in abs_paths: - if path: - for file_name in os.listdir(path): - if file_name.endswith(".bin") and os.path.exists(os.path.join( - path, file_name)): - dst_file = "%s%s" % (os.path.join( - destination_path, screen_file_name), ".bin") - copyfile(os.path.join(path, file_name), dst_file) - os.remove(os.path.join(path, file_name)) - break - - -def _calculate_center_position(parsed_dict): - x_value = parsed_dict.get("x", -1) - y_value = parsed_dict.get("y", -1) - if not check_position(x_value, y_value): - LOG.info("error (x, y) value, (%s, %s)" % (x_value, y_value)) - return -1, -1 - width = parsed_dict.get("width", -1) - height = parsed_dict.get("height", -1) - if not check_position(width, height): - LOG.info("error (width, height) value, (%s, %s)" % (width, height)) - return -1, -1 - return min((x_value + width / 2), 454), min((y_value + height / 2), 454) - - -def get_center(dump_str, attr_name, attr_value): - """execute hd.click method - - Parameters: - dump_str: json string - attr_name: target attribute name like 'id' or 'text' - attr_value: target attribute value - - Return: - 0: success 1: fail - """ - try: - parsed_dict = json.loads(dump_str, encoding="utf-8") - except JSONDecodeError as error: - LOG.info("format error, %s" % error.args) - return 1 - x_center, y_center = get_center_position(parsed_dict, attr_name, - attr_value) - LOG.info("(x_center, y_center) value, (%s, %s)" % (x_center, y_center)) - if check_position(x_center, y_center): - return True, (x_center, y_center) - else: - LOG.info("no valid center position (%s, %s)" % (x_center, y_center)) - return False, None - - -def get_hdc_command(command, shell=True): - if shell: - return " ".join(["hdc", "shell", command]) - else: - return " ".join(["hdc", command]) - - -def filter_json(result_str=""): - result = "no such node" - if result_str.find("{") != -1: - result_str = result_str.replace("\n", "").strip() - pattern = r"(.*)(\{.*\})(.*)" - matcher = re.match(pattern, result_str) - if matcher: - return matcher.group(2) - return result - - -@dataclass -class Timeout: - is_timeout = False - - -@Plugin(type=Plugin.TEST_KIT, id=CKit.liteuikit) -class LiteUiKit: - def __init__(self): - pass - - def __set_device__(self, device): - pass - - def __set_connect_type__(self, device): - pass - - def __check_config__(self, config): - pass - - def __setup__(self, device): - pass - - def __teardown__(self, device): - pass - - @staticmethod - def execute_hdc_cmd_with_timeout(device, command, timeout=800, - result_print=True): - """ - Executes a command on the device with timeout. - - Parameters: - device: device - command: the command to execute - timeout: time out value - result_print: - """ - from _core.environment.device_lite import get_hdc_path - cmd = [get_hdc_path(), "-p", device.serial_port.upper().replace( - "COM", "").strip()] + command.split(" ") - ret_message = "" - LOG.info("execute command: %s" % " ".join(cmd)) - - start_time = datetime.datetime.now() - LOG.info("starttime=%s with timeout=%s" % ( - start_time.strftime("%Y-%m-%d %H:%M:%S"), str(timeout))) - - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=False, - preexec_fn=os.setsid if platform.system() != 'Windows' else None) - is_timeout = Timeout() - proc_timer = Timer(timeout, timeout_callback, [proc, is_timeout]) - proc_timer.start() - - try: - ret_message = LiteUiKit._result_hdc_out_process( - proc, is_timeout, result_print) - except (ExecuteTerminate, ValueError) as exception: - LOG.exception("exception: %s", str(exception)) - finally: - ret_message = "{}{}".format( - ret_message, LiteUiKit._print_hdc_stdout(proc, result_print)) - error_message = LiteUiKit._print_hdc_stderr(proc, result_print) - - proc_timer.cancel() - proc.stdout.close() - proc.stderr.close() - - LOG.info("end time=%s delta=%s" % ( - datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), str( - (datetime.datetime.now() - start_time).seconds))) - - if proc.returncode == 0: - LOG.info('Info: execute command success. command=%s', command) - return_code = 0 - else: - LOG.error('Error: execute command failed. command=%s', command) - return_code = 1 - return ResultValue(return_code, error_message, ret_message) - - @staticmethod - def _result_hdc_out_process(proc, timeout, result_print): - result = "" - from xdevice import Scheduler - while proc.poll() is None: - if not Scheduler.is_execute: - raise ExecuteTerminate() - line = proc.stdout.readline() - line = line.strip() - if isinstance(line, bytes): - line = line.decode('utf-8', 'ignore').strip() - if line != "": - result = "%s%s%s" % (result, line, "\n") - if result_print: - LOG.info(line) - if timeout.is_timeout: - LOG.info("timeout flag is True") - timeout.is_timeout = False - break - return result - - @classmethod - def _print_hdc_stdout(cls, proc, result_print=True): - data = proc.stdout.read() - if isinstance(data, bytes): - data = data.decode('utf-8', 'ignore') - if data != "": - if not result_print: - return data - for line in data.rstrip("\n").split("\n"): - LOG.info(line) - return data.rstrip("\n") - return "" - - @classmethod - def _print_hdc_stderr(cls, proc, result_print=True): - data = proc.stderr.read() - if isinstance(data, bytes): - data = data.decode('utf-8', 'ignore') - if data != "": - if not result_print: - return data - LOG.error("----------stderr info start------------") - for error_message in data.rstrip("\n").split("\n"): - LOG.error(error_message) - LOG.error("----------stderr info ended------------") - return data.rstrip("\n") - return "" - - @staticmethod - def click(device, x_coordinate, y_coordinate, timeout=800): - press_command = "%s%s%s%s%s" % ("uievent ", str(x_coordinate), str( - y_coordinate), " ", " PRESSDOWN") - release_command = "%s%s%s%s%s" % ("uievent ", str(x_coordinate), " ", - str(y_coordinate), " RELEASE") - press_result = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(press_command, True), timeout, True) - if press_result.return_code == 0: - time.sleep(timeout / 1000) - release_result = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(release_command, True), timeout, True) - if release_result.return_code == 0: - LOG.info("click success.") - return True - return False - - @staticmethod - def click_id(device, node_id, timeout=800): - if not node_id: - raise ParamError("miss node id") - status, point = LiteUiKit.ui_dump_id( - device, node_id=node_id, timeout=timeout) - if status: - LiteUiKit.click(device, point[0], point[1], timeout) - else: - LOG.info("click failed") - - @staticmethod - def click_text(device, text="", timeout=800): - status = False - if not text: - raise ParamError("miss node text") - dump_str = LiteUiKit.ui_dump_tree( - device, timeout=timeout) - if dump_str and text: - status, point = get_center(dump_str, "text", text) - if status: - LiteUiKit.click(point[0], point[1], timeout) - else: - LOG.info("click failed") - - @staticmethod - def screen_shot(device, screen_path, file_name, timeout=800): - result_value = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command("screenshot"), timeout, True) - if result_value.return_code == 0: - retry_times = 2 - while retry_times > 0: - command = "rfile user/log/screenshot.bin" - if LiteUiKit.download_file(device, command, timeout=1400): - img_dir = os.path.join(screen_path, "img") - os.makedirs(img_dir, exist_ok=True) - copy_file(img_dir, file_name) - break - retry_times -= 1 - LOG.info("screen shot success.") - return True - - @staticmethod - def ui_dump_id(device, **kwargs): - args = kwargs - node_id = args.get("node_id", "") - timeout = args.get("timeout", "") - dump_node_command = "%s%s" % ("uidump node ", str(node_id)) - dump_times = 5 - while dump_times > 0: - dump_node_value = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(dump_node_command), timeout, True) - if dump_node_value.return_code == 0: - result = filter_json(dump_node_value.return_message) - if result != "no such node": - return get_center(result, "id", node_id) - dump_times -= 1 - LOG.error("dump fail, please check node id") - return None - - @staticmethod - def ui_dump_tree(device, **kwargs): - args = kwargs - node_id = args.get("node_id", "") - timeout = args.get("timeout", "") - if node_id: - dump_tree_command = "%s%s" % ("uidump tree ", node_id) - else: - dump_tree_command = "uidump tree" - dump_tree_value = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(dump_tree_command), timeout, True) - if dump_tree_value.return_code == 0: - dump_str = "" - from _core.environment.device_lite import get_hdc_path - local_path = get_hdc_path() - retry_times = 2 - while retry_times > 0: - command = "rfile user/log/dump_dom_tree.json" - if LiteUiKit.download_file(device, command, timeout=1400): - with open(local_path, "r") as file_stream: - dump_str = file_stream.read() - break - retry_times -= 1 - return dump_str - else: - LOG.error("dump failed") - raise ParamError("dump failed") - - @staticmethod - def swipe(device, start_point, end_point, timeout=800): - if not isinstance(start_point, tuple) and not isinstance( - end_point, tuple): - raise ParamError( - "The coordinates of the sliding point should be tuple") - start_x, start_y = start_point - end_x, end_y = end_point - press_first_command = "%s%s%s%s%s" % ("uievent ", str(start_x), " ", - str(start_y), " PRESSDOWN") - press_end_command = "%s%s%s%s" % ("uievent ", str(end_x), str(end_y), - " PRESSDOWN") - release_end_command = "%s%s%s%s%s" % ("uievent ", str(end_x), " ", - str(end_y), " RELEASE") - press_first_point_result = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(press_first_command, True), timeout) - if press_first_point_result.return_code == 0: - time.sleep(timeout / 1000) - press_medium_point_result = \ - LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(press_end_command, True), - timeout, True) - if press_medium_point_result.return_code == 0: - time.sleep(timeout / 1000) - press_end_point_result = \ - LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(release_end_command, True), - timeout, True) - if press_end_point_result.return_code == 0: - LOG.info("swipe success.") - return True - return False - - @staticmethod - def long_press(device, x_coordinate, y_coordinate, timeout=1200): - press_command = "%s%s%s%s%s" % ("uievent ", str(x_coordinate), " ", - str(y_coordinate), " PRESSDOWN") - release_command = "%s%s%s%s%s" % ("uievent ", str(x_coordinate), " ", - str(y_coordinate), " RELEASE") - press_result = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(press_command, True), timeout, True) - if press_result.return_code == 0: - time.sleep(timeout / 1000) - release_result = LiteUiKit.execute_hdc_cmd_with_timeout( - device, get_hdc_command(release_command, True), timeout, True) - if release_result.return_code == 0: - LOG.info("press success.") - return True - return False - - @staticmethod - def download_file(device, command, timeout=1200): - press_result = LiteUiKit.execute_hdc_cmd_with_timeout( - device, command, timeout, True) - if press_result.return_code == 0: - LOG.info("download file success.") - return True - return False - - -class ResultValue(object): - def __init__(self, return_code, error_message="", return_message=""): - self.return_code = return_code - self.error_message = error_message - self.return_message = return_message - - def set_return_code(self, ret_code): - self.return_code = ret_code - - def set_error_message(self, error_message): - self.error_message = error_message - - def set_return_message(self, return_message): - self.return_message = return_message - - def get_return_code(self): - return self.return_code - - def get_error_message(self): - return self.error_message - - def get_return_message(self): - return self.return_message diff --git a/src/xdevice/_core/utils.py b/src/xdevice/_core/utils.py index 205f9accb65e83f5aa4b91464cd2d6f48776ce2c..ba33461ddbcf658839d6226e2c859a6dda5d0e80 100755 --- a/src/xdevice/_core/utils.py +++ b/src/xdevice/_core/utils.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,6 +16,7 @@ # limitations under the License. # +import copy import os import socket import time @@ -25,15 +26,19 @@ import subprocess import signal import uuid import json +import stat from tempfile import NamedTemporaryFile from _core.executor.listener import SuiteResult from _core.driver.parser_lite import ShellHandler from _core.exception import ParamError +from _core.exception import ExecuteTerminate from _core.logger import platform_logger from _core.report.suite_reporter import SuiteReporter from _core.plugin import get_plugin from _core.plugin import Plugin +from _core.constants import ModeType +from _core.constants import ConfigConst LOG = platform_logger("Utils") @@ -95,31 +100,27 @@ def stop_standing_subprocess(process): def get_decode(stream): - if not isinstance(stream, str) and not isinstance(stream, bytes): + if isinstance(stream, str): + return stream + + if not isinstance(stream, bytes): + return str(stream) + + try: + ret = stream.decode("utf-8", errors="ignore") + except (ValueError, AttributeError, TypeError): ret = str(stream) - else: - try: - ret = stream.decode("utf-8", errors="ignore") - except (ValueError, AttributeError, TypeError): - ret = str(stream) return ret def is_proc_running(pid, name=None): if platform.system() == "Windows": - proc_sub = subprocess.Popen(["C:\\Windows\\System32\\tasklist"], - stdout=subprocess.PIPE, - shell=False) - proc = subprocess.Popen(["C:\\Windows\\System32\\findstr", "%s" % pid], - stdin=proc_sub.stdout, - stdout=subprocess.PIPE, shell=False) + list_command = ["C:\\Windows\\System32\\tasklist"] + find_command = ["C:\\Windows\\System32\\findstr", "%s" % pid] else: - proc_sub = subprocess.Popen(["/bin/ps", "-ef"], - stdout=subprocess.PIPE, - shell=False) - proc = subprocess.Popen(["/bin/grep", "%s" % pid], - stdin=proc_sub.stdout, - stdout=subprocess.PIPE, shell=False) + list_command = ["/bin/ps", "-ef"] + find_command = ["/bin/grep", "%s" % pid] + proc = _get_find_proc(find_command, list_command) (out, _) = proc.communicate() out = get_decode(out).strip() if out == "": @@ -128,7 +129,15 @@ def is_proc_running(pid, name=None): return True if name is None else out.find(name) != -1 -def exec_cmd(cmd, timeout=5 * 60, error_print=True): +def _get_find_proc(find_command, list_command): + proc_sub = subprocess.Popen(list_command, stdout=subprocess.PIPE, + shell=False) + proc = subprocess.Popen(find_command, stdin=proc_sub.stdout, + stdout=subprocess.PIPE, shell=False) + return proc + + +def exec_cmd(cmd, timeout=5 * 60, error_print=True, join_result=False): """ Executes commands in a new shell. Directing stderr to PIPE. @@ -139,35 +148,38 @@ def exec_cmd(cmd, timeout=5 * 60, error_print=True): cmd: A sequence of commands and arguments. timeout: timeout for exe cmd. error_print: print error output or not. - + join_result: join error and out Returns: The output of the command run. """ sys_type = platform.system() - if sys_type == "Windows": - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=False) - elif sys_type == "Linux" or sys_type == "Darwin": + if sys_type == "Linux" or sys_type == "Darwin": proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, - preexec_fn=os.setsid) # @UndefinedVariable + preexec_fn=os.setsid) else: - return + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=False) try: (out, err) = proc.communicate(timeout=timeout) err = get_decode(err).strip() out = get_decode(out).strip() if err and error_print: - LOG.exception(err) - return err if err else out + LOG.exception(err, exc_info=False) + if join_result: + return "%s\n %s" % (out, err) if err else out + else: + return err if err else out - except (TimeoutError, KeyboardInterrupt, AttributeError, ValueError): + except (TimeoutError, KeyboardInterrupt, AttributeError, ValueError, + EOFError, IOError): sys_type = platform.system() - if sys_type == "Windows": - os.kill(proc.pid, signal.SIGINT) - elif sys_type == "Linux" or sys_type == "Darwin": + if sys_type == "Linux" or sys_type == "Darwin": os.killpg(proc.pid, signal.SIGTERM) + else: + os.kill(proc.pid, signal.SIGINT) + raise def create_dir(path): @@ -182,15 +194,26 @@ def create_dir(path): def get_config_value(key, config_dict, is_list=True, default=None): + """get corresponding values for key in config_dict + + Args: + key: target key in config_dict + config_dict: dictionary that store values + is_list: decide return values is list type or not + default: if key not in config_dict, default value will be returned + + Returns: + corresponding values for key + """ if not isinstance(config_dict, dict): return default - value = config_dict.get(key, "") + value = config_dict.get(key, None) if isinstance(value, bool): return value - if not value: - if default: + if value is None: + if default is not None: return default return [] if is_list else "" @@ -200,28 +223,48 @@ def get_config_value(key, config_dict, is_list=True, default=None): def get_file_absolute_path(input_name, paths=None, alt_dir=None): + """find absolute path for input_name + + Args: + input_name: the target file to search + paths: path list for searching input_name + alt_dir: extra dir that appended to paths + + Returns: + absolute path for input_name + """ + input_name = str(input_name) abs_paths = set(paths) if paths else set() _update_paths(abs_paths) - for path in abs_paths: - if alt_dir: - file_path = os.path.join(path, alt_dir, input_name) + _inputs = [input_name] + if input_name.startswith("resource/"): + _inputs.append(input_name.replace("resource/", "", 1)) + elif input_name.startswith("testcases/"): + _inputs.append(input_name.replace("testcases/", "", 1)) + + for _input in _inputs: + for path in abs_paths: + if alt_dir: + file_path = os.path.join(path, alt_dir, _input) + if os.path.exists(file_path): + return os.path.abspath(file_path) + + file_path = os.path.join(path, _input) if os.path.exists(file_path): return os.path.abspath(file_path) - file_path = os.path.join(path, input_name) - if os.path.exists(file_path): - return os.path.abspath(file_path) - err_msg = "The file {} does not exist".format(input_name) - LOG.error(err_msg) + if check_mode(ModeType.decc): + LOG.error(err_msg, error_no="00109") + err_msg = "Load Error[00109]" if alt_dir: LOG.debug("alt_dir is %s" % alt_dir) LOG.debug("paths is:") for path in abs_paths: LOG.debug(path) - raise ParamError(err_msg) + raise ParamError(err_msg, error_no="00109") def _update_paths(paths): @@ -267,7 +310,9 @@ def modify_props(device, local_prop_file, target_prop_file, new_props): old_props = {} changed_prop_key = [] lines = [] - with open(local_prop_file, 'r') as old_file: + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(local_prop_file, flags, modes), "r") as old_file: lines = old_file.readlines() if lines: lines[-1] = lines[-1] + '\n' @@ -304,25 +349,42 @@ def modify_props(device, local_prop_file, target_prop_file, new_props): return is_changed -def get_device_log_file(report_path, serial=None, log_name="device_log"): +def get_device_log_file(report_path, serial=None, log_name="device_log", + device_name=""): from xdevice import Variables log_path = os.path.join(report_path, Variables.report_vars.log_dir) os.makedirs(log_path, exist_ok=True) serial = serial or time.time_ns() - device_file_name = "{}_{}.log".format(log_name, serial) + if device_name: + serial = "%s_%s" % (device_name, serial) + device_file_name = "{}_{}.log".format(log_name, str(serial).replace( + ":", "_")) device_log_file = os.path.join(log_path, device_file_name) + LOG.info("generate device log file: %s", device_log_file) return device_log_file -def check_result_report(report_path, report_file, error_message="", - report_name=""): +def check_result_report(report_root_dir, report_file, error_message="", + report_name="", module_name=""): + """ + check whether report_file exits or not. if report_file is not exist, + create empty report with error_message under report_root_dir + """ + if os.path.exists(report_file): return report_file - result_dir = os.path.join(report_path, "result") + report_dir = os.path.dirname(report_file) + if os.path.isabs(report_dir): + result_dir = report_dir + else: + result_dir = os.path.join(report_root_dir, "result", report_dir) os.makedirs(result_dir, exist_ok=True) - LOG.error("report %s not exist, create empty report under %s" % ( - report_file, result_dir)) + if check_mode(ModeType.decc): + LOG.error("report not exist, create empty report") + else: + LOG.error("report %s not exist, create empty report under %s" % ( + report_file, result_dir)) suite_name = report_name if not suite_name: @@ -330,12 +392,28 @@ def check_result_report(report_path, report_file, error_message="", suite_result = SuiteResult() suite_result.suite_name = suite_name suite_result.stacktrace = error_message + if module_name: + suite_name = module_name suite_reporter = SuiteReporter([(suite_result, [])], suite_name, - result_dir) + result_dir, modulename=module_name) suite_reporter.create_empty_report() return "%s.xml" % os.path.join(result_dir, suite_name) +def get_sub_path(test_suite_path): + pattern = "%stests%s" % (os.sep, os.sep) + file_dir = os.path.dirname(test_suite_path) + pos = file_dir.find(pattern) + if -1 == pos: + return "" + + sub_path = file_dir[pos + len(pattern):] + pos = sub_path.find(os.sep) + if -1 == pos: + return "" + return sub_path[pos + len(os.sep):] + + def is_config_str(content): return True if "{" in content and "}" in content else False @@ -346,8 +424,9 @@ def get_version(): ver_file_path = os.path.join(Variables.res_dir, 'version.txt') if not os.path.isfile(ver_file_path): return ver - - with open(ver_file_path, mode='r', encoding='UTF-8') as ver_file: + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(ver_file_path, flags, modes), "r") as ver_file: line = ver_file.readline() if '-v' in line: ver = line.strip().split('-')[1] @@ -370,17 +449,19 @@ def convert_ip(origin_ip): def convert_port(port): - if len(port) >= 2: - return "{}**{}".format(port[0], port[-1]) + _port = str(port) + if len(_port) >= 2: + return "{}{}{}".format(_port[0], "*" * (len(_port) - 2), _port[-1]) else: - return "{}**".format(port) + return "*{}".format(_port[-1]) def convert_serial(serial): if serial.startswith("local_"): - return "local_{}".format('*'*(len(serial)-6)) + return serial elif serial.startswith("remote_"): - return "remote_{}".format(convert_ip(serial.split("_")[1])) + return "remote_{}_{}".format(convert_ip(serial.split("_")[1]), + convert_port(serial.split("_")[-1])) else: length = len(serial)//3 return "{}{}{}".format( @@ -402,25 +483,45 @@ def get_shell_handler(request, parser_type): return handler -def get_kit_instances(json_config, resource_path, testcases_path): +def get_kit_instances(json_config, resource_path="", testcases_path=""): from _core.testkit.json_parser import JsonParser kit_instances = [] + + # check input param if not isinstance(json_config, JsonParser): return kit_instances + + # get kit instances for kit in json_config.config.kits: kit["paths"] = [resource_path, testcases_path] kit_type = kit.get("type", "") + device_name = kit.get("device_name", None) if get_plugin(plugin_type=Plugin.TEST_KIT, plugin_id=kit_type): test_kit = \ get_plugin(plugin_type=Plugin.TEST_KIT, plugin_id=kit_type)[0] test_kit_instance = test_kit.__class__() test_kit_instance.__check_config__(kit) + setattr(test_kit_instance, "device_name", device_name) kit_instances.append(test_kit_instance) else: - raise ParamError("kit %s not exists" % kit_type) + raise ParamError("kit %s not exists" % kit_type, error_no="00107") return kit_instances +def check_device_name(device, kit, step="setup"): + kit_device_name = getattr(kit, "device_name", None) + device_name = device.get("name") + if kit_device_name and device_name and \ + kit_device_name != device_name: + return False + if kit_device_name and device_name: + LOG.debug("do kit:%s %s for device:%s", + kit.__class__.__name__, step, device_name) + else: + LOG.debug("do kit:%s %s", kit.__class__.__name__, step) + return True + + def check_path_legal(path): if path and " " in path: return "\"%s\"" % path @@ -458,6 +559,8 @@ def get_local_ip(): else: local_ip = "127.0.0.1" return local_ip + else: + return "127.0.0.1" class SplicingAction(argparse.Action): @@ -466,16 +569,55 @@ class SplicingAction(argparse.Action): def get_test_component_version(config): + if check_mode(ModeType.decc): + return "" + try: paths = [config.resource_path, config.testcases_path] test_file = get_file_absolute_path("test_component.json", paths) - - with open(test_file, encoding="utf-8") as file_content: + flags = os.O_RDONLY + modes = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(test_file, flags, modes), "r") as file_content: json_content = json.load(file_content) version = json_content.get("version", "") return version except (ParamError, ValueError) as error: - LOG.error( - "The exception {} happened when get version".format( - error)) + LOG.error("The exception {} happened when get version".format(error)) return "" + + +def check_mode(mode): + from xdevice import Scheduler + return Scheduler.mode == mode + + +def do_module_kit_setup(request, kits): + for device in request.get_devices(): + setattr(device, ConfigConst.module_kits, []) + + from xdevice import Scheduler + for kit in kits: + run_flag = False + for device in request.get_devices(): + if not Scheduler.is_execute: + raise ExecuteTerminate() + if check_device_name(device, kit): + run_flag = True + kit_copy = copy.deepcopy(kit) + module_kits = getattr(device, ConfigConst.module_kits) + module_kits.append(kit_copy) + kit_copy.__setup__(device, request=request) + if not run_flag: + kit_device_name = getattr(kit, "device_name", None) + error_msg = "device name '%s' of '%s' not exist" % ( + kit_device_name, kit.__class__.__name__) + LOG.error(error_msg, error_no="00108") + raise ParamError(error_msg, error_no="00108") + + +def do_module_kit_teardown(request): + for device in request.get_devices(): + for kit in getattr(device, ConfigConst.module_kits, []): + if check_device_name(device, kit, step="teardown"): + kit.__teardown__(device) + setattr(device, ConfigConst.module_kits, []) diff --git a/src/xdevice/variables.py b/src/xdevice/variables.py index 6c6a5234594af3c644c6a7a73533502268d77dab..594ad67d5587cc4892b5cecbf8d1ce704b3aafd4 100755 --- a/src/xdevice/variables.py +++ b/src/xdevice/variables.py @@ -2,7 +2,7 @@ # coding=utf-8 # -# Copyright (c) 2020 Huawei Device Co., Ltd. +# Copyright (c) 2020-2021 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -23,12 +23,16 @@ from dataclasses import dataclass __all__ = ["Variables"] SRC_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +SRC_ADAPTER_DIR = os.path.abspath(os.path.join(SRC_DIR, "adapter")) MODULES_DIR = os.path.abspath(os.path.dirname(__file__)) TOP_DIR = os.path.abspath( os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) +TOP_ADAPTER_DIR = os.path.abspath(os.path.join(TOP_DIR, "adapter")) sys.path.insert(0, SRC_DIR) sys.path.insert(1, MODULES_DIR) sys.path.insert(2, TOP_DIR) +sys.path.insert(3, SRC_ADAPTER_DIR) +sys.path.insert(4, TOP_ADAPTER_DIR) @dataclass @@ -39,6 +43,7 @@ class ReportVariables: log_level = "" log_handler = "" pub_key_file = None + pub_key_string = "" @dataclass @@ -65,8 +70,8 @@ def _init_global_config(): # set report variables Variables.report_vars.log_dir = "log" Variables.report_vars.report_dir = "reports" - Variables.report_vars.log_format = "%(asctime)s %(name)-15s " \ - "%(levelname)-8s %(message)s" + Variables.report_vars.log_format = "[%(asctime)s] [%(name)s] " \ + "[%(levelname)s] %(message)s" Variables.report_vars.log_level = logging.INFO Variables.report_vars.log_handler = "console, file" @@ -111,7 +116,8 @@ def _init_logger(): tool_log_file = None if Variables.exec_dir and os.path.normcase( - Variables.exec_dir) == os.path.normcase(Variables.top_dir): + Variables.exec_dir) == os.path.normcase(Variables.top_dir) and \ + not hasattr(sys, "decc_mode"): host_log_path = os.path.join(Variables.exec_dir, Variables.report_vars.report_dir, Variables.report_vars.log_dir)
ModulesModules DoneRun Modules Total Tests Passed Failed