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.
Files changed (27) hide show
  1. cli_test_framework-0.1.0/PKG-INFO +15 -0
  2. cli_test_framework-0.1.0/README.md +418 -0
  3. cli_test_framework-0.1.0/pyproject.toml +3 -0
  4. cli_test_framework-0.1.0/setup.cfg +4 -0
  5. cli_test_framework-0.1.0/setup.py +20 -0
  6. cli_test_framework-0.1.0/src/cli_test_framework.egg-info/PKG-INFO +15 -0
  7. cli_test_framework-0.1.0/src/cli_test_framework.egg-info/SOURCES.txt +25 -0
  8. cli_test_framework-0.1.0/src/cli_test_framework.egg-info/dependency_links.txt +1 -0
  9. cli_test_framework-0.1.0/src/cli_test_framework.egg-info/top_level.txt +3 -0
  10. cli_test_framework-0.1.0/src/core/__init__.py +4 -0
  11. cli_test_framework-0.1.0/src/core/assertions.py +32 -0
  12. cli_test_framework-0.1.0/src/core/base_runner.py +56 -0
  13. cli_test_framework-0.1.0/src/core/parallel_runner.py +118 -0
  14. cli_test_framework-0.1.0/src/core/process_worker.py +93 -0
  15. cli_test_framework-0.1.0/src/core/test_case.py +21 -0
  16. cli_test_framework-0.1.0/src/runners/__init__.py +3 -0
  17. cli_test_framework-0.1.0/src/runners/json_runner.py +96 -0
  18. cli_test_framework-0.1.0/src/runners/parallel_json_runner.py +115 -0
  19. cli_test_framework-0.1.0/src/runners/yaml_runner.py +93 -0
  20. cli_test_framework-0.1.0/src/utils/__init__.py +3 -0
  21. cli_test_framework-0.1.0/src/utils/path_resolver.py +195 -0
  22. cli_test_framework-0.1.0/src/utils/report_generator.py +68 -0
  23. cli_test_framework-0.1.0/tests/test1.py +30 -0
  24. cli_test_framework-0.1.0/tests/test_comprehensive_space.py +118 -0
  25. cli_test_framework-0.1.0/tests/test_parallel_runner.py +170 -0
  26. cli_test_framework-0.1.0/tests/test_parallel_space.py +93 -0
  27. 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,3 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,3 @@
1
+ core
2
+ runners
3
+ utils
@@ -0,0 +1,4 @@
1
+ from .base_runner import BaseRunner
2
+ from .parallel_runner import ParallelRunner
3
+ from .test_case import TestCase
4
+ from .assertions import Assertions
@@ -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