cli-test-framework 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl
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/__init__.py +1 -1
- cli_test_framework/commands/__init__.py +9 -0
- cli_test_framework/commands/compare.py +180 -0
- cli_test_framework/utils/path_resolver.py +13 -3
- {cli_test_framework-0.2.2.dist-info → cli_test_framework-0.2.4.dist-info}/METADATA +1 -1
- {cli_test_framework-0.2.2.dist-info → cli_test_framework-0.2.4.dist-info}/RECORD +9 -7
- {cli_test_framework-0.2.2.dist-info → cli_test_framework-0.2.4.dist-info}/WHEEL +0 -0
- {cli_test_framework-0.2.2.dist-info → cli_test_framework-0.2.4.dist-info}/entry_points.txt +0 -0
- {cli_test_framework-0.2.2.dist-info → cli_test_framework-0.2.4.dist-info}/top_level.txt +0 -0
cli_test_framework/__init__.py
CHANGED
|
@@ -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.2.
|
|
8
|
+
__version__ = "0.2.4"
|
|
9
9
|
__author__ = "Xiaotong Wang"
|
|
10
10
|
__email__ = "xiaotongwang98@gmail.com"
|
|
11
11
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@file compare.py
|
|
6
|
+
@brief Command for comparing files in cli-test-framework
|
|
7
|
+
@author Xiaotong Wang
|
|
8
|
+
@date 2024
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
import argparse
|
|
14
|
+
import logging
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from ..file_comparator.factory import ComparatorFactory
|
|
17
|
+
from ..file_comparator.result import ComparisonResult
|
|
18
|
+
|
|
19
|
+
def configure_logging():
|
|
20
|
+
"""Configure logging settings for the application"""
|
|
21
|
+
logger = logging.getLogger("cli_test_framework.file_comparator")
|
|
22
|
+
logger.setLevel(logging.INFO)
|
|
23
|
+
|
|
24
|
+
ch = logging.StreamHandler()
|
|
25
|
+
ch.setLevel(logging.INFO)
|
|
26
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
27
|
+
ch.setFormatter(formatter)
|
|
28
|
+
logger.addHandler(ch)
|
|
29
|
+
|
|
30
|
+
return logger
|
|
31
|
+
|
|
32
|
+
def parse_arguments():
|
|
33
|
+
"""Parse command line arguments"""
|
|
34
|
+
parser = argparse.ArgumentParser(description="Compare two files.")
|
|
35
|
+
parser.add_argument("file1", help="Path to the first file")
|
|
36
|
+
parser.add_argument("file2", help="Path to the second file")
|
|
37
|
+
parser.add_argument("--start-line", type=int, default=1, help="Starting line number (1-based)")
|
|
38
|
+
parser.add_argument("--end-line", type=int, help="Ending line number (1-based)")
|
|
39
|
+
parser.add_argument("--start-column", type=int, default=1, help="Starting column number (1-based)")
|
|
40
|
+
parser.add_argument("--end-column", type=int, help="Ending column number (1-based)")
|
|
41
|
+
parser.add_argument("--file-type", help="Type of the files to compare", default="auto")
|
|
42
|
+
parser.add_argument("--encoding", default="utf-8", help="File encoding for text files")
|
|
43
|
+
parser.add_argument("--chunk-size", type=int, default=8192, help="Chunk size for binary comparison")
|
|
44
|
+
parser.add_argument("--output-format", choices=["text", "json", "html"], default="text",
|
|
45
|
+
help="Output format for the comparison result")
|
|
46
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
|
|
47
|
+
parser.add_argument("--debug", action="store_true", help="Enable debug mode with detailed logging")
|
|
48
|
+
parser.add_argument("--similarity", action="store_true",
|
|
49
|
+
help="When comparing binary files, compute and show similarity index")
|
|
50
|
+
parser.add_argument("--num-threads", type=int, default=4, help="Number of threads for parallel processing")
|
|
51
|
+
|
|
52
|
+
# JSON comparison options
|
|
53
|
+
json_group = parser.add_argument_group('JSON comparison options')
|
|
54
|
+
json_group.add_argument("--json-compare-mode", choices=["exact", "key-based"], default="exact",
|
|
55
|
+
help="JSON comparison mode: exact (default) or key-based")
|
|
56
|
+
json_group.add_argument("--json-key-field", help="Key field(s) to use for key-based JSON comparison")
|
|
57
|
+
|
|
58
|
+
# H5 comparison options
|
|
59
|
+
h5_group = parser.add_argument_group('HDF5 comparison options')
|
|
60
|
+
h5_group.add_argument("--h5-table", help="Comma-separated list of table names to compare in HDF5 files")
|
|
61
|
+
h5_group.add_argument("--h5-table-regex", help="Regular expression pattern to match table names in HDF5 files")
|
|
62
|
+
h5_group.add_argument("--h5-structure-only", action="store_true",
|
|
63
|
+
help="Only compare HDF5 file structure without comparing content")
|
|
64
|
+
h5_group.add_argument("--h5-show-content-diff", action="store_true",
|
|
65
|
+
help="Show detailed content differences when content differs")
|
|
66
|
+
h5_group.add_argument("--h5-rtol", type=float, default=1e-5,
|
|
67
|
+
help="Relative tolerance for numerical comparison in HDF5 files")
|
|
68
|
+
h5_group.add_argument("--h5-atol", type=float, default=1e-8,
|
|
69
|
+
help="Absolute tolerance for numerical comparison in HDF5 files")
|
|
70
|
+
|
|
71
|
+
return parser.parse_args()
|
|
72
|
+
|
|
73
|
+
def detect_file_type(file_path):
|
|
74
|
+
"""Detect the type of file based on its extension"""
|
|
75
|
+
ext = file_path.suffix.lower()
|
|
76
|
+
if ext in ['.txt', '.py', '.md', '.json', '.xml', '.html', '.css', '.js']:
|
|
77
|
+
return 'text'
|
|
78
|
+
elif ext == '.json':
|
|
79
|
+
return 'json'
|
|
80
|
+
elif ext in ['.h5', '.hdf5']:
|
|
81
|
+
return 'h5'
|
|
82
|
+
else:
|
|
83
|
+
return 'binary'
|
|
84
|
+
|
|
85
|
+
def format_result(result, output_format):
|
|
86
|
+
"""Format the comparison result according to the specified output format"""
|
|
87
|
+
if output_format == "json":
|
|
88
|
+
return result.to_json()
|
|
89
|
+
elif output_format == "html":
|
|
90
|
+
return result.to_html()
|
|
91
|
+
else:
|
|
92
|
+
return str(result)
|
|
93
|
+
|
|
94
|
+
def main():
|
|
95
|
+
"""Main entry point for the compare-files command"""
|
|
96
|
+
logger = configure_logging()
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
args = parse_arguments()
|
|
100
|
+
|
|
101
|
+
if args.debug:
|
|
102
|
+
logger.setLevel(logging.DEBUG)
|
|
103
|
+
logger.debug("Debug mode enabled")
|
|
104
|
+
|
|
105
|
+
# Adjust for 0-based indexing
|
|
106
|
+
start_line = max(0, args.start_line - 1)
|
|
107
|
+
end_line = None if args.end_line is None else max(0, args.end_line - 1)
|
|
108
|
+
start_column = max(0, args.start_column - 1)
|
|
109
|
+
end_column = None if args.end_column is None else max(0, args.end_column - 1)
|
|
110
|
+
|
|
111
|
+
# Resolve file paths
|
|
112
|
+
file1_path = Path(args.file1).resolve()
|
|
113
|
+
file2_path = Path(args.file2).resolve()
|
|
114
|
+
|
|
115
|
+
if not file1_path.exists():
|
|
116
|
+
raise ValueError(f"File not found: {file1_path}")
|
|
117
|
+
if not file2_path.exists():
|
|
118
|
+
raise ValueError(f"File not found: {file2_path}")
|
|
119
|
+
|
|
120
|
+
# Determine file type
|
|
121
|
+
file_type = args.file_type
|
|
122
|
+
if file_type == "auto":
|
|
123
|
+
file_type = detect_file_type(file1_path)
|
|
124
|
+
logger.info(f"Auto-detected file type: {file_type}")
|
|
125
|
+
|
|
126
|
+
# Prepare comparator kwargs
|
|
127
|
+
comparator_kwargs = {
|
|
128
|
+
"encoding": args.encoding,
|
|
129
|
+
"chunk_size": args.chunk_size,
|
|
130
|
+
"verbose": args.verbose or args.debug,
|
|
131
|
+
"num_threads": args.num_threads
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Add file type specific arguments
|
|
135
|
+
if file_type == "json":
|
|
136
|
+
comparator_kwargs["compare_mode"] = args.json_compare_mode
|
|
137
|
+
if args.json_key_field:
|
|
138
|
+
key_fields = [field.strip() for field in args.json_key_field.split(',')]
|
|
139
|
+
comparator_kwargs["key_field"] = key_fields[0] if len(key_fields) == 1 else key_fields
|
|
140
|
+
|
|
141
|
+
if file_type == "h5":
|
|
142
|
+
if args.h5_table:
|
|
143
|
+
tables = [table.strip() for table in args.h5_table.split(',')]
|
|
144
|
+
comparator_kwargs["tables"] = tables
|
|
145
|
+
if args.h5_table_regex:
|
|
146
|
+
comparator_kwargs["table_regex"] = args.h5_table_regex
|
|
147
|
+
comparator_kwargs["structure_only"] = args.h5_structure_only
|
|
148
|
+
comparator_kwargs["show_content_diff"] = args.h5_show_content_diff
|
|
149
|
+
comparator_kwargs["rtol"] = args.h5_rtol
|
|
150
|
+
comparator_kwargs["atol"] = args.h5_atol
|
|
151
|
+
|
|
152
|
+
if file_type == "binary":
|
|
153
|
+
comparator_kwargs["similarity"] = args.similarity
|
|
154
|
+
|
|
155
|
+
# Create comparator and perform comparison
|
|
156
|
+
comparator = ComparatorFactory.create_comparator(file_type, **comparator_kwargs)
|
|
157
|
+
result = comparator.compare_files(
|
|
158
|
+
file1_path,
|
|
159
|
+
file2_path,
|
|
160
|
+
start_line,
|
|
161
|
+
end_line,
|
|
162
|
+
start_column,
|
|
163
|
+
end_column
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Output result
|
|
167
|
+
output = format_result(result, args.output_format)
|
|
168
|
+
print(output)
|
|
169
|
+
|
|
170
|
+
sys.exit(0 if result.identical else 1)
|
|
171
|
+
|
|
172
|
+
except ValueError as ve:
|
|
173
|
+
logger.error(f"ValueError: {ve}")
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.exception(f"An unexpected error occurred")
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
main()
|
|
@@ -2,6 +2,7 @@ from pathlib import Path
|
|
|
2
2
|
from typing import List
|
|
3
3
|
import shlex
|
|
4
4
|
import os
|
|
5
|
+
import shutil
|
|
5
6
|
|
|
6
7
|
class PathResolver:
|
|
7
8
|
def __init__(self, workspace: Path):
|
|
@@ -24,9 +25,18 @@ class PathResolver:
|
|
|
24
25
|
"""
|
|
25
26
|
解析命令路径
|
|
26
27
|
- 系统命令(如echo, ping, dir等)保持原样
|
|
28
|
+
- 已安装的命令(在PATH中可找到)保持原样
|
|
27
29
|
- 相对路径的可执行文件转换为绝对路径
|
|
28
30
|
"""
|
|
29
|
-
#
|
|
31
|
+
# 如果是绝对路径,保持原样
|
|
32
|
+
if Path(command).is_absolute():
|
|
33
|
+
return command
|
|
34
|
+
|
|
35
|
+
# 检查命令是否在系统PATH中
|
|
36
|
+
if shutil.which(command) is not None:
|
|
37
|
+
return command
|
|
38
|
+
|
|
39
|
+
# 常见的系统命令列表(作为备用,以防shutil.which在某些情况下失效)
|
|
30
40
|
system_commands = {
|
|
31
41
|
'echo', 'ping', 'dir', 'ls', 'cat', 'grep', 'find', 'sort',
|
|
32
42
|
'head', 'tail', 'wc', 'curl', 'wget', 'git', 'python', 'node',
|
|
@@ -34,8 +44,8 @@ class PathResolver:
|
|
|
34
44
|
'kubectl', 'helm', 'terraform', 'ansible', 'ssh', 'scp', 'rsync'
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
#
|
|
38
|
-
if command in system_commands
|
|
47
|
+
# 如果在系统命令列表中,保持原样
|
|
48
|
+
if command in system_commands:
|
|
39
49
|
return command
|
|
40
50
|
|
|
41
51
|
# 否则当作相对路径处理
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
cli_test_framework/__init__.py,sha256=
|
|
1
|
+
cli_test_framework/__init__.py,sha256=03ejVkeXhQj_JPYSuPXkJ0CD30kN76tcSNGRgafcYJw,612
|
|
2
2
|
cli_test_framework/cli.py,sha256=RgFyXOo-Dxj1bUjeqhj0l-vXaaMH8lAQcTlVQyzWcdU,4860
|
|
3
|
+
cli_test_framework/commands/__init__.py,sha256=xSnEAKY71h2k-uDpbxzHvoUMU17zAX80686nRRdSV9E,120
|
|
4
|
+
cli_test_framework/commands/compare.py,sha256=qxXU684J7ObHfG-PUa3GhoTFSbmQYbyhXnhplw7Al_k,7706
|
|
3
5
|
cli_test_framework/core/__init__.py,sha256=yDI5DhuduA76F4SvfahJ3KAMyGOxrzoSifauTLY7SOk,304
|
|
4
6
|
cli_test_framework/core/assertions.py,sha256=UEm87MxQ_KS1hTdA7lPBbhroEZtmfvsVK8kxtGgRIXI,1256
|
|
5
7
|
cli_test_framework/core/base_runner.py,sha256=idcFPY2-SEMZmWydBZ93VuNvuVTvSAxkoMqUAVoQb2s,2152
|
|
@@ -21,10 +23,10 @@ cli_test_framework/runners/json_runner.py,sha256=i6h9G7tTIhY9uT-i-Jm50qvnn6myw3c
|
|
|
21
23
|
cli_test_framework/runners/parallel_json_runner.py,sha256=XUHOljkpEADuNSCtnrbUcGgZo7sdbLGbUVyRa2GmH5k,4607
|
|
22
24
|
cli_test_framework/runners/yaml_runner.py,sha256=rIwCmuKQ32dUdp1y3189StIAGGNpQ5175hknI4xvewc,3714
|
|
23
25
|
cli_test_framework/utils/__init__.py,sha256=kiSQretb8Jnm8nuRRTOtweUgFZoM2a5WZOk28JpLlzg,216
|
|
24
|
-
cli_test_framework/utils/path_resolver.py,sha256=
|
|
26
|
+
cli_test_framework/utils/path_resolver.py,sha256=7BWpuZLzJFY0i4REVY5Qfvx_cVnmI9T1d8O6BPPIUPw,9036
|
|
25
27
|
cli_test_framework/utils/report_generator.py,sha256=SEJ4psyQFQ94wXprupmy6QyE2j3c8D9Nwn4KPBYslYo,2924
|
|
26
|
-
cli_test_framework-0.2.
|
|
27
|
-
cli_test_framework-0.2.
|
|
28
|
-
cli_test_framework-0.2.
|
|
29
|
-
cli_test_framework-0.2.
|
|
30
|
-
cli_test_framework-0.2.
|
|
28
|
+
cli_test_framework-0.2.4.dist-info/METADATA,sha256=hLdOzzSyAX5tLhUL_38EDJN5ihLReJkR8VSGHd1HCjw,13149
|
|
29
|
+
cli_test_framework-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
30
|
+
cli_test_framework-0.2.4.dist-info/entry_points.txt,sha256=Gsx18WQq8ICp-F6OkHsUxyO-RSYY8vrvvNwF16enEfM,114
|
|
31
|
+
cli_test_framework-0.2.4.dist-info/top_level.txt,sha256=kCcU27wQVPOFxkzCAlS0IInGH2dHXqH-LO4oxYRBkuE,19
|
|
32
|
+
cli_test_framework-0.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|