pytest-testkit 1.0.2b1__tar.gz → 1.0.3a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_testkit-1.0.2b1/pytest_testkit.egg-info → pytest_testkit-1.0.3a1}/PKG-INFO +47 -23
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/README.md +45 -19
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pyproject.toml +2 -4
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/base/sftp.py +1 -1
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/device.py +39 -32
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/plugin.py +1 -1
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1/pytest_testkit.egg-info}/PKG-INFO +47 -23
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit.egg-info/SOURCES.txt +0 -1
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit.egg-info/requires.txt +1 -3
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/requirements.txt +2 -4
- pytest_testkit-1.0.2b1/pytest_testkit/lib/base/http.py +0 -431
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/LICENSE +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/MANIFEST.in +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/base/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/base/excel.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/base/ftp.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/base/local_exec.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/base/ssh.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/allocator.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/manager.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/log/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/log/filters.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/log/infra_logger.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/log/manager.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/pre_action/__init__.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/pre_action/action.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/pre_action/exceptions.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/pre_action/registry.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit.egg-info/dependency_links.txt +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit.egg-info/entry_points.txt +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit.egg-info/top_level.txt +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/setup.cfg +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_collect_case_with_inifile.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_creation.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_exit_docker.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_getitem.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_integration.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_is_interactive_shell.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_non_shell_commands.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_sendcmd.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_sendcmd_interactive.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_env_nodes_filter.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_get_inifile_info.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_get_item_case_info.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_local_exec.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_local_interactive.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_log_factory.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_log_factory_integration.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_security_features.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_ssh_cwd.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_ssh_file_ops.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_ssh_ignore_err.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_ssh_only_stdout.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_ssh_remove_command_echo.py +0 -0
- {pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_ssh_success.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest_testkit
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3a1
|
|
4
4
|
Summary: Pytest plugin for Infra logging framework and environment management
|
|
5
5
|
Author: Huawei
|
|
6
6
|
License: MIT License
|
|
@@ -43,14 +43,12 @@ Classifier: Operating System :: OS Independent
|
|
|
43
43
|
Requires-Python: >=3.6
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
License-File: LICENSE
|
|
46
|
-
Requires-Dist: pytest
|
|
46
|
+
Requires-Dist: pytest>=8.3.2
|
|
47
47
|
Requires-Dist: requests
|
|
48
48
|
Requires-Dist: paramiko
|
|
49
49
|
Requires-Dist: xlrd>=2.0.0
|
|
50
50
|
Requires-Dist: PyYaml
|
|
51
51
|
Requires-Dist: scp
|
|
52
|
-
Requires-Dist: execnet
|
|
53
|
-
Requires-Dist: pybind11
|
|
54
52
|
Provides-Extra: dev
|
|
55
53
|
Requires-Dist: build; extra == "dev"
|
|
56
54
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -126,6 +124,8 @@ docker run -d --name <name> -p 2222:22 <image>:<tag>
|
|
|
126
124
|
```
|
|
127
125
|
### logger 使用
|
|
128
126
|
|
|
127
|
+
> **⚠️ 安全提醒:该`logger`为 `pytest-testkit` 插件提供的基础日志能力,请勿在测试用例中打印敏感信息(如密码、密钥、Token 等),以免造成信息泄露。**
|
|
128
|
+
|
|
129
129
|
导入 logger:
|
|
130
130
|
|
|
131
131
|
```python
|
|
@@ -135,7 +135,7 @@ from pytest_testkit.lib.common.log import logger
|
|
|
135
135
|
使用 logger 记录日志:
|
|
136
136
|
|
|
137
137
|
```python
|
|
138
|
-
logger.info(
|
|
138
|
+
logger.info("Successfully connected to device")
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
#### 日志目录结构
|
|
@@ -152,12 +152,30 @@ logs/
|
|
|
152
152
|
|
|
153
153
|
**目录结构说明**:
|
|
154
154
|
- **TestCases**: 测试用例日志根目录
|
|
155
|
-
- **日期目录**: 按执行日期组织(格式:`YYYY-MM-DD
|
|
156
|
-
- **用例子目录**: 按用例名和执行时间命名(格式:`用例名_HH-MM-SS
|
|
155
|
+
- **日期目录**: 按执行日期组织(格式:`YYYY-MM-DD`,使用本机时区)
|
|
156
|
+
- **用例子目录**: 按用例名和执行时间命名(格式:`用例名_HH-MM-SS`,使用本机时区)
|
|
157
157
|
- **HTML 文件**: 插件生成的测试报告,文件名与用例名一致
|
|
158
158
|
|
|
159
159
|
通过 `logger.path` 属性可以获取当前用例的日志归档目录路径,方便在测试用例中创建自定义文件。
|
|
160
160
|
|
|
161
|
+
#### HTML 报告渲染
|
|
162
|
+
|
|
163
|
+
> **⚠️ 安全提醒:HTML 报告会完整渲染日志中记录的命令文本、回显输出及登录 banner 等内容,请勿在命令或日志中携带敏感信息(如密码、密钥、Token 等),以免在报告中明文暴露。**
|
|
164
|
+
|
|
165
|
+
测试用例的 HTML 日志报告支持以下渲染特性:
|
|
166
|
+
|
|
167
|
+
- **op_type 标签**: 命令与连接日志通过 `logging` 的 `extra={"op_type": ...}` 标记操作类型,HTML 报告 Location 列显示该标签(未标记记录回退为 `文件名:行号`)。内置标签包括:
|
|
168
|
+
- `sendcmd[<设备短名>]`: 发送命令,消息带最近 shell 提示符前缀(如 `root@host:~#ls -la`),设置 `cwd` 时追加 `(cwd: ...)`
|
|
169
|
+
- `recvcmd[<设备短名>]`: 命令文本回显(stdout+stderr 合并,不打印 dict),末尾带最新提示符;无输出时显示 `(empty)`
|
|
170
|
+
- `login[<设备短名>]` / `loginresult[<设备短名>]`: 登录开始与结果(登录失败为 ERROR 级别)
|
|
171
|
+
- `loginecho[<设备短名>]`: 登录 banner(交互 channel 首次创建时吸收,在第一条 sendcmd 之前打印)
|
|
172
|
+
- `disconnect[<设备短名>]`: 连接关闭
|
|
173
|
+
- `<设备短名>` 取设备名最后一段(按 `-` 或 `/` 分割),如 `rack1/node_3` → `node_3`
|
|
174
|
+
- **ANSI 颜色渲染**: 报告自动把 ANSI 前景色码(30-37、90-97)转换为带颜色的 `<span>` 标签。例如命令日志中的蓝色码(`\x1b[34m`)渲染为蓝色(`#2196F3`)
|
|
175
|
+
- **错误词高亮**: `recvcmd` 回显中的常见错误词(error、failed、fatal、panic、traceback、timeout、"No such file or directory" 等)会以粗体红色显示,便于快速定位失败。命令行(sendcmd)与普通日志不高亮,避免误伤命令本身
|
|
176
|
+
- **列宽调整**: Level 列 70px、Thread 列 50px、Location 列 128px,Message 列获得更多空间
|
|
177
|
+
- **本地时间**: 会话目录、日期目录、用例子目录均使用本机时区时间,与报告表内日志时间戳一致
|
|
178
|
+
|
|
161
179
|
日志级别:
|
|
162
180
|
|
|
163
181
|
| 级别 | 值 | 方法 | 说明 |
|
|
@@ -411,9 +429,9 @@ def test_environment(logger):
|
|
|
411
429
|
|
|
412
430
|
### quiet_stdout 功能
|
|
413
431
|
|
|
414
|
-
pytest-testkit 插件支持全局静默 `sendcmd` / `
|
|
415
|
-
**开启后,
|
|
416
|
-
|
|
432
|
+
pytest-testkit 插件支持全局静默 `sendcmd` / `sendcmd_oneshot` / `sendcmd_interactive` 的命令与回显日志,避免大量回显淹没日志。
|
|
433
|
+
**开启后,sendcmd / recvcmd 日志都不会打印,即使调用方显式传 `echo_output=True` 也会被全局压制。**
|
|
434
|
+
命令返回值(stdout / stderr / exit_code 等)不受影响,仅影响日志输出。
|
|
417
435
|
|
|
418
436
|
**支持CLI 参数 和 ini 配置,默认为禁用**
|
|
419
437
|
|
|
@@ -528,23 +546,27 @@ def test_use_ci_metadata():
|
|
|
528
546
|
|
|
529
547
|
- **environment/environments**: 提供单设备/多设备对象
|
|
530
548
|
|
|
531
|
-
- **sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True)
|
|
549
|
+
- **sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True, echo_output=True)**:在持久化交互 shell channel 上执行命令
|
|
532
550
|
- `cmd`: 要执行的命令字符串
|
|
533
|
-
- `timeout`:
|
|
534
|
-
- `env_vars`:
|
|
551
|
+
- `timeout`: 命令执行超时时间(秒),支持 int 或 str,默认 600s
|
|
552
|
+
- `env_vars`: 环境变量字典,内联为单命令前缀(`VAR=value cmd`),仅对本次命令生效,不污染持久化 shell
|
|
535
553
|
- `cwd`: 工作目录,命令将在该目录下执行(可选)
|
|
536
554
|
- `ignore_err`: 是否忽略命令执行错误,默认 False。设为 True 时即使命令失败 success 也为 True
|
|
537
555
|
- `only_stdout`: 为 True 时只返回 stdout 字符串,为 False 时返回完整字典
|
|
556
|
+
- `echo_output`: 是否打印 sendcmd/recvcmd 日志,默认 True
|
|
557
|
+
- 行为说明: 同一线程内 shell 状态(`cd`、`export`)跨调用持久化;PTY 合并 stderr 到 stdout,返回 dict 的 `stderr` 通常为空;退出码通过 `$?` 哨兵尽力捕获。需要独立进程执行时请用 `sendcmd_oneshot`
|
|
538
558
|
- 返回值: 默认返回 stdout 字符串;当 `only_stdout=False` 时返回 `{'success': bool, 'stdout': str, 'stderr': str}`
|
|
539
559
|
|
|
540
|
-
- **
|
|
541
|
-
- `
|
|
542
|
-
- `
|
|
543
|
-
- `
|
|
544
|
-
- `
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
-
|
|
560
|
+
- **sendcmd_oneshot(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True, echo_output=True)**:独立进程执行命令(原 sendcmd 行为)
|
|
561
|
+
- 每次调用启动独立的 `exec_command` / `subprocess.run` 进程,无持久 shell 状态,与交互 channel 互相独立
|
|
562
|
+
- SSH 路径: 以 `bash -l -c` 包装执行,原生环境变量支持,退出码为 channel 权威退出状态,stderr 为独立流
|
|
563
|
+
- `local_exec` 路径: 通过 `subprocess.run` 执行
|
|
564
|
+
- 参数与返回值与 `sendcmd` 一致(`env_vars` 通过原生环境注入而非内联前缀)
|
|
565
|
+
|
|
566
|
+
- **sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True)**:等同于 `sendcmd`(保留向后兼容)
|
|
567
|
+
- 现直接代理给 `sendcmd`,在持久化交互 shell channel 上执行
|
|
568
|
+
- `expect_prompt`: 保留签名兼容但不再生效(被忽略)
|
|
569
|
+
- 参数与返回值与 `sendcmd` 一致
|
|
548
570
|
- **输出清理**: 返回的 stdout 会自动清理以下内容,便于用户直接使用:
|
|
549
571
|
- CSI 控制序列(如 `\x1b[32m` 颜色代码、`\x1b[?2004h` bracketed paste mode 等)
|
|
550
572
|
- OSC 控制序列(如 `\x1b]0;title\x07` 窗口标题设置等)
|
|
@@ -608,8 +630,10 @@ def test_use_ci_metadata():
|
|
|
608
630
|
- **DockerProxy 容器代理对象**:通过 `environment["container_name"]` 获取
|
|
609
631
|
- 代理对象提供与 Device 一致的接口,自动包装 `docker exec` 命令
|
|
610
632
|
|
|
611
|
-
- **sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True)
|
|
612
|
-
-
|
|
633
|
+
- **sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True)**:非交互式命令(docker session 感知)
|
|
634
|
+
- 已在该容器交互式 session 中: 直接在当前 session 执行命令
|
|
635
|
+
- 其他容器持有交互式 session: 直接返回失败(提示"请退出上一个交互对话")
|
|
636
|
+
- 否则: 包装为 `docker exec {docker_name} {cmd}` 执行(不创建持久 session)
|
|
613
637
|
- 返回值: stdout 字符串或完整字典
|
|
614
638
|
|
|
615
639
|
- **sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True)**:交互式命令
|
|
@@ -68,6 +68,8 @@ docker run -d --name <name> -p 2222:22 <image>:<tag>
|
|
|
68
68
|
```
|
|
69
69
|
### logger 使用
|
|
70
70
|
|
|
71
|
+
> **⚠️ 安全提醒:该`logger`为 `pytest-testkit` 插件提供的基础日志能力,请勿在测试用例中打印敏感信息(如密码、密钥、Token 等),以免造成信息泄露。**
|
|
72
|
+
|
|
71
73
|
导入 logger:
|
|
72
74
|
|
|
73
75
|
```python
|
|
@@ -77,7 +79,7 @@ from pytest_testkit.lib.common.log import logger
|
|
|
77
79
|
使用 logger 记录日志:
|
|
78
80
|
|
|
79
81
|
```python
|
|
80
|
-
logger.info(
|
|
82
|
+
logger.info("Successfully connected to device")
|
|
81
83
|
```
|
|
82
84
|
|
|
83
85
|
#### 日志目录结构
|
|
@@ -94,12 +96,30 @@ logs/
|
|
|
94
96
|
|
|
95
97
|
**目录结构说明**:
|
|
96
98
|
- **TestCases**: 测试用例日志根目录
|
|
97
|
-
- **日期目录**: 按执行日期组织(格式:`YYYY-MM-DD
|
|
98
|
-
- **用例子目录**: 按用例名和执行时间命名(格式:`用例名_HH-MM-SS
|
|
99
|
+
- **日期目录**: 按执行日期组织(格式:`YYYY-MM-DD`,使用本机时区)
|
|
100
|
+
- **用例子目录**: 按用例名和执行时间命名(格式:`用例名_HH-MM-SS`,使用本机时区)
|
|
99
101
|
- **HTML 文件**: 插件生成的测试报告,文件名与用例名一致
|
|
100
102
|
|
|
101
103
|
通过 `logger.path` 属性可以获取当前用例的日志归档目录路径,方便在测试用例中创建自定义文件。
|
|
102
104
|
|
|
105
|
+
#### HTML 报告渲染
|
|
106
|
+
|
|
107
|
+
> **⚠️ 安全提醒:HTML 报告会完整渲染日志中记录的命令文本、回显输出及登录 banner 等内容,请勿在命令或日志中携带敏感信息(如密码、密钥、Token 等),以免在报告中明文暴露。**
|
|
108
|
+
|
|
109
|
+
测试用例的 HTML 日志报告支持以下渲染特性:
|
|
110
|
+
|
|
111
|
+
- **op_type 标签**: 命令与连接日志通过 `logging` 的 `extra={"op_type": ...}` 标记操作类型,HTML 报告 Location 列显示该标签(未标记记录回退为 `文件名:行号`)。内置标签包括:
|
|
112
|
+
- `sendcmd[<设备短名>]`: 发送命令,消息带最近 shell 提示符前缀(如 `root@host:~#ls -la`),设置 `cwd` 时追加 `(cwd: ...)`
|
|
113
|
+
- `recvcmd[<设备短名>]`: 命令文本回显(stdout+stderr 合并,不打印 dict),末尾带最新提示符;无输出时显示 `(empty)`
|
|
114
|
+
- `login[<设备短名>]` / `loginresult[<设备短名>]`: 登录开始与结果(登录失败为 ERROR 级别)
|
|
115
|
+
- `loginecho[<设备短名>]`: 登录 banner(交互 channel 首次创建时吸收,在第一条 sendcmd 之前打印)
|
|
116
|
+
- `disconnect[<设备短名>]`: 连接关闭
|
|
117
|
+
- `<设备短名>` 取设备名最后一段(按 `-` 或 `/` 分割),如 `rack1/node_3` → `node_3`
|
|
118
|
+
- **ANSI 颜色渲染**: 报告自动把 ANSI 前景色码(30-37、90-97)转换为带颜色的 `<span>` 标签。例如命令日志中的蓝色码(`\x1b[34m`)渲染为蓝色(`#2196F3`)
|
|
119
|
+
- **错误词高亮**: `recvcmd` 回显中的常见错误词(error、failed、fatal、panic、traceback、timeout、"No such file or directory" 等)会以粗体红色显示,便于快速定位失败。命令行(sendcmd)与普通日志不高亮,避免误伤命令本身
|
|
120
|
+
- **列宽调整**: Level 列 70px、Thread 列 50px、Location 列 128px,Message 列获得更多空间
|
|
121
|
+
- **本地时间**: 会话目录、日期目录、用例子目录均使用本机时区时间,与报告表内日志时间戳一致
|
|
122
|
+
|
|
103
123
|
日志级别:
|
|
104
124
|
|
|
105
125
|
| 级别 | 值 | 方法 | 说明 |
|
|
@@ -353,9 +373,9 @@ def test_environment(logger):
|
|
|
353
373
|
|
|
354
374
|
### quiet_stdout 功能
|
|
355
375
|
|
|
356
|
-
pytest-testkit 插件支持全局静默 `sendcmd` / `
|
|
357
|
-
**开启后,
|
|
358
|
-
|
|
376
|
+
pytest-testkit 插件支持全局静默 `sendcmd` / `sendcmd_oneshot` / `sendcmd_interactive` 的命令与回显日志,避免大量回显淹没日志。
|
|
377
|
+
**开启后,sendcmd / recvcmd 日志都不会打印,即使调用方显式传 `echo_output=True` 也会被全局压制。**
|
|
378
|
+
命令返回值(stdout / stderr / exit_code 等)不受影响,仅影响日志输出。
|
|
359
379
|
|
|
360
380
|
**支持CLI 参数 和 ini 配置,默认为禁用**
|
|
361
381
|
|
|
@@ -470,23 +490,27 @@ def test_use_ci_metadata():
|
|
|
470
490
|
|
|
471
491
|
- **environment/environments**: 提供单设备/多设备对象
|
|
472
492
|
|
|
473
|
-
- **sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True)
|
|
493
|
+
- **sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True, echo_output=True)**:在持久化交互 shell channel 上执行命令
|
|
474
494
|
- `cmd`: 要执行的命令字符串
|
|
475
|
-
- `timeout`:
|
|
476
|
-
- `env_vars`:
|
|
495
|
+
- `timeout`: 命令执行超时时间(秒),支持 int 或 str,默认 600s
|
|
496
|
+
- `env_vars`: 环境变量字典,内联为单命令前缀(`VAR=value cmd`),仅对本次命令生效,不污染持久化 shell
|
|
477
497
|
- `cwd`: 工作目录,命令将在该目录下执行(可选)
|
|
478
498
|
- `ignore_err`: 是否忽略命令执行错误,默认 False。设为 True 时即使命令失败 success 也为 True
|
|
479
499
|
- `only_stdout`: 为 True 时只返回 stdout 字符串,为 False 时返回完整字典
|
|
500
|
+
- `echo_output`: 是否打印 sendcmd/recvcmd 日志,默认 True
|
|
501
|
+
- 行为说明: 同一线程内 shell 状态(`cd`、`export`)跨调用持久化;PTY 合并 stderr 到 stdout,返回 dict 的 `stderr` 通常为空;退出码通过 `$?` 哨兵尽力捕获。需要独立进程执行时请用 `sendcmd_oneshot`
|
|
480
502
|
- 返回值: 默认返回 stdout 字符串;当 `only_stdout=False` 时返回 `{'success': bool, 'stdout': str, 'stderr': str}`
|
|
481
503
|
|
|
482
|
-
- **
|
|
483
|
-
- `
|
|
484
|
-
- `
|
|
485
|
-
- `
|
|
486
|
-
- `
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
-
|
|
504
|
+
- **sendcmd_oneshot(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True, echo_output=True)**:独立进程执行命令(原 sendcmd 行为)
|
|
505
|
+
- 每次调用启动独立的 `exec_command` / `subprocess.run` 进程,无持久 shell 状态,与交互 channel 互相独立
|
|
506
|
+
- SSH 路径: 以 `bash -l -c` 包装执行,原生环境变量支持,退出码为 channel 权威退出状态,stderr 为独立流
|
|
507
|
+
- `local_exec` 路径: 通过 `subprocess.run` 执行
|
|
508
|
+
- 参数与返回值与 `sendcmd` 一致(`env_vars` 通过原生环境注入而非内联前缀)
|
|
509
|
+
|
|
510
|
+
- **sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True)**:等同于 `sendcmd`(保留向后兼容)
|
|
511
|
+
- 现直接代理给 `sendcmd`,在持久化交互 shell channel 上执行
|
|
512
|
+
- `expect_prompt`: 保留签名兼容但不再生效(被忽略)
|
|
513
|
+
- 参数与返回值与 `sendcmd` 一致
|
|
490
514
|
- **输出清理**: 返回的 stdout 会自动清理以下内容,便于用户直接使用:
|
|
491
515
|
- CSI 控制序列(如 `\x1b[32m` 颜色代码、`\x1b[?2004h` bracketed paste mode 等)
|
|
492
516
|
- OSC 控制序列(如 `\x1b]0;title\x07` 窗口标题设置等)
|
|
@@ -550,8 +574,10 @@ def test_use_ci_metadata():
|
|
|
550
574
|
- **DockerProxy 容器代理对象**:通过 `environment["container_name"]` 获取
|
|
551
575
|
- 代理对象提供与 Device 一致的接口,自动包装 `docker exec` 命令
|
|
552
576
|
|
|
553
|
-
- **sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True)
|
|
554
|
-
-
|
|
577
|
+
- **sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True)**:非交互式命令(docker session 感知)
|
|
578
|
+
- 已在该容器交互式 session 中: 直接在当前 session 执行命令
|
|
579
|
+
- 其他容器持有交互式 session: 直接返回失败(提示"请退出上一个交互对话")
|
|
580
|
+
- 否则: 包装为 `docker exec {docker_name} {cmd}` 执行(不创建持久 session)
|
|
555
581
|
- 返回值: stdout 字符串或完整字典
|
|
556
582
|
|
|
557
583
|
- **sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True)**:交互式命令
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytest_testkit"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3a1"
|
|
8
8
|
description = "Pytest plugin for Infra logging framework and environment management"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -30,14 +30,12 @@ classifiers = [
|
|
|
30
30
|
]
|
|
31
31
|
keywords = ["pytest", "plugin", "logging", "html", "infra", "test", "automation"]
|
|
32
32
|
dependencies = [
|
|
33
|
-
"pytest
|
|
33
|
+
"pytest>=8.3.2",
|
|
34
34
|
"requests",
|
|
35
35
|
"paramiko",
|
|
36
36
|
"xlrd>=2.0.0",
|
|
37
37
|
"PyYaml",
|
|
38
38
|
"scp",
|
|
39
|
-
"execnet",
|
|
40
|
-
"pybind11",
|
|
41
39
|
]
|
|
42
40
|
|
|
43
41
|
[project.optional-dependencies]
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/device.py
RENAMED
|
@@ -594,10 +594,11 @@ class Device:
|
|
|
594
594
|
return True
|
|
595
595
|
|
|
596
596
|
# 登录动作日志: HTML location 列显示 login[设备短名]
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
)
|
|
597
|
+
# 改成框架日志,不在此处打印
|
|
598
|
+
# logger.info(
|
|
599
|
+
# f"Logging in to device: {self.name}",
|
|
600
|
+
# extra={"op_type": f"login[{_short_name(self.name)}]"},
|
|
601
|
+
# )
|
|
601
602
|
|
|
602
603
|
# Implement actual SSH connection logic
|
|
603
604
|
# 由于有跳板机,需要先登录跳板机,再登录目标设备
|
|
@@ -617,24 +618,27 @@ class Device:
|
|
|
617
618
|
)
|
|
618
619
|
login_result = self._thread_local.sshConnect.login()
|
|
619
620
|
if login_result is False:
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
)
|
|
621
|
+
# 改成框架日志,不在此处打印 这个是否不用修改
|
|
622
|
+
# logger.error(
|
|
623
|
+
# f"ssh login failed for {self.__str__()}",
|
|
624
|
+
# extra={"op_type": f"loginresult[{_short_name(self.name)}]"},
|
|
625
|
+
# )
|
|
624
626
|
return False
|
|
625
627
|
self._set_logged_in(True)
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
)
|
|
628
|
+
# 改成框架日志,不在此处打印
|
|
629
|
+
# logger.info(
|
|
630
|
+
# f"Successfully connected to {self.__str__()}",
|
|
631
|
+
# extra={"op_type": f"loginresult[{_short_name(self.name)}]"},
|
|
632
|
+
# )
|
|
630
633
|
# 登录后立即预创建交互 channel 并吸收 banner,
|
|
631
634
|
# 保证 loginecho 出现在第一条 sendcmd 之前
|
|
632
635
|
login_echo = self._thread_local.sshConnect.prepare_interactive_channel()
|
|
633
636
|
if login_echo:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
)
|
|
637
|
+
# 改成框架日志,不在此处打印
|
|
638
|
+
# logger.info(
|
|
639
|
+
# login_echo,
|
|
640
|
+
# extra={"op_type": f"loginecho[{_short_name(self.name)}]"},
|
|
641
|
+
# )
|
|
638
642
|
self._thread_local.sshConnect._login_echo_logged = True
|
|
639
643
|
return True
|
|
640
644
|
|
|
@@ -750,10 +754,11 @@ class Device:
|
|
|
750
754
|
if getattr(ssh_connect, "login_echo", "") and not getattr(
|
|
751
755
|
ssh_connect, "_login_echo_logged", False
|
|
752
756
|
):
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
)
|
|
757
|
+
# 改成框架日志,不在此处打印
|
|
758
|
+
# logger.info(
|
|
759
|
+
# ssh_connect.login_echo,
|
|
760
|
+
# extra={"op_type": f"loginecho[{_short_name(self.name)}]"},
|
|
761
|
+
# )
|
|
757
762
|
ssh_connect._login_echo_logged = True
|
|
758
763
|
|
|
759
764
|
param = SshCmdInteractiveParam(
|
|
@@ -767,11 +772,10 @@ class Device:
|
|
|
767
772
|
result_dict = ssh_connect.ssh_cmd_interactive(param)
|
|
768
773
|
|
|
769
774
|
# 发送命令日志: HTML location 列显示 sendcmd[设备短名],
|
|
770
|
-
#
|
|
775
|
+
# 消息不带 shell 提示符只打印命令本身
|
|
771
776
|
if echo_output and not _SESSION_QUIET_STDOUT:
|
|
772
777
|
node_tag = f"[{_short_name(self.name)}]"
|
|
773
|
-
|
|
774
|
-
send_msg = f"{prompt}{cmd}" if prompt else cmd
|
|
778
|
+
send_msg = cmd
|
|
775
779
|
if cwd is not None:
|
|
776
780
|
send_msg += f" (cwd: {cwd})"
|
|
777
781
|
logger.info(
|
|
@@ -792,15 +796,17 @@ class Device:
|
|
|
792
796
|
new_prompt = (
|
|
793
797
|
getattr(ssh_connect, "last_prompt", "") if ssh_connect else ""
|
|
794
798
|
)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
799
|
+
# 回显结果以shell提示符结尾移除它, 没有提示符则直接打印不拼装
|
|
800
|
+
if new_prompt and result_echo.endswith(new_prompt):
|
|
801
|
+
recv_msg = result_echo[: -len(new_prompt)].rstrip("\n")
|
|
802
|
+
else:
|
|
803
|
+
recv_msg = result_echo
|
|
800
804
|
logger.info(recv_msg, extra={"op_type": f"recvcmd{node_tag}"})
|
|
801
805
|
else:
|
|
802
806
|
logger.info("(empty)", extra={"op_type": f"recvcmd{node_tag}"})
|
|
803
807
|
|
|
808
|
+
logger.info(f"test tzing: {result_dict}")
|
|
809
|
+
|
|
804
810
|
# 按用户原始 only_stdout 决定返回值
|
|
805
811
|
if only_stdout:
|
|
806
812
|
return result_dict.get("stdout", "")
|
|
@@ -1072,10 +1078,11 @@ class Device:
|
|
|
1072
1078
|
self._thread_local.sshConnect.close()
|
|
1073
1079
|
self._set_logged_in(False)
|
|
1074
1080
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
)
|
|
1081
|
+
# # 移除 放到调度日志中
|
|
1082
|
+
# logger.info(
|
|
1083
|
+
# f"Disconnected from {self.__str__()}",
|
|
1084
|
+
# extra={"op_type": f"disconnect[{_short_name(self.name)}]"},
|
|
1085
|
+
# )
|
|
1079
1086
|
|
|
1080
1087
|
return True
|
|
1081
1088
|
except Exception as e:
|
|
@@ -1064,7 +1064,7 @@ def pytest_runtest_teardown(item):
|
|
|
1064
1064
|
if item.config.env_initialized and hasattr(item, "infra_environments"):
|
|
1065
1065
|
env_manager = item.config.env_manager
|
|
1066
1066
|
env_manager.release_environments(item.nodeid)
|
|
1067
|
-
logger.info(f"Released test environments")
|
|
1067
|
+
# logger.info(f"Released test environments")
|
|
1068
1068
|
|
|
1069
1069
|
result = getattr(item, "infra_test_result", "unknown")
|
|
1070
1070
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest_testkit
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3a1
|
|
4
4
|
Summary: Pytest plugin for Infra logging framework and environment management
|
|
5
5
|
Author: Huawei
|
|
6
6
|
License: MIT License
|
|
@@ -43,14 +43,12 @@ Classifier: Operating System :: OS Independent
|
|
|
43
43
|
Requires-Python: >=3.6
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
License-File: LICENSE
|
|
46
|
-
Requires-Dist: pytest
|
|
46
|
+
Requires-Dist: pytest>=8.3.2
|
|
47
47
|
Requires-Dist: requests
|
|
48
48
|
Requires-Dist: paramiko
|
|
49
49
|
Requires-Dist: xlrd>=2.0.0
|
|
50
50
|
Requires-Dist: PyYaml
|
|
51
51
|
Requires-Dist: scp
|
|
52
|
-
Requires-Dist: execnet
|
|
53
|
-
Requires-Dist: pybind11
|
|
54
52
|
Provides-Extra: dev
|
|
55
53
|
Requires-Dist: build; extra == "dev"
|
|
56
54
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -126,6 +124,8 @@ docker run -d --name <name> -p 2222:22 <image>:<tag>
|
|
|
126
124
|
```
|
|
127
125
|
### logger 使用
|
|
128
126
|
|
|
127
|
+
> **⚠️ 安全提醒:该`logger`为 `pytest-testkit` 插件提供的基础日志能力,请勿在测试用例中打印敏感信息(如密码、密钥、Token 等),以免造成信息泄露。**
|
|
128
|
+
|
|
129
129
|
导入 logger:
|
|
130
130
|
|
|
131
131
|
```python
|
|
@@ -135,7 +135,7 @@ from pytest_testkit.lib.common.log import logger
|
|
|
135
135
|
使用 logger 记录日志:
|
|
136
136
|
|
|
137
137
|
```python
|
|
138
|
-
logger.info(
|
|
138
|
+
logger.info("Successfully connected to device")
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
#### 日志目录结构
|
|
@@ -152,12 +152,30 @@ logs/
|
|
|
152
152
|
|
|
153
153
|
**目录结构说明**:
|
|
154
154
|
- **TestCases**: 测试用例日志根目录
|
|
155
|
-
- **日期目录**: 按执行日期组织(格式:`YYYY-MM-DD
|
|
156
|
-
- **用例子目录**: 按用例名和执行时间命名(格式:`用例名_HH-MM-SS
|
|
155
|
+
- **日期目录**: 按执行日期组织(格式:`YYYY-MM-DD`,使用本机时区)
|
|
156
|
+
- **用例子目录**: 按用例名和执行时间命名(格式:`用例名_HH-MM-SS`,使用本机时区)
|
|
157
157
|
- **HTML 文件**: 插件生成的测试报告,文件名与用例名一致
|
|
158
158
|
|
|
159
159
|
通过 `logger.path` 属性可以获取当前用例的日志归档目录路径,方便在测试用例中创建自定义文件。
|
|
160
160
|
|
|
161
|
+
#### HTML 报告渲染
|
|
162
|
+
|
|
163
|
+
> **⚠️ 安全提醒:HTML 报告会完整渲染日志中记录的命令文本、回显输出及登录 banner 等内容,请勿在命令或日志中携带敏感信息(如密码、密钥、Token 等),以免在报告中明文暴露。**
|
|
164
|
+
|
|
165
|
+
测试用例的 HTML 日志报告支持以下渲染特性:
|
|
166
|
+
|
|
167
|
+
- **op_type 标签**: 命令与连接日志通过 `logging` 的 `extra={"op_type": ...}` 标记操作类型,HTML 报告 Location 列显示该标签(未标记记录回退为 `文件名:行号`)。内置标签包括:
|
|
168
|
+
- `sendcmd[<设备短名>]`: 发送命令,消息带最近 shell 提示符前缀(如 `root@host:~#ls -la`),设置 `cwd` 时追加 `(cwd: ...)`
|
|
169
|
+
- `recvcmd[<设备短名>]`: 命令文本回显(stdout+stderr 合并,不打印 dict),末尾带最新提示符;无输出时显示 `(empty)`
|
|
170
|
+
- `login[<设备短名>]` / `loginresult[<设备短名>]`: 登录开始与结果(登录失败为 ERROR 级别)
|
|
171
|
+
- `loginecho[<设备短名>]`: 登录 banner(交互 channel 首次创建时吸收,在第一条 sendcmd 之前打印)
|
|
172
|
+
- `disconnect[<设备短名>]`: 连接关闭
|
|
173
|
+
- `<设备短名>` 取设备名最后一段(按 `-` 或 `/` 分割),如 `rack1/node_3` → `node_3`
|
|
174
|
+
- **ANSI 颜色渲染**: 报告自动把 ANSI 前景色码(30-37、90-97)转换为带颜色的 `<span>` 标签。例如命令日志中的蓝色码(`\x1b[34m`)渲染为蓝色(`#2196F3`)
|
|
175
|
+
- **错误词高亮**: `recvcmd` 回显中的常见错误词(error、failed、fatal、panic、traceback、timeout、"No such file or directory" 等)会以粗体红色显示,便于快速定位失败。命令行(sendcmd)与普通日志不高亮,避免误伤命令本身
|
|
176
|
+
- **列宽调整**: Level 列 70px、Thread 列 50px、Location 列 128px,Message 列获得更多空间
|
|
177
|
+
- **本地时间**: 会话目录、日期目录、用例子目录均使用本机时区时间,与报告表内日志时间戳一致
|
|
178
|
+
|
|
161
179
|
日志级别:
|
|
162
180
|
|
|
163
181
|
| 级别 | 值 | 方法 | 说明 |
|
|
@@ -411,9 +429,9 @@ def test_environment(logger):
|
|
|
411
429
|
|
|
412
430
|
### quiet_stdout 功能
|
|
413
431
|
|
|
414
|
-
pytest-testkit 插件支持全局静默 `sendcmd` / `
|
|
415
|
-
**开启后,
|
|
416
|
-
|
|
432
|
+
pytest-testkit 插件支持全局静默 `sendcmd` / `sendcmd_oneshot` / `sendcmd_interactive` 的命令与回显日志,避免大量回显淹没日志。
|
|
433
|
+
**开启后,sendcmd / recvcmd 日志都不会打印,即使调用方显式传 `echo_output=True` 也会被全局压制。**
|
|
434
|
+
命令返回值(stdout / stderr / exit_code 等)不受影响,仅影响日志输出。
|
|
417
435
|
|
|
418
436
|
**支持CLI 参数 和 ini 配置,默认为禁用**
|
|
419
437
|
|
|
@@ -528,23 +546,27 @@ def test_use_ci_metadata():
|
|
|
528
546
|
|
|
529
547
|
- **environment/environments**: 提供单设备/多设备对象
|
|
530
548
|
|
|
531
|
-
- **sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True)
|
|
549
|
+
- **sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True, echo_output=True)**:在持久化交互 shell channel 上执行命令
|
|
532
550
|
- `cmd`: 要执行的命令字符串
|
|
533
|
-
- `timeout`:
|
|
534
|
-
- `env_vars`:
|
|
551
|
+
- `timeout`: 命令执行超时时间(秒),支持 int 或 str,默认 600s
|
|
552
|
+
- `env_vars`: 环境变量字典,内联为单命令前缀(`VAR=value cmd`),仅对本次命令生效,不污染持久化 shell
|
|
535
553
|
- `cwd`: 工作目录,命令将在该目录下执行(可选)
|
|
536
554
|
- `ignore_err`: 是否忽略命令执行错误,默认 False。设为 True 时即使命令失败 success 也为 True
|
|
537
555
|
- `only_stdout`: 为 True 时只返回 stdout 字符串,为 False 时返回完整字典
|
|
556
|
+
- `echo_output`: 是否打印 sendcmd/recvcmd 日志,默认 True
|
|
557
|
+
- 行为说明: 同一线程内 shell 状态(`cd`、`export`)跨调用持久化;PTY 合并 stderr 到 stdout,返回 dict 的 `stderr` 通常为空;退出码通过 `$?` 哨兵尽力捕获。需要独立进程执行时请用 `sendcmd_oneshot`
|
|
538
558
|
- 返回值: 默认返回 stdout 字符串;当 `only_stdout=False` 时返回 `{'success': bool, 'stdout': str, 'stderr': str}`
|
|
539
559
|
|
|
540
|
-
- **
|
|
541
|
-
- `
|
|
542
|
-
- `
|
|
543
|
-
- `
|
|
544
|
-
- `
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
-
|
|
560
|
+
- **sendcmd_oneshot(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True, echo_output=True)**:独立进程执行命令(原 sendcmd 行为)
|
|
561
|
+
- 每次调用启动独立的 `exec_command` / `subprocess.run` 进程,无持久 shell 状态,与交互 channel 互相独立
|
|
562
|
+
- SSH 路径: 以 `bash -l -c` 包装执行,原生环境变量支持,退出码为 channel 权威退出状态,stderr 为独立流
|
|
563
|
+
- `local_exec` 路径: 通过 `subprocess.run` 执行
|
|
564
|
+
- 参数与返回值与 `sendcmd` 一致(`env_vars` 通过原生环境注入而非内联前缀)
|
|
565
|
+
|
|
566
|
+
- **sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True)**:等同于 `sendcmd`(保留向后兼容)
|
|
567
|
+
- 现直接代理给 `sendcmd`,在持久化交互 shell channel 上执行
|
|
568
|
+
- `expect_prompt`: 保留签名兼容但不再生效(被忽略)
|
|
569
|
+
- 参数与返回值与 `sendcmd` 一致
|
|
548
570
|
- **输出清理**: 返回的 stdout 会自动清理以下内容,便于用户直接使用:
|
|
549
571
|
- CSI 控制序列(如 `\x1b[32m` 颜色代码、`\x1b[?2004h` bracketed paste mode 等)
|
|
550
572
|
- OSC 控制序列(如 `\x1b]0;title\x07` 窗口标题设置等)
|
|
@@ -608,8 +630,10 @@ def test_use_ci_metadata():
|
|
|
608
630
|
- **DockerProxy 容器代理对象**:通过 `environment["container_name"]` 获取
|
|
609
631
|
- 代理对象提供与 Device 一致的接口,自动包装 `docker exec` 命令
|
|
610
632
|
|
|
611
|
-
- **sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True)
|
|
612
|
-
-
|
|
633
|
+
- **sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True)**:非交互式命令(docker session 感知)
|
|
634
|
+
- 已在该容器交互式 session 中: 直接在当前 session 执行命令
|
|
635
|
+
- 其他容器持有交互式 session: 直接返回失败(提示"请退出上一个交互对话")
|
|
636
|
+
- 否则: 包装为 `docker exec {docker_name} {cmd}` 执行(不创建持久 session)
|
|
613
637
|
- 返回值: stdout 字符串或完整字典
|
|
614
638
|
|
|
615
639
|
- **sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True)**:交互式命令
|
|
@@ -15,7 +15,6 @@ pytest_testkit/lib/__init__.py
|
|
|
15
15
|
pytest_testkit/lib/base/__init__.py
|
|
16
16
|
pytest_testkit/lib/base/excel.py
|
|
17
17
|
pytest_testkit/lib/base/ftp.py
|
|
18
|
-
pytest_testkit/lib/base/http.py
|
|
19
18
|
pytest_testkit/lib/base/local_exec.py
|
|
20
19
|
pytest_testkit/lib/base/sftp.py
|
|
21
20
|
pytest_testkit/lib/base/ssh.py
|
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
# Copyright (C) 2024 Intel Corporation
|
|
2
|
-
# SPDX-License-Identifier: MPL-2.0
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import requests
|
|
6
|
-
from pytest_testkit.lib.common import log
|
|
7
|
-
|
|
8
|
-
HTTP_METHOD_GET = "GET"
|
|
9
|
-
HTTP_METHOD_PUT = "PUT"
|
|
10
|
-
HTTP_METHOD_POST = "POST"
|
|
11
|
-
HTTP_METHOD_PATCH = "PATCH"
|
|
12
|
-
HTTP_METHOD_DELETE = "DELETE"
|
|
13
|
-
HTTP_METHOD_HEAD = "HEAD"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Http:
|
|
17
|
-
"""Base class of Restful"""
|
|
18
|
-
|
|
19
|
-
# 1xx: Informational
|
|
20
|
-
# 2xx: Success
|
|
21
|
-
# 3xx: Redirection
|
|
22
|
-
# 4xx: Client Error
|
|
23
|
-
# 5xx: Server Error
|
|
24
|
-
HttpErrorCode = {
|
|
25
|
-
200: "OK. The request has succeeded.",
|
|
26
|
-
201: "Created. The request has been fulfilled and resulted in a new "
|
|
27
|
-
"resource being created.",
|
|
28
|
-
202: "Accepted as an async process",
|
|
29
|
-
204: "OK no content returned",
|
|
30
|
-
400: "Bad Request. Badly formed URI, parameters, headers, or body content.",
|
|
31
|
-
401: "Unauthorized. The request requires user authentication.",
|
|
32
|
-
403: "Forbidden. The request was valid, but the server is refusing action.",
|
|
33
|
-
404: "Not Found. Resource doesn't exist.",
|
|
34
|
-
405: "Method Not Allowed.",
|
|
35
|
-
406: "Not Acceptable. Accept headers are not satisfiable.",
|
|
36
|
-
409: "Conflict. The request could not be completed due to a conflict "
|
|
37
|
-
"with the current state of the resource.",
|
|
38
|
-
412: "Precondition Failed. The precondition given in the "
|
|
39
|
-
"request-header fields evaluated to false on the server.",
|
|
40
|
-
422: "Unprocessable Entity, Semantically invalid content on a POST.",
|
|
41
|
-
500: "Internal Server Error.",
|
|
42
|
-
503: "Service Unavailable.",
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
def __init__(self, max_retry=0):
|
|
46
|
-
self.logger = log.get_logger(str(self.__module__))
|
|
47
|
-
# pgxun: 此赋值用于指定对外公开哪些函数。
|
|
48
|
-
self.method_release_requirement = []
|
|
49
|
-
self.session = requests.Session()
|
|
50
|
-
|
|
51
|
-
# 设置HTTP命令重试的次数。0的缺省值表示不更改HTTP模块的重试次数
|
|
52
|
-
# (requests模块的缺省值是:0,不重试)。
|
|
53
|
-
"""
|
|
54
|
-
max_retries: The maximum number of retries each connection
|
|
55
|
-
should attempt. Note, this applies only to failed DNS lookups, socket
|
|
56
|
-
connections and connection timeouts, never to requests where data has
|
|
57
|
-
made it to the server. By default, Requests does not retry failed
|
|
58
|
-
connections.
|
|
59
|
-
"""
|
|
60
|
-
if max_retry != 0:
|
|
61
|
-
retry_counts = requests.adapters.HTTPAdapter(max_retries=max_retry)
|
|
62
|
-
self.session.mount("http://", retry_counts)
|
|
63
|
-
self.session.mount("https://", retry_counts)
|
|
64
|
-
|
|
65
|
-
def __convert_json_list_to_table(self, json_list):
|
|
66
|
-
"""Convert json list to table"""
|
|
67
|
-
|
|
68
|
-
# calculate column width
|
|
69
|
-
headers = self.__calculate_column_width(json_list)
|
|
70
|
-
text_string = "-------------- Title --------------\n"
|
|
71
|
-
pattern = "{0:{width}s}"
|
|
72
|
-
|
|
73
|
-
# format headers
|
|
74
|
-
for name, width in headers.items():
|
|
75
|
-
text_string = text_string + pattern.format(name, width=width)
|
|
76
|
-
|
|
77
|
-
text_string = text_string + "\n"
|
|
78
|
-
for _, width in headers.items():
|
|
79
|
-
split = "-" * (
|
|
80
|
-
width - 4
|
|
81
|
-
) # 4 space char, the same to __calculateColumnWidth
|
|
82
|
-
text_string = text_string + pattern.format(split, width=width)
|
|
83
|
-
|
|
84
|
-
# format values
|
|
85
|
-
for item in json_list:
|
|
86
|
-
text_string = text_string + "\n"
|
|
87
|
-
for name, width in headers.items():
|
|
88
|
-
text_string = text_string + pattern.format(
|
|
89
|
-
str(item.get(name)), width=width
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
return text_string + "\n"
|
|
93
|
-
|
|
94
|
-
@staticmethod
|
|
95
|
-
def __calculate_column_width(json_list):
|
|
96
|
-
"""Calculate table column width according to json's key-value"""
|
|
97
|
-
|
|
98
|
-
headers = {}
|
|
99
|
-
if not isinstance(json_list, list):
|
|
100
|
-
return headers
|
|
101
|
-
|
|
102
|
-
# at least 5 chars for each column, in the commonParser, he set
|
|
103
|
-
# splitter must has 5 '-'s, i don't why
|
|
104
|
-
min_header_with = 5
|
|
105
|
-
fixed_space_width = 4 # add 4 space by default
|
|
106
|
-
for js in json_list:
|
|
107
|
-
for key, value in js.items():
|
|
108
|
-
value_length = len(str(value)) + fixed_space_width
|
|
109
|
-
if key in headers.keys():
|
|
110
|
-
headers[key] = max(value_length, headers[key])
|
|
111
|
-
else:
|
|
112
|
-
key_length = len(str(key)) + fixed_space_width
|
|
113
|
-
headers[key] = max(
|
|
114
|
-
key_length, value_length, min_header_with + fixed_space_width
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
return headers
|
|
118
|
-
|
|
119
|
-
@staticmethod
|
|
120
|
-
def __convert_dict_items_to_json(params):
|
|
121
|
-
"""Convert dict type parameters in given Http parameters to json type"""
|
|
122
|
-
for key, value in params.items():
|
|
123
|
-
if key.lower() not in ["data", "params"]:
|
|
124
|
-
continue
|
|
125
|
-
|
|
126
|
-
if isinstance(value, dict):
|
|
127
|
-
params[key] = json.dumps(value)
|
|
128
|
-
return params
|
|
129
|
-
|
|
130
|
-
def __log_request(self, method=None, url=None, **params):
|
|
131
|
-
"""log out user's request parameters"""
|
|
132
|
-
self.logger.info(
|
|
133
|
-
"-----> Http request method: %s, url: %s <-----" % (method, url)
|
|
134
|
-
)
|
|
135
|
-
if len(str(params["data"])) > 100:
|
|
136
|
-
self.logger.info(
|
|
137
|
-
"Request params(data too long, only headers shown): %s"
|
|
138
|
-
% params["headers"]
|
|
139
|
-
)
|
|
140
|
-
else:
|
|
141
|
-
self.logger.info("Request params: %s" % params)
|
|
142
|
-
|
|
143
|
-
def __handle_http_error_code(self, status_code):
|
|
144
|
-
"""Return error message according to Http status code"""
|
|
145
|
-
status_code = int(status_code)
|
|
146
|
-
if self.HttpErrorCode.has_key(status_code):
|
|
147
|
-
return self.HttpErrorCode[status_code]
|
|
148
|
-
else:
|
|
149
|
-
return "<Error Info Absent>."
|
|
150
|
-
|
|
151
|
-
def __log_response(self, response):
|
|
152
|
-
"""log out response message according to response status code
|
|
153
|
-
pgxun: 打印返回消息的日志。
|
|
154
|
-
"""
|
|
155
|
-
|
|
156
|
-
# 1xx: Informational
|
|
157
|
-
# 2xx: Success
|
|
158
|
-
# 3xx: Redirection
|
|
159
|
-
# 4xx: Client Error
|
|
160
|
-
# 5xx: Server Error
|
|
161
|
-
|
|
162
|
-
if not response.ok:
|
|
163
|
-
msg = "Response error. Status code: %s, elapsed: %s, message: %s" % (
|
|
164
|
-
response.status_code,
|
|
165
|
-
response.elapsed,
|
|
166
|
-
self.__handle_http_error_code(response.status_code),
|
|
167
|
-
)
|
|
168
|
-
self.logger.warning(str(msg))
|
|
169
|
-
else:
|
|
170
|
-
self.logger.info(
|
|
171
|
-
"-----> Http response success, status code: "
|
|
172
|
-
+ str(response.status_code)
|
|
173
|
-
+ " <-----"
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
# 规避一个下载文件接口的问题
|
|
177
|
-
if response.text.find("{") < 0:
|
|
178
|
-
msg = "Response elapsed: %s, message: %s" % (
|
|
179
|
-
response.elapsed,
|
|
180
|
-
"This interface is used to download file, no json response data",
|
|
181
|
-
)
|
|
182
|
-
else:
|
|
183
|
-
# 说明:如果转换为dict再打印,会使得打印的key排序和得到的响应的原始顺序
|
|
184
|
-
# 不一样,导致用户观察结果不方便。
|
|
185
|
-
msg = "Response elapsed: {}, message: \n{}".format(
|
|
186
|
-
response.elapsed, response.text
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# 部分告警信息中有中文句号,要特殊处理一下
|
|
190
|
-
self.logger.info(str(msg))
|
|
191
|
-
|
|
192
|
-
def convert_json_to_table(self, json_obj):
|
|
193
|
-
"""Convert json object to table text string
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
jsonList (list) : Json list to be convert
|
|
197
|
-
|
|
198
|
-
Returns: A pretty formatted table text string.
|
|
199
|
-
|
|
200
|
-
Raises:
|
|
201
|
-
InvalidParamException
|
|
202
|
-
|
|
203
|
-
Examples:
|
|
204
|
-
In:
|
|
205
|
-
[{u'COUNT': u'19034', u'ISONLINE': u'1',
|
|
206
|
-
u'DESCRIPTION': u'Default user', u'LEVEL': u'1',
|
|
207
|
-
u'LOCKSTATUS': u'0', u'CREATETIME': u'1440243958',
|
|
208
|
-
u'SCOPE': u'0', u'TYPE': 202, u'ID': u'admin',
|
|
209
|
-
u'NAME': u'admin'}]
|
|
210
|
-
Out:
|
|
211
|
-
-------------- Title --------------
|
|
212
|
-
COUNT ISONLINE DESCRIPTION LOCKSTATUS LEVEL ID SCOPE TYPE CREATETIME NAME
|
|
213
|
-
----- -------- ------------ ---------- ----- ----- ----- ---- ---------- -----
|
|
214
|
-
19083 1 Default user 0 1 admin 0 202 1440243958 admin
|
|
215
|
-
|
|
216
|
-
"""
|
|
217
|
-
|
|
218
|
-
json_list = json_obj
|
|
219
|
-
if isinstance(json_obj, dict):
|
|
220
|
-
json_list = [json_obj]
|
|
221
|
-
|
|
222
|
-
# json is an array, means it contains many tables
|
|
223
|
-
if len(json_list) > 0:
|
|
224
|
-
if not all(isinstance(x, dict) for x in json_list):
|
|
225
|
-
self.logger.warning(
|
|
226
|
-
"Can not deal with json object which contains array, "
|
|
227
|
-
"contact us please. data:" + json
|
|
228
|
-
)
|
|
229
|
-
return []
|
|
230
|
-
return self.__convert_json_list_to_table(json_list)
|
|
231
|
-
else:
|
|
232
|
-
return []
|
|
233
|
-
|
|
234
|
-
def encode_json_to_utf8(self, unicode_json):
|
|
235
|
-
"""Convert unicode json encoding to utf-8"""
|
|
236
|
-
|
|
237
|
-
if isinstance(unicode_json, dict):
|
|
238
|
-
return {
|
|
239
|
-
self.encode_json_to_utf8(key): self.encode_json_to_utf8(value)
|
|
240
|
-
for key, value in unicode_json.iteritems()
|
|
241
|
-
}
|
|
242
|
-
elif isinstance(unicode_json, list):
|
|
243
|
-
return [self.encode_json_to_utf8(item) for item in unicode_json]
|
|
244
|
-
elif isinstance(unicode_json, unicode):
|
|
245
|
-
# deal with special character in the response
|
|
246
|
-
return (
|
|
247
|
-
unicode_json
|
|
248
|
-
.encode("utf-8")
|
|
249
|
-
.replace("(", "(")
|
|
250
|
-
.replace(")", ")")
|
|
251
|
-
.replace(">", ">")
|
|
252
|
-
.replace("<", "<")
|
|
253
|
-
.replace("&", "&")
|
|
254
|
-
.replace("\r", "")
|
|
255
|
-
.replace("\n", "")
|
|
256
|
-
)
|
|
257
|
-
else:
|
|
258
|
-
return unicode_json
|
|
259
|
-
|
|
260
|
-
"""
|
|
261
|
-
pgxun说明:
|
|
262
|
-
requests的request函数定义如下:
|
|
263
|
-
requests.request(method, url, **kwargs)[source]
|
|
264
|
-
method:是HTTP动作的名字,如GET、PUT等。
|
|
265
|
-
url -- URL for the new Request object.
|
|
266
|
-
kwargs -- (optional) Dictionary or bytes to be sent in the query string for the Request.
|
|
267
|
-
kwargs包括以下12个可选的内容,常用的有:
|
|
268
|
-
data -- 这是请求的body部分,可以是:Dict, bytes, or file-like object to send in the body of the Request.
|
|
269
|
-
json -- 这是请求的body部分,JSON格式。json data to send in the body of the Request.
|
|
270
|
-
headers -- 这是请求的Headers部分,dict格式。 Dictionary of HTTP Headers to send with the Request.
|
|
271
|
-
等等
|
|
272
|
-
返回值:request命令的响应response。
|
|
273
|
-
注意:
|
|
274
|
-
1、你也可以不使用params,而是直接将12个可选内容写在request的调用参数列表中。
|
|
275
|
-
2、params中可以直接写入"key=value"的字段格式,如果不是12个可选的内容,则会作为url的参数,例如:
|
|
276
|
-
payload = {'key1': 'value1', 'key2': 'value2'}
|
|
277
|
-
|
|
278
|
-
3、params传入的示例:
|
|
279
|
-
|
|
280
|
-
"""
|
|
281
|
-
|
|
282
|
-
def request(self, method, url, **kwargs):
|
|
283
|
-
"""Make a Http request according to given method
|
|
284
|
-
|
|
285
|
-
Args:
|
|
286
|
-
method (str) : Http method, values: get/post/put/delete
|
|
287
|
-
url: (str) : Url for the request
|
|
288
|
-
kwargs: (dict) : (Optional). Parameters see bellow
|
|
289
|
-
|
|
290
|
-
Field Type Optional Description
|
|
291
|
-
==============================================================================
|
|
292
|
-
其它非12个关键字 dict True Dictionary to be sent in the query string for the request.
|
|
293
|
-
data dict True Dictionary to send in the body of the request.
|
|
294
|
-
headers dict True Dictionary of HTTP Headers to send with the request.
|
|
295
|
-
timeout float
|
|
296
|
-
|
|
297
|
-
def request(self, method, url,
|
|
298
|
-
params=None, data=None, headers=None, cookies=None, files=None,
|
|
299
|
-
auth=None, timeout=None, allow_redirects=True, proxies=None,
|
|
300
|
-
hooks=None, stream=None, verify=None, cert=None, json=None):
|
|
301
|
-
:param method: method for the new :class:`Request` object.
|
|
302
|
-
:param url: URL for the new :class:`Request` object.
|
|
303
|
-
:param params: (optional) Dictionary or bytes to be sent in the query
|
|
304
|
-
string for the :class:`Request`.
|
|
305
|
-
:param data: (optional) Dictionary, bytes, or file-like object to send
|
|
306
|
-
in the body of the :class:`Request`.
|
|
307
|
-
:param json: (optional) json to send in the body of the
|
|
308
|
-
:class:`Request`.
|
|
309
|
-
:param headers: (optional) Dictionary of HTTP Headers to send with the
|
|
310
|
-
:class:`Request`.
|
|
311
|
-
:param cookies: (optional) Dict or CookieJar object to send with the
|
|
312
|
-
:class:`Request`.
|
|
313
|
-
:param files: (optional) Dictionary of ``'filename': file-like-objects``
|
|
314
|
-
for multipart encoding upload.
|
|
315
|
-
:param auth: (optional) Auth tuple or callable to enable
|
|
316
|
-
Basic/Digest/Custom HTTP Auth.
|
|
317
|
-
:param timeout: (optional) How long to wait for the server to send
|
|
318
|
-
data before giving up, as a float, or a :ref:`(connect timeout,
|
|
319
|
-
read timeout) <timeouts>` tuple.
|
|
320
|
-
:type timeout: float or tuple
|
|
321
|
-
:param allow_redirects: (optional) Set to True by default.
|
|
322
|
-
:type allow_redirects: bool
|
|
323
|
-
:param proxies: (optional) Dictionary mapping protocol or protocol and
|
|
324
|
-
hostname to the URL of the proxy.
|
|
325
|
-
:param stream: (optional) whether to immediately download the response
|
|
326
|
-
content. Defaults to ``False``.
|
|
327
|
-
:param verify: (optional) Either a boolean, in which case it controls whether we verify
|
|
328
|
-
the server's TLS certificate, or a string, in which case it must be a path
|
|
329
|
-
to a CA bundle to use. Defaults to ``True``.
|
|
330
|
-
:param cert: (optional) if String, path to ssl client cert file (.pem).
|
|
331
|
-
If Tuple, ('cert', 'key') pair.
|
|
332
|
-
:rtype: requests.Response
|
|
333
|
-
|
|
334
|
-
Returns:
|
|
335
|
-
Http response (Response) : A response object.
|
|
336
|
-
Usage:
|
|
337
|
-
r.ok (bool) : Whether the request is ok. Return True/False
|
|
338
|
-
r.status_code (int) : Http response code. Int
|
|
339
|
-
r.elapsed (time) : Request used time. e.g. 0:00:01.662000
|
|
340
|
-
r.json() (json) : Response content as json format
|
|
341
|
-
r.text (str) : Response content as text string format
|
|
342
|
-
|
|
343
|
-
Raises:
|
|
344
|
-
None
|
|
345
|
-
|
|
346
|
-
Examples:
|
|
347
|
-
|
|
348
|
-
"""
|
|
349
|
-
# 请注意在参数中指定Timout参数,否则可能会等很久才会异常结束。
|
|
350
|
-
kwargs["verify"] = False
|
|
351
|
-
# 下面的函数将每个字段的值都转化成了json格式。
|
|
352
|
-
kwargs = self.__convert_dict_items_to_json(kwargs)
|
|
353
|
-
method = str(method).lower()
|
|
354
|
-
|
|
355
|
-
def send_request():
|
|
356
|
-
self.__log_request(method, url, **kwargs)
|
|
357
|
-
|
|
358
|
-
if method == "get":
|
|
359
|
-
r = self.session.get(url, **kwargs)
|
|
360
|
-
elif method == "post":
|
|
361
|
-
r = self.session.post(url, **kwargs)
|
|
362
|
-
elif method == "put":
|
|
363
|
-
r = self.session.put(url, **kwargs)
|
|
364
|
-
elif method == "delete":
|
|
365
|
-
r = self.session.delete(url, **kwargs)
|
|
366
|
-
elif method == "patch":
|
|
367
|
-
r = self.session.patch(url, **kwargs)
|
|
368
|
-
elif method == "head":
|
|
369
|
-
r = self.session.head(url, **kwargs)
|
|
370
|
-
else:
|
|
371
|
-
self.logger.warning("method not implemented: " + method)
|
|
372
|
-
return None
|
|
373
|
-
|
|
374
|
-
self.__log_response(r)
|
|
375
|
-
return r
|
|
376
|
-
|
|
377
|
-
try:
|
|
378
|
-
result = send_request()
|
|
379
|
-
except Exception as err:
|
|
380
|
-
self.logger.warning("Send request failed: ", err)
|
|
381
|
-
|
|
382
|
-
return result
|
|
383
|
-
|
|
384
|
-
def get(self, url, **kwargs):
|
|
385
|
-
return self.request("get", url, kwargs)
|
|
386
|
-
|
|
387
|
-
def put(self, url, **kwargs):
|
|
388
|
-
return self.request("put", url, kwargs)
|
|
389
|
-
|
|
390
|
-
def post(self, url, **kwargs):
|
|
391
|
-
return self.request("post", url, kwargs)
|
|
392
|
-
|
|
393
|
-
def patch(self, url, **kwargs):
|
|
394
|
-
return self.request("patch", url, kwargs)
|
|
395
|
-
|
|
396
|
-
def delete(self, url, **kwargs):
|
|
397
|
-
return self.request("delete", url, kwargs)
|
|
398
|
-
|
|
399
|
-
def head(self, url, **kwargs):
|
|
400
|
-
return self.request("head", url, kwargs)
|
|
401
|
-
|
|
402
|
-
def get_headers(self):
|
|
403
|
-
"""Get restful request headers"""
|
|
404
|
-
return self.session.headers
|
|
405
|
-
|
|
406
|
-
def update_headers(self, params):
|
|
407
|
-
"""Update restful request headers
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
params (dict) : Http header options.
|
|
411
|
-
More information please see
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
E.g.
|
|
415
|
-
Field Type Optional Description
|
|
416
|
-
==============================================================================
|
|
417
|
-
accept-encoding str True List of acceptable encodings. See HTTP compression
|
|
418
|
-
content-type str True The MIME type of body of the request. E.g.application/json
|
|
419
|
-
|
|
420
|
-
Returns:
|
|
421
|
-
None
|
|
422
|
-
|
|
423
|
-
Raises:
|
|
424
|
-
None
|
|
425
|
-
|
|
426
|
-
Examples:
|
|
427
|
-
restSession.updateHeaders({'content-type': 'application/json'})
|
|
428
|
-
|
|
429
|
-
"""
|
|
430
|
-
|
|
431
|
-
self.session.headers.update(params)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/__init__.py
RENAMED
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/allocator.py
RENAMED
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/environment/manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/common/log/infra_logger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit/lib/pre_action/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/pytest_testkit.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_is_interactive_shell.py
RENAMED
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_non_shell_commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_testkit-1.0.2b1 → pytest_testkit-1.0.3a1}/tests/test_docker_proxy_sendcmd_interactive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|