cli-test-framework 0.1.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.1.0/PKG-INFO +15 -0
- cli_test_framework-0.1.0/README.md +418 -0
- cli_test_framework-0.1.0/pyproject.toml +3 -0
- cli_test_framework-0.1.0/setup.cfg +4 -0
- cli_test_framework-0.1.0/setup.py +20 -0
- cli_test_framework-0.1.0/src/cli_test_framework.egg-info/PKG-INFO +15 -0
- cli_test_framework-0.1.0/src/cli_test_framework.egg-info/SOURCES.txt +25 -0
- cli_test_framework-0.1.0/src/cli_test_framework.egg-info/dependency_links.txt +1 -0
- cli_test_framework-0.1.0/src/cli_test_framework.egg-info/top_level.txt +3 -0
- cli_test_framework-0.1.0/src/core/__init__.py +4 -0
- cli_test_framework-0.1.0/src/core/assertions.py +32 -0
- cli_test_framework-0.1.0/src/core/base_runner.py +56 -0
- cli_test_framework-0.1.0/src/core/parallel_runner.py +118 -0
- cli_test_framework-0.1.0/src/core/process_worker.py +93 -0
- cli_test_framework-0.1.0/src/core/test_case.py +21 -0
- cli_test_framework-0.1.0/src/runners/__init__.py +3 -0
- cli_test_framework-0.1.0/src/runners/json_runner.py +96 -0
- cli_test_framework-0.1.0/src/runners/parallel_json_runner.py +115 -0
- cli_test_framework-0.1.0/src/runners/yaml_runner.py +93 -0
- cli_test_framework-0.1.0/src/utils/__init__.py +3 -0
- cli_test_framework-0.1.0/src/utils/path_resolver.py +195 -0
- cli_test_framework-0.1.0/src/utils/report_generator.py +68 -0
- cli_test_framework-0.1.0/tests/test1.py +30 -0
- cli_test_framework-0.1.0/tests/test_comprehensive_space.py +118 -0
- cli_test_framework-0.1.0/tests/test_parallel_runner.py +170 -0
- cli_test_framework-0.1.0/tests/test_parallel_space.py +93 -0
- cli_test_framework-0.1.0/tests/test_runners.py +32 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli-test-framework
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A small command line testing framework in Python.
|
|
5
|
+
Author: Xiaotong Wang
|
|
6
|
+
Author-email: xiaotongwang98@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: author-email
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
Dynamic: summary
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# CLI Testing Framework
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
This is a lightweight and extensible automated testing framework that supports defining test cases via JSON/YAML formats, providing complete test execution, result verification, and report generation capabilities. The framework is designed to provide standardized test management for command-line tools and scripts, with enterprise-grade parallel execution support.
|
|
6
|
+
|
|
7
|
+
## 2. Features
|
|
8
|
+
|
|
9
|
+
- **🚀 Parallel Test Execution**: Support for multi-threading and multi-processing parallel testing with significant performance improvements
|
|
10
|
+
- **🏗️ Modular Architecture**: Decoupled design of core components (runner/assertion/report)
|
|
11
|
+
- **📄 Multi-Format Support**: Native support for JSON/YAML test case formats
|
|
12
|
+
- **🧠 Intelligent Command Parsing**: Smart handling of complex commands like `"python ./script.py"`
|
|
13
|
+
- **📁 Smart Path Resolution**: Automatic handling of relative and absolute path conversions
|
|
14
|
+
- **✅ Rich Assertion Mechanism**: Return code validation, output content matching, regex verification
|
|
15
|
+
- **🔌 Extensible Interfaces**: Quickly implement new test format support by inheriting BaseRunner
|
|
16
|
+
- **🔒 Isolated Execution Environment**: Independent sub-process execution ensures test isolation
|
|
17
|
+
- **📊 Comprehensive Reports**: Detailed pass rate statistics and failure diagnostics
|
|
18
|
+
- **🔧 Thread-Safe Design**: Robust concurrent execution with proper synchronization
|
|
19
|
+
|
|
20
|
+
## 3. Quick Start
|
|
21
|
+
|
|
22
|
+
### Environment Requirements
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install -r requirements.txt
|
|
26
|
+
Python >= 3.6
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Sequential Execution
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from src.runners.json_runner import JSONRunner
|
|
33
|
+
|
|
34
|
+
runner = JSONRunner(
|
|
35
|
+
config_file="path/to/test_cases.json",
|
|
36
|
+
workspace="/project/root"
|
|
37
|
+
)
|
|
38
|
+
success = runner.run_tests()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Parallel Execution (NEW!)
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from src.runners.parallel_json_runner import ParallelJSONRunner
|
|
45
|
+
|
|
46
|
+
# Multi-threaded execution (recommended for I/O-intensive tests)
|
|
47
|
+
runner = ParallelJSONRunner(
|
|
48
|
+
config_file="path/to/test_cases.json",
|
|
49
|
+
workspace="/project/root",
|
|
50
|
+
max_workers=4, # Maximum concurrent workers
|
|
51
|
+
execution_mode="thread" # "thread" or "process"
|
|
52
|
+
)
|
|
53
|
+
success = runner.run_tests()
|
|
54
|
+
|
|
55
|
+
# Performance comparison example
|
|
56
|
+
python parallel_example.py
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### YAML Support
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from src.runners.yaml_runner import YAMLRunner
|
|
63
|
+
|
|
64
|
+
runner = YAMLRunner(
|
|
65
|
+
config_file="path/to/test_cases.yaml",
|
|
66
|
+
workspace="/project/root"
|
|
67
|
+
)
|
|
68
|
+
success = runner.run_tests()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 4. Test Case Format
|
|
72
|
+
|
|
73
|
+
### JSON Format
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"test_cases": [
|
|
78
|
+
{
|
|
79
|
+
"name": "File Comparison Test",
|
|
80
|
+
"command": "python ./compare_files.py",
|
|
81
|
+
"args": ["file1.txt", "file2.txt", "--verbose"],
|
|
82
|
+
"expected": {
|
|
83
|
+
"return_code": 0,
|
|
84
|
+
"output_contains": ["Files are identical"],
|
|
85
|
+
"output_matches": [".*comparison completed.*"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### YAML Format
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
test_cases:
|
|
96
|
+
- name: Directory Scan Test
|
|
97
|
+
command: ls
|
|
98
|
+
args:
|
|
99
|
+
- -l
|
|
100
|
+
- docs/
|
|
101
|
+
expected:
|
|
102
|
+
return_code: 0
|
|
103
|
+
output_matches: ".*\\.md$"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Supported Command Formats
|
|
107
|
+
|
|
108
|
+
The framework intelligently handles various command formats:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"command": "echo", // Simple command
|
|
113
|
+
"command": "python script.py", // Command with script
|
|
114
|
+
"command": "node ./app.js --port", // Complex command with flags
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 5. Parallel Testing
|
|
119
|
+
|
|
120
|
+
### Performance Benefits
|
|
121
|
+
|
|
122
|
+
Parallel execution provides significant performance improvements:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Run performance comparison
|
|
126
|
+
python parallel_example.py
|
|
127
|
+
|
|
128
|
+
# Typical output:
|
|
129
|
+
# Sequential execution: 12.45 seconds
|
|
130
|
+
# Parallel execution (thread): 3.21 seconds (3.88x speedup)
|
|
131
|
+
# Parallel execution (process): 4.12 seconds (3.02x speedup)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Execution Modes
|
|
135
|
+
|
|
136
|
+
#### Thread Mode (Recommended)
|
|
137
|
+
- **Best for**: I/O-intensive tests (network requests, file operations)
|
|
138
|
+
- **Advantages**: Fast startup, shared memory, suitable for most test scenarios
|
|
139
|
+
- **Recommended workers**: CPU cores × 2-4
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
runner = ParallelJSONRunner(
|
|
143
|
+
config_file="test_cases.json",
|
|
144
|
+
max_workers=4,
|
|
145
|
+
execution_mode="thread"
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Process Mode
|
|
150
|
+
- **Best for**: CPU-intensive tests, complete isolation requirements
|
|
151
|
+
- **Advantages**: Complete isolation, bypasses GIL limitations
|
|
152
|
+
- **Recommended workers**: CPU cores
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
runner = ParallelJSONRunner(
|
|
156
|
+
config_file="test_cases.json",
|
|
157
|
+
max_workers=2,
|
|
158
|
+
execution_mode="process"
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Thread Safety Features
|
|
163
|
+
|
|
164
|
+
- **Result Collection Lock**: `threading.Lock()` protects shared result data
|
|
165
|
+
- **Output Control Lock**: Prevents concurrent output confusion
|
|
166
|
+
- **Exception Isolation**: Individual test failures don't affect others
|
|
167
|
+
|
|
168
|
+
## 6. System Architecture
|
|
169
|
+
|
|
170
|
+
### Enhanced Architecture Flow
|
|
171
|
+
|
|
172
|
+
```mermaid
|
|
173
|
+
graph TD
|
|
174
|
+
A[Test Cases] --> B{Execution Mode}
|
|
175
|
+
B -->|Sequential| C[JSONRunner/YAMLRunner]
|
|
176
|
+
B -->|Parallel| D[ParallelRunner]
|
|
177
|
+
D --> E[ThreadPoolExecutor/ProcessPoolExecutor]
|
|
178
|
+
C --> F[Command Parser]
|
|
179
|
+
E --> F
|
|
180
|
+
F --> G[Path Resolver]
|
|
181
|
+
G --> H[Sub-process Execution]
|
|
182
|
+
H --> I[Assertion Engine]
|
|
183
|
+
I --> J[Thread-Safe Result Collection]
|
|
184
|
+
J --> K[Report Generator]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Core Components
|
|
188
|
+
|
|
189
|
+
#### 1. Intelligent Command Parser
|
|
190
|
+
```python
|
|
191
|
+
# Handles complex commands like "python ./script.py"
|
|
192
|
+
command_parts = case["command"].split()
|
|
193
|
+
if len(command_parts) > 1:
|
|
194
|
+
actual_command = resolve_command(command_parts[0]) # "python"
|
|
195
|
+
script_parts = resolve_paths(command_parts[1:]) # "./script.py" -> full path
|
|
196
|
+
final_command = f"{actual_command} {' '.join(script_parts)}"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### 2. Enhanced Path Resolver
|
|
200
|
+
```python
|
|
201
|
+
def resolve_command(self, command: str) -> str:
|
|
202
|
+
system_commands = {
|
|
203
|
+
'echo', 'ping', 'python', 'node', 'java', 'docker', ...
|
|
204
|
+
}
|
|
205
|
+
if command in system_commands or Path(command).is_absolute():
|
|
206
|
+
return command
|
|
207
|
+
return str(self.workspace / command)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### 3. Parallel Runner Base Class
|
|
211
|
+
```python
|
|
212
|
+
class ParallelRunner(BaseRunner):
|
|
213
|
+
def __init__(self, max_workers=None, execution_mode="thread"):
|
|
214
|
+
self.max_workers = max_workers or os.cpu_count()
|
|
215
|
+
self.execution_mode = execution_mode
|
|
216
|
+
self._results_lock = threading.Lock()
|
|
217
|
+
self._print_lock = threading.Lock()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## 7. Advanced Usage
|
|
221
|
+
|
|
222
|
+
### Performance Testing
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
# Quick performance test
|
|
226
|
+
python performance_test.py
|
|
227
|
+
|
|
228
|
+
# Unit tests for parallel functionality
|
|
229
|
+
python -m pytest tests/test_parallel_runner.py -v
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Error Handling and Fallback
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
try:
|
|
236
|
+
runner = ParallelJSONRunner(config_file="test_cases.json")
|
|
237
|
+
success = runner.run_tests()
|
|
238
|
+
|
|
239
|
+
if not success:
|
|
240
|
+
# Check failed tests
|
|
241
|
+
for detail in runner.results["details"]:
|
|
242
|
+
if detail["status"] == "failed":
|
|
243
|
+
print(f"Failed test: {detail['name']}")
|
|
244
|
+
print(f"Error: {detail['message']}")
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
print(f"Execution error: {e}")
|
|
248
|
+
# Fallback to sequential execution
|
|
249
|
+
runner.run_tests_sequential()
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Best Practices
|
|
253
|
+
|
|
254
|
+
1. **Choose Appropriate Concurrency**:
|
|
255
|
+
```python
|
|
256
|
+
import os
|
|
257
|
+
|
|
258
|
+
# For CPU-intensive tasks
|
|
259
|
+
max_workers = os.cpu_count()
|
|
260
|
+
|
|
261
|
+
# For I/O-intensive tasks
|
|
262
|
+
max_workers = os.cpu_count() * 2
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
2. **Test Case Design**:
|
|
266
|
+
- ✅ Ensure test independence (no dependencies between tests)
|
|
267
|
+
- ✅ Avoid shared resource conflicts (different files/ports)
|
|
268
|
+
- ✅ Use relative paths (framework handles resolution automatically)
|
|
269
|
+
|
|
270
|
+
3. **Debugging**:
|
|
271
|
+
```python
|
|
272
|
+
# Enable verbose output for debugging
|
|
273
|
+
runner = ParallelJSONRunner(
|
|
274
|
+
config_file="test_cases.json",
|
|
275
|
+
max_workers=1, # Set to 1 for easier debugging
|
|
276
|
+
execution_mode="thread"
|
|
277
|
+
)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 8. Example Demonstrations
|
|
281
|
+
|
|
282
|
+
### Input Example
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"test_cases": [
|
|
287
|
+
{
|
|
288
|
+
"name": "Python Version Check",
|
|
289
|
+
"command": "python --version",
|
|
290
|
+
"args": [],
|
|
291
|
+
"expected": {
|
|
292
|
+
"output_matches": "Python 3\\.[89]\\.",
|
|
293
|
+
"return_code": 0
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"name": "File Processing Test",
|
|
298
|
+
"command": "python ./process_file.py",
|
|
299
|
+
"args": ["input.txt", "--output", "result.txt"],
|
|
300
|
+
"expected": {
|
|
301
|
+
"return_code": 0,
|
|
302
|
+
"output_contains": ["Processing completed"]
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Output Report
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
Test Results Summary:
|
|
313
|
+
Total Tests: 15
|
|
314
|
+
Passed: 15
|
|
315
|
+
Failed: 0
|
|
316
|
+
|
|
317
|
+
Performance Statistics:
|
|
318
|
+
Sequential execution time: 12.45 seconds
|
|
319
|
+
Parallel execution time: 3.21 seconds
|
|
320
|
+
Speedup ratio: 3.88x
|
|
321
|
+
|
|
322
|
+
Detailed Results:
|
|
323
|
+
✓ Python Version Check
|
|
324
|
+
✓ File Processing Test
|
|
325
|
+
✓ JSON Comparison Test
|
|
326
|
+
...
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## 9. Troubleshooting
|
|
330
|
+
|
|
331
|
+
### Common Issues
|
|
332
|
+
|
|
333
|
+
1. **Process Mode Serialization Error**
|
|
334
|
+
- **Cause**: Objects contain non-serializable attributes (like locks)
|
|
335
|
+
- **Solution**: Use independent process worker functions
|
|
336
|
+
|
|
337
|
+
2. **Path Resolution Error**
|
|
338
|
+
- **Cause**: System commands treated as relative paths
|
|
339
|
+
- **Solution**: Update `PathResolver` system command list
|
|
340
|
+
|
|
341
|
+
3. **Performance Not Improved**
|
|
342
|
+
- **Cause**: Test cases too short, parallel overhead exceeds benefits
|
|
343
|
+
- **Solution**: Increase test case count or use more complex tests
|
|
344
|
+
|
|
345
|
+
4. **Command Not Found Error**
|
|
346
|
+
- **Cause**: Complex commands like `"python ./script.py"` not parsed correctly
|
|
347
|
+
- **Solution**: Framework now automatically handles this (fixed in latest version)
|
|
348
|
+
|
|
349
|
+
### Debug Tips
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
# Enable detailed logging
|
|
353
|
+
import logging
|
|
354
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
355
|
+
|
|
356
|
+
# Check detailed results
|
|
357
|
+
import json
|
|
358
|
+
print(json.dumps(runner.results, indent=2, ensure_ascii=False))
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## 10. Extension and Customization
|
|
362
|
+
|
|
363
|
+
### Adding New Runners
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
class XMLRunner(BaseRunner):
|
|
367
|
+
def load_test_cases(self):
|
|
368
|
+
import xml.etree.ElementTree as ET
|
|
369
|
+
# Parse XML structure and convert to TestCase objects
|
|
370
|
+
|
|
371
|
+
class CustomParallelRunner(ParallelRunner):
|
|
372
|
+
def custom_preprocessing(self):
|
|
373
|
+
# Add custom logic before test execution
|
|
374
|
+
pass
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Custom Assertions
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
class CustomAssertions(Assertions):
|
|
381
|
+
@staticmethod
|
|
382
|
+
def performance_threshold(execution_time, max_time):
|
|
383
|
+
if execution_time > max_time:
|
|
384
|
+
raise AssertionError(f"Execution too slow: {execution_time}s > {max_time}s")
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## 11. Version Compatibility
|
|
388
|
+
|
|
389
|
+
- **Python Version**: 3.6+
|
|
390
|
+
- **Dependencies**: Standard library only (no external dependencies for core functionality)
|
|
391
|
+
- **Backward Compatibility**: Fully compatible with existing `JSONRunner` code
|
|
392
|
+
- **Platform Support**: Windows, macOS, Linux
|
|
393
|
+
|
|
394
|
+
## 12. Performance Benchmarks
|
|
395
|
+
|
|
396
|
+
| Test Scenario | Sequential | Parallel (Thread) | Parallel (Process) | Speedup |
|
|
397
|
+
|---------------|------------|-------------------|-------------------|---------|
|
|
398
|
+
| 10 I/O tests | 5.2s | 1.4s | 2.1s | 3.7x |
|
|
399
|
+
| 20 CPU tests | 12.8s | 8.9s | 6.2s | 2.1x |
|
|
400
|
+
| Mixed tests | 8.5s | 2.3s | 3.1s | 3.7x |
|
|
401
|
+
|
|
402
|
+
## 13. Contributing
|
|
403
|
+
|
|
404
|
+
1. Fork the repository
|
|
405
|
+
2. Create a feature branch
|
|
406
|
+
3. Add tests for new functionality
|
|
407
|
+
4. Ensure all tests pass: `python -m pytest tests/ -v`
|
|
408
|
+
5. Submit a pull request
|
|
409
|
+
|
|
410
|
+
## 14. License
|
|
411
|
+
|
|
412
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
**🚀 Ready to supercharge your testing workflow with parallel execution!**
|
|
417
|
+
|
|
418
|
+
For detailed parallel testing guide, see: [PARALLEL_TESTING_GUIDE.md](PARALLEL_TESTING_GUIDE.md)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="cli-test-framework",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
author="Xiaotong Wang",
|
|
7
|
+
author_email="xiaotongwang98@gmail.com",
|
|
8
|
+
description="A small command line testing framework in Python.",
|
|
9
|
+
packages=find_packages(where="src"),
|
|
10
|
+
package_dir={"": "src"},
|
|
11
|
+
install_requires=[
|
|
12
|
+
# List your project dependencies here
|
|
13
|
+
],
|
|
14
|
+
classifiers=[
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
],
|
|
19
|
+
python_requires='>=3.6',
|
|
20
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli-test-framework
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A small command line testing framework in Python.
|
|
5
|
+
Author: Xiaotong Wang
|
|
6
|
+
Author-email: xiaotongwang98@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: author-email
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
Dynamic: summary
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
src/cli_test_framework.egg-info/PKG-INFO
|
|
5
|
+
src/cli_test_framework.egg-info/SOURCES.txt
|
|
6
|
+
src/cli_test_framework.egg-info/dependency_links.txt
|
|
7
|
+
src/cli_test_framework.egg-info/top_level.txt
|
|
8
|
+
src/core/__init__.py
|
|
9
|
+
src/core/assertions.py
|
|
10
|
+
src/core/base_runner.py
|
|
11
|
+
src/core/parallel_runner.py
|
|
12
|
+
src/core/process_worker.py
|
|
13
|
+
src/core/test_case.py
|
|
14
|
+
src/runners/__init__.py
|
|
15
|
+
src/runners/json_runner.py
|
|
16
|
+
src/runners/parallel_json_runner.py
|
|
17
|
+
src/runners/yaml_runner.py
|
|
18
|
+
src/utils/__init__.py
|
|
19
|
+
src/utils/path_resolver.py
|
|
20
|
+
src/utils/report_generator.py
|
|
21
|
+
tests/test1.py
|
|
22
|
+
tests/test_comprehensive_space.py
|
|
23
|
+
tests/test_parallel_runner.py
|
|
24
|
+
tests/test_parallel_space.py
|
|
25
|
+
tests/test_runners.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any, Pattern
|
|
3
|
+
|
|
4
|
+
class Assertions:
|
|
5
|
+
@staticmethod
|
|
6
|
+
def equals(actual: Any, expected: Any, message: str = "") -> bool:
|
|
7
|
+
if actual != expected:
|
|
8
|
+
raise AssertionError(f"{message} Expected: {expected}, but got: {actual}")
|
|
9
|
+
return True
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def contains(container: str, item: str, message: str = "") -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Check if the item is contained within the container string.
|
|
15
|
+
This method returns True if the item is found anywhere within the container,
|
|
16
|
+
even if the container contains other information.
|
|
17
|
+
"""
|
|
18
|
+
if item not in container:
|
|
19
|
+
raise AssertionError(f"{message} Expected to contain: {item}")
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def matches(text: str, pattern: str, message: str = "") -> bool:
|
|
24
|
+
if not re.search(pattern, text):
|
|
25
|
+
raise AssertionError(f"{message} Text does not match pattern: {pattern}")
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def return_code_equals(actual: int, expected: int, message: str = "") -> bool:
|
|
30
|
+
if actual != expected:
|
|
31
|
+
raise AssertionError(f"{message} Expected return code: {expected}, got: {actual}")
|
|
32
|
+
return True
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
|
|
7
|
+
class BaseRunner(ABC):
|
|
8
|
+
def __init__(self, config_file: str, workspace: Optional[str] = None):
|
|
9
|
+
if workspace:
|
|
10
|
+
self.workspace = Path(workspace)
|
|
11
|
+
else:
|
|
12
|
+
self.workspace = Path(__file__).parent.parent.parent
|
|
13
|
+
self.config_path = self.workspace / config_file
|
|
14
|
+
self.test_cases: List[TestCase] = []
|
|
15
|
+
self.results: Dict[str, Any] = {
|
|
16
|
+
"total": 0,
|
|
17
|
+
"passed": 0,
|
|
18
|
+
"failed": 0,
|
|
19
|
+
"details": []
|
|
20
|
+
}
|
|
21
|
+
self.assertions = Assertions()
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def load_test_cases(self) -> None:
|
|
25
|
+
"""Load test cases from configuration file"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def run_tests(self) -> bool:
|
|
29
|
+
"""Run all test cases and return whether all tests passed"""
|
|
30
|
+
self.load_test_cases()
|
|
31
|
+
self.results["total"] = len(self.test_cases)
|
|
32
|
+
|
|
33
|
+
print(f"\nStarting test execution... Total tests: {self.results['total']}")
|
|
34
|
+
print("=" * 50)
|
|
35
|
+
|
|
36
|
+
for i, case in enumerate(self.test_cases, 1):
|
|
37
|
+
print(f"\nRunning test {i}/{self.results['total']}: {case.name}")
|
|
38
|
+
result = self.run_single_test(case)
|
|
39
|
+
self.results["details"].append(result)
|
|
40
|
+
if result["status"] == "passed":
|
|
41
|
+
self.results["passed"] += 1
|
|
42
|
+
print(f"✓ Test passed: {case.name}")
|
|
43
|
+
else:
|
|
44
|
+
self.results["failed"] += 1
|
|
45
|
+
print(f"✗ Test failed: {case.name}")
|
|
46
|
+
if result["message"]:
|
|
47
|
+
print(f" Error: {result['message']}")
|
|
48
|
+
|
|
49
|
+
print("\n" + "=" * 50)
|
|
50
|
+
print(f"Test execution completed. Passed: {self.results['passed']}, Failed: {self.results['failed']}")
|
|
51
|
+
return self.results["failed"] == 0
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def run_single_test(self, case: TestCase) -> Dict[str, str]:
|
|
55
|
+
"""Run a single test case and return the result"""
|
|
56
|
+
pass
|