cli-test-framework 0.2.3__tar.gz → 0.3.0__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.
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/CHANGELOG.md +60 -0
- {cli_test_framework-0.2.3/src/cli_test_framework.egg-info → cli_test_framework-0.3.0}/PKG-INFO +3 -3
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/docs/user_manual.md +153 -6
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/setup.py +3 -3
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/__init__.py +9 -1
- cli_test_framework-0.3.0/src/cli_test_framework/core/base_runner.py +81 -0
- cli_test_framework-0.3.0/src/cli_test_framework/core/parallel_runner.py +125 -0
- cli_test_framework-0.3.0/src/cli_test_framework/core/setup.py +134 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/runners/json_runner.py +3 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/runners/parallel_json_runner.py +3 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/runners/yaml_runner.py +3 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/utils/path_resolver.py +13 -3
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0/src/cli_test_framework.egg-info}/PKG-INFO +3 -3
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework.egg-info/SOURCES.txt +6 -1
- cli_test_framework-0.3.0/tests/__pycache__/test_setup_module.cpython-312-pytest-7.4.4.pyc +0 -0
- cli_test_framework-0.3.0/tests/fixtures/test_with_setup.json +42 -0
- cli_test_framework-0.3.0/tests/fixtures/test_with_setup.yaml +42 -0
- cli_test_framework-0.3.0/tests/test_setup_module.py +352 -0
- cli_test_framework-0.2.3/src/cli_test_framework/core/base_runner.py +0 -56
- cli_test_framework-0.2.3/src/cli_test_framework/core/parallel_runner.py +0 -118
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/MANIFEST.in +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/README.md +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/pyproject.toml +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/setup.cfg +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/cli.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/commands/__init__.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/commands/compare.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/core/__init__.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/core/assertions.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/core/process_worker.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/core/test_case.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/__init__.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/base_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/binary_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/csv_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/factory.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/h5_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/json_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/result.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/text_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/file_comparator/xml_comparator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/runners/__init__.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/utils/__init__.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/utils/report_generator.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework.egg-info/dependency_links.txt +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework.egg-info/entry_points.txt +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework.egg-info/requires.txt +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework.egg-info/top_level.txt +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/__init__.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/__pycache__/test_parallel_runner.cpython-312-pytest-7.4.4.pyc +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/fixtures/test_cases.json +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/fixtures/test_cases.yaml +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/fixtures/test_cases1.json +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/performance_test.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/test1.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/test_comprehensive_space.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/test_parallel_runner.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/test_parallel_space.py +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/test_report.txt +0 -0
- {cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/tests/test_runners.py +0 -0
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# 变更日志
|
|
2
2
|
|
|
3
|
+
## [v0.3.0] - 2025-06-07
|
|
4
|
+
|
|
5
|
+
### 🎉 重大新功能 - Setup模块系统
|
|
6
|
+
|
|
7
|
+
#### 插件化Setup架构
|
|
8
|
+
- **BaseSetup抽象基类**:定义了setup插件的标准接口,支持setup()和teardown()方法
|
|
9
|
+
- **SetupManager管理器**:统一管理多个setup插件的生命周期
|
|
10
|
+
- **可扩展设计**:用户可以轻松创建自定义setup插件
|
|
11
|
+
|
|
12
|
+
#### 内置环境变量插件
|
|
13
|
+
- **EnvironmentSetup插件**:自动设置和管理环境变量
|
|
14
|
+
- **智能恢复机制**:测试完成后自动恢复原始环境状态,支持新增和覆盖的环境变量
|
|
15
|
+
- **配置文件集成**:直接在JSON/YAML测试配置中定义环境变量
|
|
16
|
+
|
|
17
|
+
#### 全面Runner集成
|
|
18
|
+
- **JSONRunner**:完全支持setup配置的加载和执行
|
|
19
|
+
- **YAMLRunner**:完全支持setup配置的加载和执行
|
|
20
|
+
- **ParallelJSONRunner**:完全支持setup配置,在并行模式下正确处理setup/teardown
|
|
21
|
+
- **自动生命周期**:setup和teardown在测试前后自动执行
|
|
22
|
+
|
|
23
|
+
#### 配置文件语法扩展
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"setup": {
|
|
27
|
+
"environment_variables": {
|
|
28
|
+
"TEST_ENV": "development",
|
|
29
|
+
"API_URL": "http://localhost:8080"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"test_cases": [...]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 🔧 改进和修复
|
|
37
|
+
- **Python版本要求**:从Python 3.6+更新为Python 3.9+(由于依赖版本要求)
|
|
38
|
+
- **错误处理增强**:改进了setup/teardown的异常处理,使用try-finally确保清理总是执行
|
|
39
|
+
- **并行安全**:Setup模块在并行执行模式下的线程安全处理
|
|
40
|
+
- **包导出修复**:移除了错误的FileComparator导出
|
|
41
|
+
|
|
42
|
+
### 🧪 测试覆盖
|
|
43
|
+
- **新增16个单元测试**:覆盖setup模块所有核心功能
|
|
44
|
+
- **集成测试**:确保与所有runner的兼容性
|
|
45
|
+
- **边界条件测试**:测试空配置、错误情况等边界条件
|
|
46
|
+
|
|
47
|
+
### 📚 文档更新
|
|
48
|
+
- **用户手册扩展**:新增完整的Setup模块章节,包含详细使用说明和最佳实践
|
|
49
|
+
- **示例代码**:提供了完整的使用示例和自定义插件示例
|
|
50
|
+
- **API文档**:完整的setup模块API文档和类型注解
|
|
51
|
+
|
|
52
|
+
### ⚠️ 破坏性变更
|
|
53
|
+
- **Python版本要求**:从3.6+更新为3.9+
|
|
54
|
+
- **包结构调整**:setup模块相关类现在从cli_test_framework包导出
|
|
55
|
+
|
|
56
|
+
### 🚀 迁移指南
|
|
57
|
+
- **现有代码兼容**:现有的测试配置文件无需修改即可正常工作
|
|
58
|
+
- **新功能使用**:在配置文件中添加setup部分即可使用环境变量设置
|
|
59
|
+
- **自定义插件**:继承BaseSetup类创建自定义setup插件
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
3
63
|
## [v2.0.0] - 2025-05-27
|
|
4
64
|
|
|
5
65
|
### 🚀 重大新功能
|
{cli_test_framework-0.2.3/src/cli_test_framework.egg-info → cli_test_framework-0.3.0}/PKG-INFO
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-test-framework
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.
|
|
5
5
|
Home-page: https://github.com/yourusername/cli-test-framework
|
|
6
6
|
Author: Xiaotong Wang
|
|
7
7
|
Author-email: xiaotongwang98@gmail.com
|
|
@@ -15,7 +15,7 @@ Classifier: Development Status :: 4 - Beta
|
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Topic :: Software Development :: Testing
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
Requires-Dist: dukpy==0.5.0
|
|
21
21
|
Requires-Dist: h5py>=3.8.0
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
2. [Installation](#installation)
|
|
6
6
|
3. [Basic Usage](#basic-usage)
|
|
7
7
|
4. [Test Case Definition](#test-case-definition)
|
|
8
|
-
5. [
|
|
9
|
-
6. [
|
|
10
|
-
7. [
|
|
11
|
-
8. [
|
|
12
|
-
9. [
|
|
13
|
-
10. [
|
|
8
|
+
5. [Setup Module](#setup-module)
|
|
9
|
+
6. [Parallel Testing](#parallel-testing)
|
|
10
|
+
7. [File Comparison](#file-comparison)
|
|
11
|
+
8. [Advanced Features](#advanced-features)
|
|
12
|
+
9. [Troubleshooting](#troubleshooting)
|
|
13
|
+
10. [API Reference](#api-reference)
|
|
14
|
+
11. [Examples](#examples)
|
|
14
15
|
|
|
15
16
|
## Introduction
|
|
16
17
|
|
|
@@ -121,6 +122,152 @@ test_cases:
|
|
|
121
122
|
- ".*regex pattern.*"
|
|
122
123
|
```
|
|
123
124
|
|
|
125
|
+
## Setup Module
|
|
126
|
+
|
|
127
|
+
The Setup Module provides a plugin-based system for executing pre-test setup tasks and post-test cleanup. It's designed to be extensible and supports multiple setup plugins running in sequence.
|
|
128
|
+
|
|
129
|
+
### Key Features
|
|
130
|
+
- **Plugin Architecture**: Extensible design allowing custom setup plugins
|
|
131
|
+
- **Built-in Environment Plugin**: Set environment variables for tests
|
|
132
|
+
- **Full Runner Support**: Works with JSONRunner, YAMLRunner, and ParallelJSONRunner
|
|
133
|
+
- **Lifecycle Management**: Automatic setup and teardown with proper cleanup
|
|
134
|
+
|
|
135
|
+
### Environment Variable Setup
|
|
136
|
+
|
|
137
|
+
#### JSON Configuration
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"setup": {
|
|
141
|
+
"environment_variables": {
|
|
142
|
+
"TEST_ENV": "development",
|
|
143
|
+
"DEBUG_MODE": "true",
|
|
144
|
+
"API_URL": "http://localhost:8080",
|
|
145
|
+
"DATABASE_URL": "sqlite:///test.db"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"test_cases": [
|
|
149
|
+
{
|
|
150
|
+
"name": "Test with environment variables",
|
|
151
|
+
"command": "python",
|
|
152
|
+
"args": ["-c", "import os; print(f'Env: {os.environ.get(\"TEST_ENV\")}')"],
|
|
153
|
+
"expected": {
|
|
154
|
+
"return_code": 0,
|
|
155
|
+
"output_contains": ["Env: development"]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### YAML Configuration
|
|
163
|
+
```yaml
|
|
164
|
+
setup:
|
|
165
|
+
environment_variables:
|
|
166
|
+
TEST_ENV: "production"
|
|
167
|
+
DATABASE_URL: "postgresql://localhost:5432/test"
|
|
168
|
+
MAX_CONNECTIONS: "10"
|
|
169
|
+
TIMEOUT_SECONDS: "30"
|
|
170
|
+
|
|
171
|
+
test_cases:
|
|
172
|
+
- name: "Test database environment"
|
|
173
|
+
command: "python"
|
|
174
|
+
args:
|
|
175
|
+
- "-c"
|
|
176
|
+
- "import os; print(f'DB: {os.environ.get(\"DATABASE_URL\")}')"
|
|
177
|
+
expected:
|
|
178
|
+
return_code: 0
|
|
179
|
+
output_contains:
|
|
180
|
+
- "DB: postgresql://localhost:5432/test"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Custom Setup Plugins
|
|
184
|
+
|
|
185
|
+
#### Creating Custom Plugins
|
|
186
|
+
```python
|
|
187
|
+
from cli_test_framework import BaseSetup
|
|
188
|
+
|
|
189
|
+
class DatabaseSetup(BaseSetup):
|
|
190
|
+
def setup(self):
|
|
191
|
+
"""Initialize test database"""
|
|
192
|
+
print("Setting up test database...")
|
|
193
|
+
# Your database initialization code here
|
|
194
|
+
|
|
195
|
+
def teardown(self):
|
|
196
|
+
"""Clean up test database"""
|
|
197
|
+
print("Cleaning up test database...")
|
|
198
|
+
# Your database cleanup code here
|
|
199
|
+
|
|
200
|
+
class ServiceSetup(BaseSetup):
|
|
201
|
+
def setup(self):
|
|
202
|
+
"""Start test services"""
|
|
203
|
+
self.service_port = self.config.get('port', 8080)
|
|
204
|
+
print(f"Starting test service on port {self.service_port}")
|
|
205
|
+
# Your service startup code here
|
|
206
|
+
|
|
207
|
+
def teardown(self):
|
|
208
|
+
"""Stop test services"""
|
|
209
|
+
print("Stopping test services...")
|
|
210
|
+
# Your service shutdown code here
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Using Custom Plugins
|
|
214
|
+
```python
|
|
215
|
+
from cli_test_framework import JSONRunner
|
|
216
|
+
|
|
217
|
+
# Create runner
|
|
218
|
+
runner = JSONRunner("test_cases.json")
|
|
219
|
+
|
|
220
|
+
# Add custom setup plugins
|
|
221
|
+
db_setup = DatabaseSetup({"connection": "test_db"})
|
|
222
|
+
service_setup = ServiceSetup({"port": 9090})
|
|
223
|
+
|
|
224
|
+
runner.setup_manager.add_setup(db_setup)
|
|
225
|
+
runner.setup_manager.add_setup(service_setup)
|
|
226
|
+
|
|
227
|
+
# Run tests (setup will be executed automatically)
|
|
228
|
+
success = runner.run_tests()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Execution Flow
|
|
232
|
+
|
|
233
|
+
1. **Load Configuration**: Read setup configuration from test file
|
|
234
|
+
2. **Setup Phase**: Execute all setup plugins in order
|
|
235
|
+
- Environment variables are set
|
|
236
|
+
- Custom setups are executed
|
|
237
|
+
- Setup status is reported
|
|
238
|
+
3. **Test Execution**: Run all test cases with setup environment
|
|
239
|
+
4. **Teardown Phase**: Clean up all setups in reverse order
|
|
240
|
+
- Environment variables are restored
|
|
241
|
+
- Custom cleanups are executed
|
|
242
|
+
- Cleanup is guaranteed even if tests fail
|
|
243
|
+
|
|
244
|
+
### Best Practices
|
|
245
|
+
|
|
246
|
+
1. **Idempotent Operations**: Make setup operations safe to run multiple times
|
|
247
|
+
2. **Proper Cleanup**: Always implement teardown to avoid side effects
|
|
248
|
+
3. **Error Handling**: Setup failures stop test execution, teardown failures don't
|
|
249
|
+
4. **Resource Management**: Use try-finally patterns in custom plugins
|
|
250
|
+
5. **Configuration Validation**: Check required configuration parameters in setup
|
|
251
|
+
|
|
252
|
+
### Integration Examples
|
|
253
|
+
|
|
254
|
+
#### With JSON Runner
|
|
255
|
+
```bash
|
|
256
|
+
cli-test test_with_setup.json
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### With YAML Runner
|
|
260
|
+
```bash
|
|
261
|
+
cli-test test_with_setup.yaml --runner yaml
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### With Parallel Runner
|
|
265
|
+
```bash
|
|
266
|
+
cli-test test_with_setup.json --runner parallel --max-workers 4
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Note: In parallel mode, setup and teardown run in the main thread to ensure environment consistency.
|
|
270
|
+
|
|
124
271
|
## Parallel Testing
|
|
125
272
|
|
|
126
273
|
### Thread Mode
|
|
@@ -8,10 +8,10 @@ with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="cli-test-framework",
|
|
11
|
-
version="0.
|
|
11
|
+
version="0.3.0",
|
|
12
12
|
author="Xiaotong Wang",
|
|
13
13
|
author_email="xiaotongwang98@gmail.com",
|
|
14
|
-
description="A
|
|
14
|
+
description="A powerful command line testing framework in Python with setup modules, parallel execution, and file comparison capabilities.",
|
|
15
15
|
long_description=long_description,
|
|
16
16
|
long_description_content_type="text/markdown",
|
|
17
17
|
url="https://github.com/yourusername/cli-test-framework",
|
|
@@ -39,7 +39,7 @@ setup(
|
|
|
39
39
|
"Topic :: Software Development :: Testing",
|
|
40
40
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
41
41
|
],
|
|
42
|
-
python_requires='>=3.
|
|
42
|
+
python_requires='>=3.9',
|
|
43
43
|
project_urls={
|
|
44
44
|
'Documentation': 'https://github.com/yourusername/cli-test-framework/docs/user_manual.md',
|
|
45
45
|
'Source': 'https://github.com/yourusername/cli-test-framework',
|
|
@@ -5,7 +5,7 @@ This package provides tools for testing command-line applications and scripts
|
|
|
5
5
|
with support for parallel execution and advanced file comparison capabilities.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
9
|
__author__ = "Xiaotong Wang"
|
|
10
10
|
__email__ = "xiaotongwang98@gmail.com"
|
|
11
11
|
|
|
@@ -13,9 +13,17 @@ __email__ = "xiaotongwang98@gmail.com"
|
|
|
13
13
|
from .runners.json_runner import JSONRunner
|
|
14
14
|
from .runners.parallel_json_runner import ParallelJSONRunner
|
|
15
15
|
from .runners.yaml_runner import YAMLRunner
|
|
16
|
+
from .core.test_case import TestCase
|
|
17
|
+
from .core.assertions import Assertions
|
|
18
|
+
from .core.setup import BaseSetup, EnvironmentSetup, SetupManager
|
|
16
19
|
|
|
17
20
|
__all__ = [
|
|
18
21
|
'JSONRunner',
|
|
19
22
|
'ParallelJSONRunner',
|
|
20
23
|
'YAMLRunner',
|
|
24
|
+
'TestCase',
|
|
25
|
+
'Assertions',
|
|
26
|
+
'BaseSetup',
|
|
27
|
+
'EnvironmentSetup',
|
|
28
|
+
'SetupManager'
|
|
21
29
|
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Dict, Any, Optional
|
|
4
|
+
from .test_case import TestCase
|
|
5
|
+
from .assertions import Assertions
|
|
6
|
+
from .setup import SetupManager, EnvironmentSetup
|
|
7
|
+
|
|
8
|
+
class BaseRunner(ABC):
|
|
9
|
+
def __init__(self, config_file: str, workspace: Optional[str] = None):
|
|
10
|
+
if workspace:
|
|
11
|
+
self.workspace = Path(workspace)
|
|
12
|
+
else:
|
|
13
|
+
self.workspace = Path(__file__).parent.parent.parent
|
|
14
|
+
self.config_path = self.workspace / config_file
|
|
15
|
+
self.test_cases: List[TestCase] = []
|
|
16
|
+
self.results: Dict[str, Any] = {
|
|
17
|
+
"total": 0,
|
|
18
|
+
"passed": 0,
|
|
19
|
+
"failed": 0,
|
|
20
|
+
"details": []
|
|
21
|
+
}
|
|
22
|
+
self.assertions = Assertions()
|
|
23
|
+
self.setup_manager = SetupManager()
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def load_test_cases(self) -> None:
|
|
27
|
+
"""Load test cases from configuration file"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def load_setup_from_config(self, config: Dict[str, Any]) -> None:
|
|
31
|
+
"""从配置文件加载setup配置"""
|
|
32
|
+
setup_config = config.get("setup", {})
|
|
33
|
+
|
|
34
|
+
# 处理环境变量设置
|
|
35
|
+
if "environment_variables" in setup_config:
|
|
36
|
+
env_setup = EnvironmentSetup({"environment_variables": setup_config["environment_variables"]})
|
|
37
|
+
self.setup_manager.add_setup(env_setup)
|
|
38
|
+
|
|
39
|
+
# 这里可以扩展支持其他类型的setup插件
|
|
40
|
+
# 例如:
|
|
41
|
+
# if "custom_setups" in setup_config:
|
|
42
|
+
# for custom_setup_config in setup_config["custom_setups"]:
|
|
43
|
+
# # 动态加载自定义setup插件
|
|
44
|
+
# pass
|
|
45
|
+
|
|
46
|
+
def run_tests(self) -> bool:
|
|
47
|
+
"""Run all test cases and return whether all tests passed"""
|
|
48
|
+
try:
|
|
49
|
+
self.load_test_cases()
|
|
50
|
+
self.results["total"] = len(self.test_cases)
|
|
51
|
+
|
|
52
|
+
# 执行setup任务
|
|
53
|
+
self.setup_manager.setup_all()
|
|
54
|
+
|
|
55
|
+
print(f"\nStarting test execution... Total tests: {self.results['total']}")
|
|
56
|
+
print("=" * 50)
|
|
57
|
+
|
|
58
|
+
for i, case in enumerate(self.test_cases, 1):
|
|
59
|
+
print(f"\nRunning test {i}/{self.results['total']}: {case.name}")
|
|
60
|
+
result = self.run_single_test(case)
|
|
61
|
+
self.results["details"].append(result)
|
|
62
|
+
if result["status"] == "passed":
|
|
63
|
+
self.results["passed"] += 1
|
|
64
|
+
print(f"✓ Test passed: {case.name}")
|
|
65
|
+
else:
|
|
66
|
+
self.results["failed"] += 1
|
|
67
|
+
print(f"✗ Test failed: {case.name}")
|
|
68
|
+
if result["message"]:
|
|
69
|
+
print(f" Error: {result['message']}")
|
|
70
|
+
|
|
71
|
+
print("\n" + "=" * 50)
|
|
72
|
+
print(f"Test execution completed. Passed: {self.results['passed']}, Failed: {self.results['failed']}")
|
|
73
|
+
return self.results["failed"] == 0
|
|
74
|
+
finally:
|
|
75
|
+
# 确保teardown总是被执行
|
|
76
|
+
self.setup_manager.teardown_all()
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def run_single_test(self, case: TestCase) -> Dict[str, str]:
|
|
80
|
+
"""Run a single test case and return the result"""
|
|
81
|
+
pass
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
|
|
3
|
+
from typing import List, Dict, Any, Optional, Union
|
|
4
|
+
import time
|
|
5
|
+
import threading
|
|
6
|
+
from .base_runner import BaseRunner
|
|
7
|
+
from .test_case import TestCase
|
|
8
|
+
from .process_worker import run_test_in_process
|
|
9
|
+
|
|
10
|
+
class ParallelRunner(BaseRunner):
|
|
11
|
+
"""并行测试运行器基类,支持多线程和多进程执行"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, config_file: str, workspace: Optional[str] = None,
|
|
14
|
+
max_workers: Optional[int] = None,
|
|
15
|
+
execution_mode: str = "thread"):
|
|
16
|
+
"""
|
|
17
|
+
初始化并行运行器
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config_file: 配置文件路径
|
|
21
|
+
workspace: 工作目录
|
|
22
|
+
max_workers: 最大并发数,默认为CPU核心数
|
|
23
|
+
execution_mode: 执行模式,'thread'(线程) 或 'process'(进程)
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(config_file, workspace)
|
|
26
|
+
self.max_workers = max_workers
|
|
27
|
+
self.execution_mode = execution_mode
|
|
28
|
+
self.lock = threading.Lock() # 用于线程安全的结果更新
|
|
29
|
+
|
|
30
|
+
def run_tests(self) -> bool:
|
|
31
|
+
"""并行运行所有测试用例"""
|
|
32
|
+
try:
|
|
33
|
+
self.load_test_cases()
|
|
34
|
+
self.results["total"] = len(self.test_cases)
|
|
35
|
+
|
|
36
|
+
# 执行setup任务
|
|
37
|
+
self.setup_manager.setup_all()
|
|
38
|
+
|
|
39
|
+
print(f"\nStarting parallel test execution... Total tests: {self.results['total']}")
|
|
40
|
+
print(f"Execution mode: {self.execution_mode}, Max workers: {self.max_workers or 'auto'}")
|
|
41
|
+
print("=" * 50)
|
|
42
|
+
|
|
43
|
+
start_time = time.time()
|
|
44
|
+
|
|
45
|
+
if self.execution_mode == "process":
|
|
46
|
+
executor_class = ProcessPoolExecutor
|
|
47
|
+
else:
|
|
48
|
+
executor_class = ThreadPoolExecutor
|
|
49
|
+
|
|
50
|
+
with executor_class(max_workers=self.max_workers) as executor:
|
|
51
|
+
# 提交所有测试任务
|
|
52
|
+
if self.execution_mode == "process":
|
|
53
|
+
# 进程模式:使用独立的工作器函数
|
|
54
|
+
future_to_case = {
|
|
55
|
+
executor.submit(
|
|
56
|
+
run_test_in_process,
|
|
57
|
+
i,
|
|
58
|
+
{
|
|
59
|
+
"name": case.name,
|
|
60
|
+
"command": case.command,
|
|
61
|
+
"args": case.args,
|
|
62
|
+
"expected": case.expected
|
|
63
|
+
},
|
|
64
|
+
str(self.workspace) if self.workspace else None
|
|
65
|
+
): (i, case)
|
|
66
|
+
for i, case in enumerate(self.test_cases, 1)
|
|
67
|
+
}
|
|
68
|
+
else:
|
|
69
|
+
# 线程模式:使用实例方法
|
|
70
|
+
future_to_case = {
|
|
71
|
+
executor.submit(self._run_test_with_index, i, case): (i, case)
|
|
72
|
+
for i, case in enumerate(self.test_cases, 1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# 收集结果
|
|
76
|
+
for future in as_completed(future_to_case):
|
|
77
|
+
test_index, case = future_to_case[future]
|
|
78
|
+
try:
|
|
79
|
+
result = future.result()
|
|
80
|
+
self._update_results(result, test_index, case)
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
error_result = {
|
|
83
|
+
"name": case.name,
|
|
84
|
+
"status": "failed",
|
|
85
|
+
"message": f"Test execution failed: {str(exc)}",
|
|
86
|
+
"output": "",
|
|
87
|
+
"command": "",
|
|
88
|
+
"return_code": None
|
|
89
|
+
}
|
|
90
|
+
self._update_results(error_result, test_index, case)
|
|
91
|
+
|
|
92
|
+
end_time = time.time()
|
|
93
|
+
execution_time = end_time - start_time
|
|
94
|
+
|
|
95
|
+
print("\n" + "=" * 50)
|
|
96
|
+
print(f"Parallel test execution completed in {execution_time:.2f} seconds")
|
|
97
|
+
print(f"Passed: {self.results['passed']}, Failed: {self.results['failed']}")
|
|
98
|
+
return self.results["failed"] == 0
|
|
99
|
+
finally:
|
|
100
|
+
# 确保teardown总是被执行
|
|
101
|
+
self.setup_manager.teardown_all()
|
|
102
|
+
|
|
103
|
+
def _run_test_with_index(self, test_index: int, case: TestCase) -> Dict[str, Any]:
|
|
104
|
+
"""运行单个测试并返回结果(包含索引信息)"""
|
|
105
|
+
print(f"[Worker] Running test {test_index}: {case.name}")
|
|
106
|
+
result = self.run_single_test(case)
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
def _update_results(self, result: Dict[str, Any], test_index: int, case: TestCase) -> None:
|
|
110
|
+
"""线程安全地更新测试结果"""
|
|
111
|
+
with self.lock:
|
|
112
|
+
self.results["details"].append(result)
|
|
113
|
+
if result["status"] == "passed":
|
|
114
|
+
self.results["passed"] += 1
|
|
115
|
+
print(f"✓ Test {test_index} passed: {case.name}")
|
|
116
|
+
else:
|
|
117
|
+
self.results["failed"] += 1
|
|
118
|
+
print(f"✗ Test {test_index} failed: {case.name}")
|
|
119
|
+
if result["message"]:
|
|
120
|
+
print(f" Error: {result['message']}")
|
|
121
|
+
|
|
122
|
+
def run_tests_sequential(self) -> bool:
|
|
123
|
+
"""回退到顺序执行模式"""
|
|
124
|
+
print("Falling back to sequential execution...")
|
|
125
|
+
return super().run_tests()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseSetup(ABC):
|
|
7
|
+
"""测试前置任务的基类,允许用户以插件形式自定义"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
10
|
+
"""
|
|
11
|
+
初始化Setup
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
config: 配置参数字典
|
|
15
|
+
"""
|
|
16
|
+
self.config = config or {}
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def setup(self) -> None:
|
|
20
|
+
"""
|
|
21
|
+
执行前置任务设置
|
|
22
|
+
子类必须实现此方法
|
|
23
|
+
"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def teardown(self) -> None:
|
|
28
|
+
"""
|
|
29
|
+
执行后置清理任务
|
|
30
|
+
子类必须实现此方法
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def get_name(self) -> str:
|
|
35
|
+
"""返回Setup的名称"""
|
|
36
|
+
return self.__class__.__name__
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EnvironmentSetup(BaseSetup):
|
|
40
|
+
"""内置的环境变量设置插件"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
43
|
+
super().__init__(config)
|
|
44
|
+
self._original_env = {} # 存储原始环境变量值
|
|
45
|
+
self._added_env = set() # 存储新添加的环境变量
|
|
46
|
+
|
|
47
|
+
def setup(self) -> None:
|
|
48
|
+
"""设置环境变量"""
|
|
49
|
+
env_vars = self.config.get('environment_variables', {})
|
|
50
|
+
|
|
51
|
+
if not env_vars:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
print(f"Setting up environment variables...")
|
|
55
|
+
|
|
56
|
+
for key, value in env_vars.items():
|
|
57
|
+
# 保存原始值
|
|
58
|
+
if key in os.environ:
|
|
59
|
+
self._original_env[key] = os.environ[key]
|
|
60
|
+
else:
|
|
61
|
+
self._added_env.add(key)
|
|
62
|
+
|
|
63
|
+
# 设置新值
|
|
64
|
+
os.environ[key] = str(value)
|
|
65
|
+
print(f" {key} = {value}")
|
|
66
|
+
|
|
67
|
+
def teardown(self) -> None:
|
|
68
|
+
"""恢复环境变量"""
|
|
69
|
+
if not self._original_env and not self._added_env:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
print(f"Restoring environment variables...")
|
|
73
|
+
|
|
74
|
+
# 恢复原始值
|
|
75
|
+
for key, value in self._original_env.items():
|
|
76
|
+
os.environ[key] = value
|
|
77
|
+
print(f" Restored {key}")
|
|
78
|
+
|
|
79
|
+
# 删除新添加的环境变量
|
|
80
|
+
for key in self._added_env:
|
|
81
|
+
if key in os.environ:
|
|
82
|
+
del os.environ[key]
|
|
83
|
+
print(f" Removed {key}")
|
|
84
|
+
|
|
85
|
+
# 清空记录
|
|
86
|
+
self._original_env.clear()
|
|
87
|
+
self._added_env.clear()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SetupManager:
|
|
91
|
+
"""Setup管理器,负责管理多个Setup插件"""
|
|
92
|
+
|
|
93
|
+
def __init__(self):
|
|
94
|
+
self.setups = []
|
|
95
|
+
|
|
96
|
+
def add_setup(self, setup: BaseSetup) -> None:
|
|
97
|
+
"""添加Setup插件"""
|
|
98
|
+
self.setups.append(setup)
|
|
99
|
+
|
|
100
|
+
def setup_all(self) -> None:
|
|
101
|
+
"""执行所有Setup的前置任务"""
|
|
102
|
+
if not self.setups:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
print("\n" + "=" * 50)
|
|
106
|
+
print("Executing setup tasks...")
|
|
107
|
+
print("=" * 50)
|
|
108
|
+
|
|
109
|
+
for setup in self.setups:
|
|
110
|
+
try:
|
|
111
|
+
print(f"\nRunning setup: {setup.get_name()}")
|
|
112
|
+
setup.setup()
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"Error in setup {setup.get_name()}: {str(e)}")
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
def teardown_all(self) -> None:
|
|
118
|
+
"""执行所有Setup的后置清理任务(逆序执行)"""
|
|
119
|
+
if not self.setups:
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
print("\n" + "=" * 50)
|
|
123
|
+
print("Executing teardown tasks...")
|
|
124
|
+
print("=" * 50)
|
|
125
|
+
|
|
126
|
+
# 逆序执行teardown
|
|
127
|
+
for setup in reversed(self.setups):
|
|
128
|
+
try:
|
|
129
|
+
print(f"\nRunning teardown: {setup.get_name()}")
|
|
130
|
+
setup.teardown()
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"Error in teardown {setup.get_name()}: {str(e)}")
|
|
133
|
+
# teardown错误不应该阻止其他teardown的执行
|
|
134
|
+
continue
|
{cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/runners/json_runner.py
RENAMED
|
@@ -18,6 +18,9 @@ class JSONRunner(BaseRunner):
|
|
|
18
18
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
19
19
|
config = json.load(f)
|
|
20
20
|
|
|
21
|
+
# 加载setup配置
|
|
22
|
+
self.load_setup_from_config(config)
|
|
23
|
+
|
|
21
24
|
required_fields = ["name", "command", "args", "expected"]
|
|
22
25
|
for case in config["test_cases"]:
|
|
23
26
|
if not all(field in case for field in required_fields):
|
|
@@ -31,6 +31,9 @@ class ParallelJSONRunner(ParallelRunner):
|
|
|
31
31
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
32
32
|
config = json.load(f)
|
|
33
33
|
|
|
34
|
+
# 加载setup配置
|
|
35
|
+
self.load_setup_from_config(config)
|
|
36
|
+
|
|
34
37
|
required_fields = ["name", "command", "args", "expected"]
|
|
35
38
|
for case in config["test_cases"]:
|
|
36
39
|
if not all(field in case for field in required_fields):
|
{cli_test_framework-0.2.3 → cli_test_framework-0.3.0}/src/cli_test_framework/runners/yaml_runner.py
RENAMED
|
@@ -17,6 +17,9 @@ class YAMLRunner(BaseRunner):
|
|
|
17
17
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
18
18
|
config = yaml.safe_load(f)
|
|
19
19
|
|
|
20
|
+
# 加载setup配置
|
|
21
|
+
self.load_setup_from_config(config)
|
|
22
|
+
|
|
20
23
|
required_fields = ["name", "command", "args", "expected"]
|
|
21
24
|
for case in config["test_cases"]:
|
|
22
25
|
if not all(field in case for field in required_fields):
|